Coverage for src/sideshow/orders.py: 0%
84 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-26 13:16 -0600
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-26 13:16 -0600
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# Sideshow -- Case/Special Order Tracker
5# Copyright © 2024-2025 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"""
24Sideshow Order Handler
25"""
27from wuttjamaican.app import GenericHandler
30class OrderHandler(GenericHandler):
31 """
32 Base class and default implementation for the :term:`order
33 handler`.
35 This is responsible for business logic involving customer orders
36 after they have been first created. (The :term:`new order batch`
37 handler is responsible for creation logic.)
38 """
40 def get_order_qty_uom_text(self, order_qty, order_uom, case_size=None, html=False):
41 """
42 Return the display text for a given order quantity.
44 Default logic will return something like ``"3 Cases (x 6 = 18
45 Units)"``.
47 :param order_qty: Numeric quantity.
49 :param order_uom: An order UOM constant; should be something
50 from :data:`~sideshow.enum.ORDER_UOM`.
52 :param case_size: Case size for the product, if known.
54 :param html: Whether the return value should include any HTML.
55 If false (the default), it will be plain text only. If
56 true, will replace the ``x`` character with ``×``.
58 :returns: Display text.
59 """
60 enum = self.app.enum
62 if order_uom == enum.ORDER_UOM_CASE:
63 if case_size is None:
64 case_qty = unit_qty = '??'
65 else:
66 case_qty = self.app.render_quantity(case_size)
67 unit_qty = self.app.render_quantity(order_qty * case_size)
68 CS = enum.ORDER_UOM[enum.ORDER_UOM_CASE]
69 EA = enum.ORDER_UOM[enum.ORDER_UOM_UNIT]
70 order_qty = self.app.render_quantity(order_qty)
71 times = '×' if html else 'x'
72 return (f"{order_qty} {CS} ({times} {case_qty} = {unit_qty} {EA})")
74 # units
75 unit_qty = self.app.render_quantity(order_qty)
76 EA = enum.ORDER_UOM[enum.ORDER_UOM_UNIT]
77 return f"{unit_qty} {EA}"
79 def item_status_to_variant(self, status_code):
80 """
81 Return a Buefy style variant for the given status code.
83 Default logic will return ``None`` for "normal" item status,
84 but may return ``'warning'`` for some (e.g. canceled).
86 :param status_code: The status code for an order item.
88 :returns: Style variant string (e.g. ``'warning'``) or
89 ``None``.
90 """
91 enum = self.app.enum
92 if status_code in (enum.ORDER_ITEM_STATUS_CANCELED,
93 enum.ORDER_ITEM_STATUS_REFUND_PENDING,
94 enum.ORDER_ITEM_STATUS_REFUNDED,
95 enum.ORDER_ITEM_STATUS_RESTOCKED,
96 enum.ORDER_ITEM_STATUS_EXPIRED,
97 enum.ORDER_ITEM_STATUS_INACTIVE):
98 return 'warning'
100 def process_placement(self, items, user, vendor_name=None, po_number=None, note=None):
101 """
102 Process the "placement" step for the given order items.
104 This may eventually do something involving an *actual*
105 purchase order, or at least a minimal representation of one,
106 but for now it does not.
108 Instead, this will simply update each item to indicate its new
109 status. A note will be attached to indicate the vendor and/or
110 PO number, if provided.
112 :param items: Sequence of
113 :class:`~sideshow.db.model.orders.OrderItem` records.
115 :param user:
116 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
117 performing the action.
119 :param vendor_name: Name of the vendor to which purchase order
120 is placed, if known.
122 :param po_number: Purchase order number, if known.
124 :param note: Optional *additional* note to be attached to each
125 order item.
126 """
127 enum = self.app.enum
129 placed = None
130 if vendor_name:
131 placed = f"PO {po_number or ''} for vendor {vendor_name}"
132 elif po_number:
133 placed = f"PO {po_number}"
135 for item in items:
136 item.add_event(enum.ORDER_ITEM_EVENT_PLACED, user, note=placed)
137 if note:
138 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
139 item.status_code = enum.ORDER_ITEM_STATUS_PLACED
141 def process_receiving(self, items, user, vendor_name=None,
142 invoice_number=None, po_number=None, note=None):
143 """
144 Process the "receiving" step for the given order items.
146 This will update the status for each item, to indicate they
147 are "received".
149 TODO: This also should email the customer notifying their
150 items are ready for pickup etc.
152 :param items: Sequence of
153 :class:`~sideshow.db.model.orders.OrderItem` records.
155 :param user:
156 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
157 performing the action.
159 :param vendor_name: Name of the vendor, if known.
161 :param po_number: Purchase order number, if known.
163 :param invoice_number: Invoice number, if known.
165 :param note: Optional *additional* note to be attached to each
166 order item.
167 """
168 enum = self.app.enum
170 received = None
171 if invoice_number and po_number and vendor_name:
172 received = f"invoice {invoice_number} (PO {po_number}) from vendor {vendor_name}"
173 elif invoice_number and vendor_name:
174 received = f"invoice {invoice_number} from vendor {vendor_name}"
175 elif po_number and vendor_name:
176 received = f"PO {po_number} from vendor {vendor_name}"
177 elif vendor_name:
178 received = f"from vendor {vendor_name}"
180 for item in items:
181 item.add_event(enum.ORDER_ITEM_EVENT_RECEIVED, user, note=received)
182 if note:
183 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
184 item.status_code = enum.ORDER_ITEM_STATUS_RECEIVED
186 def process_reorder(self, items, user, note=None):
187 """
188 Process the "reorder" step for the given order items.
190 This will update the status for each item, to indicate they
191 are "ready" (again) for placement.
193 :param items: Sequence of
194 :class:`~sideshow.db.model.orders.OrderItem` records.
196 :param user:
197 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
198 performing the action.
200 :param note: Optional *additional* note to be attached to each
201 order item.
202 """
203 enum = self.app.enum
205 for item in items:
206 item.add_event(enum.ORDER_ITEM_EVENT_REORDER, user)
207 if note:
208 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
209 item.status_code = enum.ORDER_ITEM_STATUS_READY
211 def process_contact_success(self, items, user, note=None):
212 """
213 Process the "successful contact" step for the given order
214 items.
216 This will update the status for each item, to indicate they
217 are "contacted" and awaiting delivery.
219 :param items: Sequence of
220 :class:`~sideshow.db.model.orders.OrderItem` records.
222 :param user:
223 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
224 performing the action.
226 :param note: Optional *additional* note to be attached to each
227 order item.
228 """
229 enum = self.app.enum
231 for item in items:
232 item.add_event(enum.ORDER_ITEM_EVENT_CONTACTED, user)
233 if note:
234 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
235 item.status_code = enum.ORDER_ITEM_STATUS_CONTACTED
237 def process_contact_failure(self, items, user, note=None):
238 """
239 Process the "failed contact" step for the given order items.
241 This will update the status for each item, to indicate
242 "contact failed".
244 :param items: Sequence of
245 :class:`~sideshow.db.model.orders.OrderItem` records.
247 :param user:
248 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
249 performing the action.
251 :param note: Optional *additional* note to be attached to each
252 order item.
253 """
254 enum = self.app.enum
256 for item in items:
257 item.add_event(enum.ORDER_ITEM_EVENT_CONTACT_FAILED, user)
258 if note:
259 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
260 item.status_code = enum.ORDER_ITEM_STATUS_CONTACT_FAILED
262 def process_delivery(self, items, user, note=None):
263 """
264 Process the "delivery" step for the given order items.
266 This will update the status for each item, to indicate they
267 are "delivered".
269 :param items: Sequence of
270 :class:`~sideshow.db.model.orders.OrderItem` records.
272 :param user:
273 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
274 performing the action.
276 :param note: Optional *additional* note to be attached to each
277 order item.
278 """
279 enum = self.app.enum
281 for item in items:
282 item.add_event(enum.ORDER_ITEM_EVENT_DELIVERED, user)
283 if note:
284 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
285 item.status_code = enum.ORDER_ITEM_STATUS_DELIVERED
287 def process_restock(self, items, user, note=None):
288 """
289 Process the "restock" step for the given order items.
291 This will update the status for each item, to indicate they
292 are "restocked".
294 :param items: Sequence of
295 :class:`~sideshow.db.model.orders.OrderItem` records.
297 :param user:
298 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
299 performing the action.
301 :param note: Optional *additional* note to be attached to each
302 order item.
303 """
304 enum = self.app.enum
306 for item in items:
307 item.add_event(enum.ORDER_ITEM_EVENT_RESTOCKED, user)
308 if note:
309 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
310 item.status_code = enum.ORDER_ITEM_STATUS_RESTOCKED