Coverage for .tox/coverage/lib/python3.11/site-packages/sideshow/db/model/batch/neworder.py: 100%

72 statements  

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

24Data models for New Order Batch 

25 

26* :class:`NewOrderBatch` 

27* :class:`NewOrderBatchRow` 

28""" 

29 

30import sqlalchemy as sa 

31from sqlalchemy import orm 

32from sqlalchemy.ext.declarative import declared_attr 

33 

34from wuttjamaican.db import model 

35 

36 

37class NewOrderBatch(model.BatchMixin, model.Base): 

38 """ 

39 :term:`Batch <batch>` used for entering new :term:`orders <order>` 

40 into the system. Each batch ultimately becomes an 

41 :class:`~sideshow.db.model.orders.Order`. 

42 

43 See also :class:`~sideshow.batch.neworder.NewOrderBatchHandler` 

44 which is the default :term:`batch handler` for this :term:`batch 

45 type`. 

46 

47 Generic batch attributes (undocumented below) are inherited from 

48 :class:`~wuttjamaican:wuttjamaican.db.model.batch.BatchMixin`. 

49 """ 

50 __tablename__ = 'sideshow_batch_neworder' 

51 __batchrow_class__ = 'NewOrderBatchRow' 

52 

53 batch_type = 'neworder' 

54 """ 

55 Official :term:`batch type` key. 

56 """ 

57 

58 @declared_attr 

59 def __table_args__(cls): 

60 return cls.__default_table_args__() + ( 

61 sa.ForeignKeyConstraint(['local_customer_uuid'], ['sideshow_customer_local.uuid']), 

62 sa.ForeignKeyConstraint(['pending_customer_uuid'], ['sideshow_customer_pending.uuid']), 

63 ) 

64 

65 STATUS_OK = 1 

66 

67 STATUS = { 

68 STATUS_OK : "ok", 

69 } 

70 

71 store_id = sa.Column(sa.String(length=10), nullable=True, doc=""" 

72 ID of the store to which the order pertains, if applicable. 

73 """) 

74 

75 customer_id = sa.Column(sa.String(length=20), nullable=True, doc=""" 

76 Proper account ID for the :term:`external customer` to which the 

77 order pertains, if applicable. 

78 

79 See also :attr:`local_customer` and :attr:`pending_customer`. 

80 """) 

81 

82 local_customer_uuid = sa.Column(model.UUID(), nullable=True) 

83 

84 @declared_attr 

85 def local_customer(cls): 

86 return orm.relationship( 

87 'LocalCustomer', 

88 back_populates='new_order_batches', 

89 doc=""" 

90 Reference to the 

91 :class:`~sideshow.db.model.customers.LocalCustomer` record 

92 for the order, if applicable. 

93 

94 See also :attr:`customer_id` and :attr:`pending_customer`. 

95 """) 

96 

97 pending_customer_uuid = sa.Column(model.UUID(), nullable=True) 

98 

99 @declared_attr 

100 def pending_customer(cls): 

101 return orm.relationship( 

102 'PendingCustomer', 

103 back_populates='new_order_batches', 

104 doc=""" 

105 Reference to the 

106 :class:`~sideshow.db.model.customers.PendingCustomer` 

107 record for the order, if applicable. 

108 

109 See also :attr:`customer_id` and :attr:`local_customer`. 

110 """) 

111 

112 customer_name = sa.Column(sa.String(length=100), nullable=True, doc=""" 

113 Name for the customer account. 

114 """) 

115 

116 phone_number = sa.Column(sa.String(length=20), nullable=True, doc=""" 

117 Phone number for the customer. 

118 """) 

119 

120 email_address = sa.Column(sa.String(length=255), nullable=True, doc=""" 

121 Email address for the customer. 

122 """) 

123 

124 total_price = sa.Column(sa.Numeric(precision=10, scale=3), nullable=True, doc=""" 

125 Full price (not including tax etc.) for all items on the order. 

126 """) 

127 

128 

129class NewOrderBatchRow(model.BatchRowMixin, model.Base): 

130 """ 

131 Row of data within a :class:`NewOrderBatch`. Each row ultimately 

132 becomes an :class:`~sideshow.db.model.orders.OrderItem`. 

133 

134 Generic row attributes (undocumented below) are inherited from 

135 :class:`~wuttjamaican:wuttjamaican.db.model.batch.BatchRowMixin`. 

136 """ 

137 __tablename__ = 'sideshow_batch_neworder_row' 

138 __batch_class__ = NewOrderBatch 

139 

140 @declared_attr 

141 def __table_args__(cls): 

142 return cls.__default_table_args__() + ( 

143 sa.ForeignKeyConstraint(['local_product_uuid'], ['sideshow_product_local.uuid']), 

144 sa.ForeignKeyConstraint(['pending_product_uuid'], ['sideshow_product_pending.uuid']), 

145 ) 

146 

147 STATUS_OK = 1 

148 """ 

149 This is the default value for :attr:`status_code`. All rows are 

150 considered "OK" if they have either a :attr:`product_id` or 

151 :attr:`pending_product`. 

152 """ 

153 

154 STATUS_MISSING_PRODUCT = 2 

155 """ 

156 Status code indicating the row has no :attr:`product_id` or 

157 :attr:`pending_product` set. 

158 """ 

159 

160 STATUS_MISSING_ORDER_QTY = 3 

161 """ 

162 Status code indicating the row has no :attr:`order_qty` and/or 

163 :attr:`order_uom` set. 

164 """ 

165 

166 STATUS = { 

167 STATUS_OK : "ok", 

168 STATUS_MISSING_PRODUCT : "missing product", 

169 STATUS_MISSING_ORDER_QTY : "missing order qty/uom", 

170 } 

171 """ 

172 Dict of possible status code -> label options. 

173 """ 

174 

175 product_id = sa.Column(sa.String(length=20), nullable=True, doc=""" 

176 Proper ID for the :term:`external product` which the order item 

177 represents, if applicable. 

178 

179 See also :attr:`local_product` and :attr:`pending_product`. 

180 """) 

181 

182 local_product_uuid = sa.Column(model.UUID(), nullable=True) 

183 

184 @declared_attr 

185 def local_product(cls): 

186 return orm.relationship( 

187 'LocalProduct', 

188 back_populates='new_order_batch_rows', 

189 doc=""" 

190 Reference to the 

191 :class:`~sideshow.db.model.products.LocalProduct` record 

192 for the order item, if applicable. 

193 

194 See also :attr:`product_id` and :attr:`pending_product`. 

195 """) 

196 

197 pending_product_uuid = sa.Column(model.UUID(), nullable=True) 

198 

199 @declared_attr 

200 def pending_product(cls): 

201 return orm.relationship( 

202 'PendingProduct', 

203 back_populates='new_order_batch_rows', 

204 doc=""" 

205 Reference to the 

206 :class:`~sideshow.db.model.products.PendingProduct` record 

207 for the order item, if applicable. 

208 

209 See also :attr:`product_id` and :attr:`local_product`. 

210 """) 

211 

212 product_scancode = sa.Column(sa.String(length=14), nullable=True, doc=""" 

213 Scancode for the product, as string. 

214 

215 .. note:: 

216 

217 This column allows 14 chars, so can store a full GPC with check 

218 digit. However as of writing the actual format used here does 

219 not matter to Sideshow logic; "anything" should work. 

220 

221 That may change eventually, depending on POS integration 

222 scenarios that come up. Maybe a config option to declare 

223 whether check digit should be included or not, etc. 

224 """) 

225 

226 product_brand = sa.Column(sa.String(length=100), nullable=True, doc=""" 

227 Brand name for the product - up to 100 chars. 

228 """) 

229 

230 product_description = sa.Column(sa.String(length=255), nullable=True, doc=""" 

231 Description for the product - up to 255 chars. 

232 """) 

233 

234 product_size = sa.Column(sa.String(length=30), nullable=True, doc=""" 

235 Size of the product, as string - up to 30 chars. 

236 """) 

237 

238 product_weighed = sa.Column(sa.Boolean(), nullable=True, doc=""" 

239 Flag indicating the product is sold by weight; default is null. 

240 """) 

241 

242 department_id = sa.Column(sa.String(length=10), nullable=True, doc=""" 

243 ID of the department to which the product belongs, if known. 

244 """) 

245 

246 department_name = sa.Column(sa.String(length=30), nullable=True, doc=""" 

247 Name of the department to which the product belongs, if known. 

248 """) 

249 

250 special_order = sa.Column(sa.Boolean(), nullable=True, doc=""" 

251 Flag indicating the item is a "special order" - e.g. something not 

252 normally carried by the store. Default is null. 

253 """) 

254 

255 case_size = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc=""" 

256 Case pack count for the product, if known. 

257 

258 If this is not set, then customer cannot order a "case" of the item. 

259 """) 

260 

261 order_qty = sa.Column(sa.Numeric(precision=10, scale=4), nullable=False, doc=""" 

262 Quantity (as decimal) of product being ordered. 

263 

264 This must be interpreted along with :attr:`order_uom` to determine 

265 the *complete* order quantity, e.g. "2 cases". 

266 """) 

267 

268 order_uom = sa.Column(sa.String(length=10), nullable=False, doc=""" 

269 Code indicating the unit of measure for product being ordered. 

270 

271 This should be one of the codes from 

272 :data:`~sideshow.enum.ORDER_UOM`. 

273 

274 Sideshow will treat :data:`~sideshow.enum.ORDER_UOM_CASE` 

275 differently but :data:`~sideshow.enum.ORDER_UOM_UNIT` and others 

276 are all treated the same (i.e. "unit" is assumed). 

277 """) 

278 

279 unit_cost = sa.Column(sa.Numeric(precision=9, scale=5), nullable=True, doc=""" 

280 Cost of goods amount for one "unit" (not "case") of the product, 

281 as decimal to 4 places. 

282 """) 

283 

284 unit_price_reg = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc=""" 

285 Regular price for the item unit. Unless a sale is in effect, 

286 :attr:`unit_price_quoted` will typically match this value. 

287 """) 

288 

289 unit_price_sale = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc=""" 

290 Sale price for the item unit, if applicable. If set, then 

291 :attr:`unit_price_quoted` will typically match this value. See 

292 also :attr:`sale_ends`. 

293 """) 

294 

295 sale_ends = sa.Column(sa.DateTime(timezone=True), nullable=True, doc=""" 

296 End date/time for the sale in effect, if any. 

297 

298 This is only relevant if :attr:`unit_price_sale` is set. 

299 """) 

300 

301 unit_price_quoted = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc=""" 

302 Quoted price for the item unit. This is the "effective" unit 

303 price, which is used to calculate :attr:`total_price`. 

304 

305 This price does *not* reflect the :attr:`discount_percent`. It 

306 normally should match either :attr:`unit_price_reg` or 

307 :attr:`unit_price_sale`. 

308 

309 See also :attr:`case_price_quoted`, if applicable. 

310 """) 

311 

312 case_price_quoted = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc=""" 

313 Quoted price for a "case" of the item, if applicable. 

314 

315 This is mostly for display purposes; :attr:`unit_price_quoted` is 

316 used for calculations. 

317 """) 

318 

319 discount_percent = sa.Column(sa.Numeric(precision=5, scale=3), nullable=True, doc=""" 

320 Discount percent to apply when calculating :attr:`total_price`, if 

321 applicable. 

322 """) 

323 

324 total_price = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc=""" 

325 Full price (not including tax etc.) which the customer is quoted 

326 for the order item. 

327 

328 This is calculated using values from: 

329 

330 * :attr:`unit_price_quoted` 

331 * :attr:`order_qty` 

332 * :attr:`order_uom` 

333 * :attr:`case_size` 

334 * :attr:`discount_percent` 

335 """) 

336 

337 def __str__(self): 

338 return str(self.pending_product or self.product_description or "")