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

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

47 

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. 

51 

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

53 Units)"``. 

54 

55 :param order_qty: Numeric quantity. 

56 

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

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

59 

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

61 

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

65 

66 :returns: Display text. 

67 """ 

68 enum = self.app.enum 

69 

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

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

81 

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

86 

87 def item_status_to_variant(self, status_code): 

88 """ 

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

90 

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

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

93 

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

95 

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' 

107 

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. 

111 

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. 

115 

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. 

119 

120 :param items: Sequence of 

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

122 

123 :param user: 

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

125 performing the action. 

126 

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

128 is placed, if known. 

129 

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

131 

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

133 order item. 

134 """ 

135 enum = self.app.enum 

136 

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

142 

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 

148 

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. 

153 

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

155 are "received". 

156 

157 TODO: This also should email the customer notifying their 

158 items are ready for pickup etc. 

159 

160 :param items: Sequence of 

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

162 

163 :param user: 

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

165 performing the action. 

166 

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

168 

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

170 

171 :param invoice_number: Invoice number, if known. 

172 

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

174 order item. 

175 """ 

176 enum = self.app.enum 

177 

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

187 

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 

193 

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

195 """ 

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

197 

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

199 are "ready" (again) for placement. 

200 

201 :param items: Sequence of 

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

203 

204 :param user: 

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

206 performing the action. 

207 

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

209 order item. 

210 """ 

211 enum = self.app.enum 

212 

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 

218 

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

220 """ 

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

222 items. 

223 

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

225 are "contacted" and awaiting delivery. 

226 

227 :param items: Sequence of 

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

229 

230 :param user: 

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

232 performing the action. 

233 

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

235 order item. 

236 """ 

237 enum = self.app.enum 

238 

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 

244 

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

246 """ 

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

248 

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

250 "contact failed". 

251 

252 :param items: Sequence of 

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

254 

255 :param user: 

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

257 performing the action. 

258 

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

260 order item. 

261 """ 

262 enum = self.app.enum 

263 

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 

269 

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

271 """ 

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

273 

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

275 are "delivered". 

276 

277 :param items: Sequence of 

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

279 

280 :param user: 

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

282 performing the action. 

283 

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

285 order item. 

286 """ 

287 enum = self.app.enum 

288 

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 

294 

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

296 """ 

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

298 

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

300 are "restocked". 

301 

302 :param items: Sequence of 

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

304 

305 :param user: 

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

307 performing the action. 

308 

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

310 order item. 

311 """ 

312 enum = self.app.enum 

313 

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