Coverage for src/sideshow/orders.py: 15%
86 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-20 09:03 -0600
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-20 09:03 -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 expose_store_id(self):
41 """
42 Returns boolean indicating whether the ``store_id`` field
43 should be exposed at all. This is false by default.
44 """
45 return self.config.get_bool('sideshow.orders.expose_store_id',
46 default=False)
48 def get_order_qty_uom_text(self, order_qty, order_uom, case_size=None, html=False):
49 """
50 Return the display text for a given order quantity.
52 Default logic will return something like ``"3 Cases (x 6 = 18
53 Units)"``.
55 :param order_qty: Numeric quantity.
57 :param order_uom: An order UOM constant; should be something
58 from :data:`~sideshow.enum.ORDER_UOM`.
60 :param case_size: Case size for the product, if known.
62 :param html: Whether the return value should include any HTML.
63 If false (the default), it will be plain text only. If
64 true, will replace the ``x`` character with ``×``.
66 :returns: Display text.
67 """
68 enum = self.app.enum
70 if order_uom == enum.ORDER_UOM_CASE:
71 if case_size is None:
72 case_qty = unit_qty = '??'
73 else:
74 case_qty = self.app.render_quantity(case_size)
75 unit_qty = self.app.render_quantity(order_qty * case_size)
76 CS = enum.ORDER_UOM[enum.ORDER_UOM_CASE]
77 EA = enum.ORDER_UOM[enum.ORDER_UOM_UNIT]
78 order_qty = self.app.render_quantity(order_qty)
79 times = '×' if html else 'x'
80 return (f"{order_qty} {CS} ({times} {case_qty} = {unit_qty} {EA})")
82 # units
83 unit_qty = self.app.render_quantity(order_qty)
84 EA = enum.ORDER_UOM[enum.ORDER_UOM_UNIT]
85 return f"{unit_qty} {EA}"
87 def item_status_to_variant(self, status_code):
88 """
89 Return a Buefy style variant for the given status code.
91 Default logic will return ``None`` for "normal" item status,
92 but may return ``'warning'`` for some (e.g. canceled).
94 :param status_code: The status code for an order item.
96 :returns: Style variant string (e.g. ``'warning'``) or
97 ``None``.
98 """
99 enum = self.app.enum
100 if status_code in (enum.ORDER_ITEM_STATUS_CANCELED,
101 enum.ORDER_ITEM_STATUS_REFUND_PENDING,
102 enum.ORDER_ITEM_STATUS_REFUNDED,
103 enum.ORDER_ITEM_STATUS_RESTOCKED,
104 enum.ORDER_ITEM_STATUS_EXPIRED,
105 enum.ORDER_ITEM_STATUS_INACTIVE):
106 return 'warning'
108 def process_placement(self, items, user, vendor_name=None, po_number=None, note=None):
109 """
110 Process the "placement" step for the given order items.
112 This may eventually do something involving an *actual*
113 purchase order, or at least a minimal representation of one,
114 but for now it does not.
116 Instead, this will simply update each item to indicate its new
117 status. A note will be attached to indicate the vendor and/or
118 PO number, if provided.
120 :param items: Sequence of
121 :class:`~sideshow.db.model.orders.OrderItem` records.
123 :param user:
124 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
125 performing the action.
127 :param vendor_name: Name of the vendor to which purchase order
128 is placed, if known.
130 :param po_number: Purchase order number, if known.
132 :param note: Optional *additional* note to be attached to each
133 order item.
134 """
135 enum = self.app.enum
137 placed = None
138 if vendor_name:
139 placed = f"PO {po_number or ''} for vendor {vendor_name}"
140 elif po_number:
141 placed = f"PO {po_number}"
143 for item in items:
144 item.add_event(enum.ORDER_ITEM_EVENT_PLACED, user, note=placed)
145 if note:
146 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
147 item.status_code = enum.ORDER_ITEM_STATUS_PLACED
149 def process_receiving(self, items, user, vendor_name=None,
150 invoice_number=None, po_number=None, note=None):
151 """
152 Process the "receiving" step for the given order items.
154 This will update the status for each item, to indicate they
155 are "received".
157 TODO: This also should email the customer notifying their
158 items are ready for pickup etc.
160 :param items: Sequence of
161 :class:`~sideshow.db.model.orders.OrderItem` records.
163 :param user:
164 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
165 performing the action.
167 :param vendor_name: Name of the vendor, if known.
169 :param po_number: Purchase order number, if known.
171 :param invoice_number: Invoice number, if known.
173 :param note: Optional *additional* note to be attached to each
174 order item.
175 """
176 enum = self.app.enum
178 received = None
179 if invoice_number and po_number and vendor_name:
180 received = f"invoice {invoice_number} (PO {po_number}) from vendor {vendor_name}"
181 elif invoice_number and vendor_name:
182 received = f"invoice {invoice_number} from vendor {vendor_name}"
183 elif po_number and vendor_name:
184 received = f"PO {po_number} from vendor {vendor_name}"
185 elif vendor_name:
186 received = f"from vendor {vendor_name}"
188 for item in items:
189 item.add_event(enum.ORDER_ITEM_EVENT_RECEIVED, user, note=received)
190 if note:
191 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
192 item.status_code = enum.ORDER_ITEM_STATUS_RECEIVED
194 def process_reorder(self, items, user, note=None):
195 """
196 Process the "reorder" step for the given order items.
198 This will update the status for each item, to indicate they
199 are "ready" (again) for placement.
201 :param items: Sequence of
202 :class:`~sideshow.db.model.orders.OrderItem` records.
204 :param user:
205 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
206 performing the action.
208 :param note: Optional *additional* note to be attached to each
209 order item.
210 """
211 enum = self.app.enum
213 for item in items:
214 item.add_event(enum.ORDER_ITEM_EVENT_REORDER, user)
215 if note:
216 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
217 item.status_code = enum.ORDER_ITEM_STATUS_READY
219 def process_contact_success(self, items, user, note=None):
220 """
221 Process the "successful contact" step for the given order
222 items.
224 This will update the status for each item, to indicate they
225 are "contacted" and awaiting delivery.
227 :param items: Sequence of
228 :class:`~sideshow.db.model.orders.OrderItem` records.
230 :param user:
231 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
232 performing the action.
234 :param note: Optional *additional* note to be attached to each
235 order item.
236 """
237 enum = self.app.enum
239 for item in items:
240 item.add_event(enum.ORDER_ITEM_EVENT_CONTACTED, user)
241 if note:
242 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
243 item.status_code = enum.ORDER_ITEM_STATUS_CONTACTED
245 def process_contact_failure(self, items, user, note=None):
246 """
247 Process the "failed contact" step for the given order items.
249 This will update the status for each item, to indicate
250 "contact failed".
252 :param items: Sequence of
253 :class:`~sideshow.db.model.orders.OrderItem` records.
255 :param user:
256 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
257 performing the action.
259 :param note: Optional *additional* note to be attached to each
260 order item.
261 """
262 enum = self.app.enum
264 for item in items:
265 item.add_event(enum.ORDER_ITEM_EVENT_CONTACT_FAILED, user)
266 if note:
267 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
268 item.status_code = enum.ORDER_ITEM_STATUS_CONTACT_FAILED
270 def process_delivery(self, items, user, note=None):
271 """
272 Process the "delivery" step for the given order items.
274 This will update the status for each item, to indicate they
275 are "delivered".
277 :param items: Sequence of
278 :class:`~sideshow.db.model.orders.OrderItem` records.
280 :param user:
281 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
282 performing the action.
284 :param note: Optional *additional* note to be attached to each
285 order item.
286 """
287 enum = self.app.enum
289 for item in items:
290 item.add_event(enum.ORDER_ITEM_EVENT_DELIVERED, user)
291 if note:
292 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
293 item.status_code = enum.ORDER_ITEM_STATUS_DELIVERED
295 def process_restock(self, items, user, note=None):
296 """
297 Process the "restock" step for the given order items.
299 This will update the status for each item, to indicate they
300 are "restocked".
302 :param items: Sequence of
303 :class:`~sideshow.db.model.orders.OrderItem` records.
305 :param user:
306 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
307 performing the action.
309 :param note: Optional *additional* note to be attached to each
310 order item.
311 """
312 enum = self.app.enum
314 for item in items:
315 item.add_event(enum.ORDER_ITEM_EVENT_RESTOCKED, user)
316 if note:
317 item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note)
318 item.status_code = enum.ORDER_ITEM_STATUS_RESTOCKED