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
« 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"""
27from wuttaweb.views import MasterView
28from wuttaweb.forms.schema import UserRef, WuttaEnum, WuttaMoney
30from sideshow.db.model import PendingProduct
33class PendingProductView(MasterView):
34 """
35 Master view for
36 :class:`~sideshow.db.model.products.PendingProduct`; route
37 prefix is ``pending_products``.
39 Notable URLs provided by this class:
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'
52 labels = {
53 'product_id': "Product ID",
54 }
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 ]
71 sort_defaults = 'scancode'
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 ]
95 def configure_grid(self, g):
96 """ """
97 super().configure_grid(g)
98 enum = self.app.enum
100 # unit_cost
101 g.set_renderer('unit_cost', 'currency', scale=4)
103 # unit_price_reg
104 g.set_label('unit_price_reg', "Reg. Price", column_only=True)
105 g.set_renderer('unit_price_reg', 'currency')
107 # status
108 g.set_renderer('status', self.grid_render_enum, enum=enum.PendingProductStatus)
110 # links
111 g.set_link('scancode')
112 g.set_link('brand_name')
113 g.set_link('description')
114 g.set_link('size')
116 def configure_form(self, f):
117 """ """
118 super().configure_form(f)
119 enum = self.app.enum
120 product = f.model_instance
122 # product_id
123 if self.creating:
124 f.remove('product_id')
125 else:
126 f.set_readonly('product_id')
128 # unit_price_reg
129 f.set_node('unit_price_reg', WuttaMoney(self.request))
131 # notes
132 f.set_widget('notes', 'notes')
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')
141 # created
142 if self.creating:
143 f.remove('created')
144 else:
145 f.set_readonly('created')
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')
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))
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))
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()
173 orders = set([item.order for item in product.order_items])
174 orders = sorted(orders, key=lambda order: order.order_id)
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 })
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')
197 return grid
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()
206 batches = set([row.batch for row in product.new_order_batch_rows])
207 batches = sorted(batches, key=lambda batch: batch.id)
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 })
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')
232 return grid
234 def delete_instance(self, product):
235 """ """
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))
245 # go ahead and delete per usual
246 super().delete_instance(product)
249def defaults(config, **kwargs):
250 base = globals()
252 PendingProductView = kwargs.get('PendingProductView', base['PendingProductView'])
253 PendingProductView.defaults(config)
256def includeme(config):
257 defaults(config)