Coverage for src/sideshow/db/model/batch/neworder.py: 100%

64 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-06 15:04 -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(['pending_customer_uuid'], ['sideshow_pending_customer.uuid']), 

62 ) 

63 

64 STATUS_OK = 1 

65 

66 STATUS = { 

67 STATUS_OK : "ok", 

68 } 

69 

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

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

72 """) 

73 

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

75 ID of the proper customer account to which the order pertains, if 

76 applicable. 

77 

78 This will be set only when an "existing" customer account can be 

79 selected for the order. See also :attr:`pending_customer`. 

80 """) 

81 

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

83 

84 @declared_attr 

85 def pending_customer(cls): 

86 return orm.relationship( 

87 'PendingCustomer', 

88 back_populates='new_order_batches', 

89 doc=""" 

90 Reference to the 

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

92 record for the order, if applicable. 

93 

94 This is set only when making an order for a "new / 

95 unknown" customer. See also :attr:`customer_id`. 

96 """) 

97 

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

99 Name for the customer account. 

100 """) 

101 

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

103 Phone number for the customer. 

104 """) 

105 

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

107 Email address for the customer. 

108 """) 

109 

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

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

112 """) 

113 

114 

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

116 """ 

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

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

119 

120 Generic row attributes (undocumented below) are inherited from 

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

122 """ 

123 __tablename__ = 'sideshow_batch_neworder_row' 

124 __batch_class__ = NewOrderBatch 

125 

126 @declared_attr 

127 def __table_args__(cls): 

128 return cls.__default_table_args__() + ( 

129 sa.ForeignKeyConstraint(['pending_product_uuid'], ['sideshow_pending_product.uuid']), 

130 ) 

131 

132 STATUS_OK = 1 

133 """ 

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

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

136 :attr:`pending_product`. 

137 """ 

138 

139 STATUS_MISSING_PRODUCT = 2 

140 """ 

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

142 :attr:`pending_product` set. 

143 """ 

144 

145 STATUS_MISSING_ORDER_QTY = 3 

146 """ 

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

148 :attr:`order_uom` set. 

149 """ 

150 

151 STATUS = { 

152 STATUS_OK : "ok", 

153 STATUS_MISSING_PRODUCT : "missing product", 

154 STATUS_MISSING_ORDER_QTY : "missing order qty/uom", 

155 } 

156 """ 

157 Dict of possible status code -> label options. 

158 """ 

159 

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

161 ID of the true product which the order item represents, if 

162 applicable. 

163 

164 This will be set only when an "existing" product can be selected 

165 for the order. See also :attr:`pending_product`. 

166 """) 

167 

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

169 

170 @declared_attr 

171 def pending_product(cls): 

172 return orm.relationship( 

173 'PendingProduct', 

174 back_populates='new_order_batch_rows', 

175 doc=""" 

176 Reference to the 

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

178 for the order item, if applicable. 

179 

180 This is set only when making an order for a "new / 

181 unknown" product. See also :attr:`product_id`. 

182 """) 

183 

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

185 Scancode for the product, as string. 

186 

187 .. note:: 

188 

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

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

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

192 

193 That may change eventually, depending on POS integration 

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

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

196 """) 

197 

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

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

200 """) 

201 

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

203 Description for the product - up to 255 chars. 

204 """) 

205 

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

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

208 """) 

209 

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

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

212 """) 

213 

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

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

216 """) 

217 

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

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

220 """) 

221 

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

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

224 normally carried by the store. Default is null. 

225 """) 

226 

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

228 Case pack count for the product, if known. 

229 

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

231 """) 

232 

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

234 Quantity (as decimal) of product being ordered. 

235 

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

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

238 """) 

239 

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

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

242 

243 This should be one of the codes from 

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

245 

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

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

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

249 """) 

250 

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

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

253 as decimal to 4 places. 

254 """) 

255 

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

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

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

259 """) 

260 

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

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

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

264 also :attr:`sale_ends`. 

265 """) 

266 

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

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

269 

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

271 """) 

272 

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

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

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

276 

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

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

279 :attr:`unit_price_sale`. 

280 

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

282 """) 

283 

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

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

286 

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

288 used for calculations. 

289 """) 

290 

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

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

293 applicable. 

294 """) 

295 

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

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

298 for the order item. 

299 

300 This is calculated using values from: 

301 

302 * :attr:`unit_price_quoted` 

303 * :attr:`order_qty` 

304 * :attr:`order_uom` 

305 * :attr:`case_size` 

306 * :attr:`discount_percent` 

307 """) 

308 

309 def __str__(self): 

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