Coverage for src/sideshow/web/views/products.py: 100%

84 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-06 15:40 -0600

1# -*- coding: utf-8; -*- 

2################################################################################ 

3# 

4# Sideshow -- Case/Special Order Tracker 

5# Copyright © 2024 Lance Edgar 

6# 

7# This file is part of Sideshow. 

8# 

9# Sideshow is free software: you can redistribute it and/or modify it 

10# under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# Sideshow is distributed in the hope that it will be useful, but 

15# WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 

17# General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with Sideshow. If not, see <http://www.gnu.org/licenses/>. 

21# 

22################################################################################ 

23""" 

24Views for Products 

25""" 

26 

27from wuttaweb.views import MasterView 

28from wuttaweb.forms.schema import UserRef, WuttaEnum, WuttaMoney 

29 

30from sideshow.db.model import PendingProduct 

31 

32 

33class PendingProductView(MasterView): 

34 """ 

35 Master view for 

36 :class:`~sideshow.db.model.products.PendingProduct`; route 

37 prefix is ``pending_products``. 

38 

39 Notable URLs provided by this class: 

40 

41 * ``/pending/products/`` 

42 * ``/pending/products/new`` 

43 * ``/pending/products/XXX`` 

44 * ``/pending/products/XXX/edit`` 

45 * ``/pending/products/XXX/delete`` 

46 """ 

47 model_class = PendingProduct 

48 model_title = "Pending Product" 

49 route_prefix = 'pending_products' 

50 url_prefix = '/pending/products' 

51 

52 labels = { 

53 'product_id': "Product ID", 

54 } 

55 

56 grid_columns = [ 

57 'scancode', 

58 'department_name', 

59 'brand_name', 

60 'description', 

61 'size', 

62 'unit_cost', 

63 'case_size', 

64 'unit_price_reg', 

65 'special_order', 

66 'status', 

67 'created', 

68 'created_by', 

69 ] 

70 

71 sort_defaults = 'scancode' 

72 

73 form_fields = [ 

74 'product_id', 

75 'scancode', 

76 'department_id', 

77 'department_name', 

78 'brand_name', 

79 'description', 

80 'size', 

81 'vendor_name', 

82 'vendor_item_code', 

83 'unit_cost', 

84 'case_size', 

85 'unit_price_reg', 

86 'special_order', 

87 'notes', 

88 'status', 

89 'created', 

90 'created_by', 

91 'orders', 

92 'new_order_batches', 

93 ] 

94 

95 def configure_grid(self, g): 

96 """ """ 

97 super().configure_grid(g) 

98 enum = self.app.enum 

99 

100 # unit_cost 

101 g.set_renderer('unit_cost', 'currency', scale=4) 

102 

103 # unit_price_reg 

104 g.set_label('unit_price_reg', "Reg. Price", column_only=True) 

105 g.set_renderer('unit_price_reg', 'currency') 

106 

107 # status 

108 g.set_renderer('status', self.grid_render_enum, enum=enum.PendingProductStatus) 

109 

110 # links 

111 g.set_link('scancode') 

112 g.set_link('brand_name') 

113 g.set_link('description') 

114 g.set_link('size') 

115 

116 def configure_form(self, f): 

117 """ """ 

118 super().configure_form(f) 

119 enum = self.app.enum 

120 product = f.model_instance 

121 

122 # product_id 

123 if self.creating: 

124 f.remove('product_id') 

125 else: 

126 f.set_readonly('product_id') 

127 

128 # unit_price_reg 

129 f.set_node('unit_price_reg', WuttaMoney(self.request)) 

130 

131 # notes 

132 f.set_widget('notes', 'notes') 

133 

134 # status 

135 if self.creating: 

136 f.remove('status') 

137 else: 

138 f.set_node('status', WuttaEnum(self.request, enum.PendingProductStatus)) 

139 f.set_readonly('status') 

140 

141 # created 

142 if self.creating: 

143 f.remove('created') 

144 else: 

145 f.set_readonly('created') 

146 

147 # created_by 

148 if self.creating: 

149 f.remove('created_by') 

150 else: 

151 f.set_node('created_by', UserRef(self.request)) 

152 f.set_readonly('created_by') 

153 

154 # orders 

155 if self.creating or self.editing: 

156 f.remove('orders') 

157 else: 

158 f.set_grid('orders', self.make_orders_grid(product)) 

159 

160 # new_order_batches 

161 if self.creating or self.editing: 

162 f.remove('new_order_batches') 

163 else: 

164 f.set_grid('new_order_batches', self.make_new_order_batches_grid(product)) 

165 

166 def make_orders_grid(self, product): 

167 """ 

168 Make and return the grid for the Orders field. 

169 """ 

170 model = self.app.model 

171 route_prefix = self.get_route_prefix() 

172 

173 orders = set([item.order for item in product.order_items]) 

174 orders = sorted(orders, key=lambda order: order.order_id) 

175 

176 grid = self.make_grid(key=f'{route_prefix}.view.orders', 

177 model_class=model.Order, 

178 data=orders, 

179 columns=[ 

180 'order_id', 

181 'total_price', 

182 'created', 

183 'created_by', 

184 ], 

185 labels={ 

186 'order_id': "Order ID", 

187 }, 

188 renderers={ 

189 'total_price': 'currency', 

190 }) 

191 

192 if self.request.has_perm('orders.view'): 

193 url = lambda order, i: self.request.route_url('orders.view', uuid=order.uuid) 

194 grid.add_action('view', icon='eye', url=url) 

195 grid.set_link('order_id') 

196 

197 return grid 

198 

199 def make_new_order_batches_grid(self, product): 

200 """ 

201 Make and return the grid for the New Order Batches field. 

202 """ 

203 model = self.app.model 

204 route_prefix = self.get_route_prefix() 

205 

206 batches = set([row.batch for row in product.new_order_batch_rows]) 

207 batches = sorted(batches, key=lambda batch: batch.id) 

208 

209 grid = self.make_grid(key=f'{route_prefix}.view.new_order_batches', 

210 model_class=model.NewOrderBatch, 

211 data=batches, 

212 columns=[ 

213 'id', 

214 'total_price', 

215 'created', 

216 'created_by', 

217 'executed', 

218 ], 

219 labels={ 

220 'id': "Batch ID", 

221 'status_code': "Status", 

222 }, 

223 renderers={ 

224 'id': 'batch_id', 

225 }) 

226 

227 if self.request.has_perm('neworder_batches.view'): 

228 url = lambda batch, i: self.request.route_url('neworder_batches.view', uuid=batch.uuid) 

229 grid.add_action('view', icon='eye', url=url) 

230 grid.set_link('id') 

231 

232 return grid 

233 

234 def delete_instance(self, product): 

235 """ """ 

236 

237 # avoid deleting if still referenced by new order batch(es) 

238 for row in product.new_order_batch_rows: 

239 if not row.batch.executed: 

240 model_title = self.get_model_title() 

241 self.request.session.flash(f"Cannot delete {model_title} still attached " 

242 "to New Order Batch(es)", 'warning') 

243 raise self.redirect(self.get_action_url('view', product)) 

244 

245 # go ahead and delete per usual 

246 super().delete_instance(product) 

247 

248 

249def defaults(config, **kwargs): 

250 base = globals() 

251 

252 PendingProductView = kwargs.get('PendingProductView', base['PendingProductView']) 

253 PendingProductView.defaults(config) 

254 

255 

256def includeme(config): 

257 defaults(config)