Coverage for src/sideshow/orders.py: 89%

84 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-23 16:52 -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""" 

26 

27from wuttjamaican.app import GenericHandler 

28 

29 

30class OrderHandler(GenericHandler): 

31 """ 

32 Base class and default implementation for the :term:`order 

33 handler`. 

34 

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 """ 

39 

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. 

43 

44 Default logic will return something like ``"3 Cases (x 6 = 18 

45 Units)"``. 

46 

47 :param order_qty: Numeric quantity. 

48 

49 :param order_uom: An order UOM constant; should be something 

50 from :data:`~sideshow.enum.ORDER_UOM`. 

51 

52 :param case_size: Case size for the product, if known. 

53 

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 ``&times;``. 

57 

58 :returns: Display text. 

59 """ 

60 enum = self.app.enum 

61 

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 = '&times;' if html else 'x' 

72 return (f"{order_qty} {CS} ({times} {case_qty} = {unit_qty} {EA})") 

73 

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}" 

78 

79 def item_status_to_variant(self, status_code): 

80 """ 

81 Return a Buefy style variant for the given status code. 

82 

83 Default logic will return ``None`` for "normal" item status, 

84 but may return ``'warning'`` for some (e.g. canceled). 

85 

86 :param status_code: The status code for an order item. 

87 

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' 

99 

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. 

103 

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. 

107 

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. 

111 

112 :param items: Sequence of 

113 :class:`~sideshow.db.model.orders.OrderItem` records. 

114 

115 :param user: 

116 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` 

117 performing the action. 

118 

119 :param vendor_name: Name of the vendor to which purchase order 

120 is placed, if known. 

121 

122 :param po_number: Purchase order number, if known. 

123 

124 :param note: Optional *additional* note to be attached to each 

125 order item. 

126 """ 

127 enum = self.app.enum 

128 

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}" 

134 

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 

140 

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. 

145 

146 This will update the status for each item, to indicate they 

147 are "received". 

148 

149 TODO: This also should email the customer notifying their 

150 items are ready for pickup etc. 

151 

152 :param items: Sequence of 

153 :class:`~sideshow.db.model.orders.OrderItem` records. 

154 

155 :param user: 

156 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` 

157 performing the action. 

158 

159 :param vendor_name: Name of the vendor, if known. 

160 

161 :param po_number: Purchase order number, if known. 

162 

163 :param invoice_number: Invoice number, if known. 

164 

165 :param note: Optional *additional* note to be attached to each 

166 order item. 

167 """ 

168 enum = self.app.enum 

169 

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}" 

179 

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 

185 

186 def process_reorder(self, items, user, note=None): 

187 """ 

188 Process the "reorder" step for the given order items. 

189 

190 This will update the status for each item, to indicate they 

191 are "ready" (again) for placement. 

192 

193 :param items: Sequence of 

194 :class:`~sideshow.db.model.orders.OrderItem` records. 

195 

196 :param user: 

197 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` 

198 performing the action. 

199 

200 :param note: Optional *additional* note to be attached to each 

201 order item. 

202 """ 

203 enum = self.app.enum 

204 

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 

210 

211 def process_contact_success(self, items, user, note=None): 

212 """ 

213 Process the "successful contact" step for the given order 

214 items. 

215 

216 This will update the status for each item, to indicate they 

217 are "contacted" and awaiting delivery. 

218 

219 :param items: Sequence of 

220 :class:`~sideshow.db.model.orders.OrderItem` records. 

221 

222 :param user: 

223 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` 

224 performing the action. 

225 

226 :param note: Optional *additional* note to be attached to each 

227 order item. 

228 """ 

229 enum = self.app.enum 

230 

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 

236 

237 def process_contact_failure(self, items, user, note=None): 

238 """ 

239 Process the "failed contact" step for the given order items. 

240 

241 This will update the status for each item, to indicate 

242 "contact failed". 

243 

244 :param items: Sequence of 

245 :class:`~sideshow.db.model.orders.OrderItem` records. 

246 

247 :param user: 

248 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` 

249 performing the action. 

250 

251 :param note: Optional *additional* note to be attached to each 

252 order item. 

253 """ 

254 enum = self.app.enum 

255 

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 

261 

262 def process_delivery(self, items, user, note=None): 

263 """ 

264 Process the "delivery" step for the given order items. 

265 

266 This will update the status for each item, to indicate they 

267 are "delivered". 

268 

269 :param items: Sequence of 

270 :class:`~sideshow.db.model.orders.OrderItem` records. 

271 

272 :param user: 

273 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` 

274 performing the action. 

275 

276 :param note: Optional *additional* note to be attached to each 

277 order item. 

278 """ 

279 enum = self.app.enum 

280 

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 

286 

287 def process_restock(self, items, user, note=None): 

288 """ 

289 Process the "restock" step for the given order items. 

290 

291 This will update the status for each item, to indicate they 

292 are "restocked". 

293 

294 :param items: Sequence of 

295 :class:`~sideshow.db.model.orders.OrderItem` records. 

296 

297 :param user: 

298 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` 

299 performing the action. 

300 

301 :param note: Optional *additional* note to be attached to each 

302 order item. 

303 """ 

304 enum = self.app.enum 

305 

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