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

43 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 Products 

25""" 

26 

27import datetime 

28 

29import sqlalchemy as sa 

30from sqlalchemy import orm 

31 

32from wuttjamaican.db import model 

33 

34from sideshow.enum import PendingProductStatus 

35 

36 

37class ProductMixin: 

38 """ 

39 Base class for product tables. This has shared columns, used by e.g.: 

40 

41 * :class:`LocalProduct` 

42 * :class:`PendingProduct` 

43 """ 

44 

45 scancode = sa.Column(sa.String(length=14), nullable=True, doc=""" 

46 Scancode for the product, as string. 

47 

48 .. note:: 

49 

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

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

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

53 

54 That may change eventually, depending on POS integration 

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

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

57 """) 

58 

59 brand_name = sa.Column(sa.String(length=100), nullable=True, doc=""" 

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

61 """) 

62 

63 description = sa.Column(sa.String(length=255), nullable=True, doc=""" 

64 Description for the product - up to 255 chars. 

65 """) 

66 

67 size = sa.Column(sa.String(length=30), nullable=True, doc=""" 

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

69 """) 

70 

71 weighed = sa.Column(sa.Boolean(), nullable=True, doc=""" 

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

73 """) 

74 

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

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

77 """) 

78 

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

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

81 """) 

82 

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

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

85 normally carried by the store. Default is null. 

86 """) 

87 

88 vendor_name = sa.Column(sa.String(length=50), nullable=True, doc=""" 

89 Name of vendor from which product may be purchased, if known. See 

90 also :attr:`vendor_item_code`. 

91 """) 

92 

93 vendor_item_code = sa.Column(sa.String(length=20), nullable=True, doc=""" 

94 Item code (SKU) to use when ordering this product from the vendor 

95 identified by :attr:`vendor_name`, if known. 

96 """) 

97 

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

99 Case pack count for the product, if known. 

100 """) 

101 

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

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

104 as decimal to 4 places. 

105 """) 

106 

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

108 Regular price for a "unit" of the product. 

109 """) 

110 

111 notes = sa.Column(sa.Text(), nullable=True, doc=""" 

112 Arbitrary notes regarding the product, if applicable. 

113 """) 

114 

115 @property 

116 def full_description(self): 

117 """ """ 

118 fields = [ 

119 self.brand_name or '', 

120 self.description or '', 

121 self.size or ''] 

122 fields = [f.strip() for f in fields if f.strip()] 

123 return ' '.join(fields) 

124 

125 def __str__(self): 

126 return self.full_description 

127 

128 

129class LocalProduct(ProductMixin, model.Base): 

130 """ 

131 This table contains the :term:`local product` records. 

132 

133 Sideshow will do customer lookups against this table by default, 

134 unless it's configured to use :term:`external products <external 

135 product>` instead. 

136 

137 Also by default, when a :term:`new order batch` with 

138 :term:`pending product(s) <pending product>` is executed, new 

139 record(s) will be added to this local products table, for lookup 

140 next time. 

141 """ 

142 __tablename__ = 'sideshow_product_local' 

143 

144 uuid = model.uuid_column() 

145 

146 external_id = sa.Column(sa.String(length=20), nullable=True, doc=""" 

147 ID of the true external product associated with this record, if 

148 applicable. 

149 """) 

150 

151 order_items = orm.relationship( 

152 'OrderItem', 

153 back_populates='local_product', 

154 cascade_backrefs=False, 

155 doc=""" 

156 List of :class:`~sideshow.db.model.orders.OrderItem` records 

157 associated with this product. 

158 """) 

159 

160 new_order_batch_rows = orm.relationship( 

161 'NewOrderBatchRow', 

162 back_populates='local_product', 

163 cascade_backrefs=False, 

164 doc=""" 

165 List of 

166 :class:`~sideshow.db.model.batch.neworder.NewOrderBatchRow` 

167 records associated with this product. 

168 """) 

169 

170 

171class PendingProduct(ProductMixin, model.Base): 

172 """ 

173 This table contains the :term:`pending product` records, used when 

174 creating an :term:`order` for new/unknown product(s). 

175 

176 Sideshow will automatically create and (hopefully) delete these 

177 records as needed. 

178 

179 By default, when a :term:`new order batch` with pending product(s) 

180 is executed, new record(s) will be added to the :term:`local 

181 products <local product>` table, for lookup next time. 

182 """ 

183 __tablename__ = 'sideshow_product_pending' 

184 

185 uuid = model.uuid_column() 

186 

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

188 ID of the true product associated with this record, if applicable. 

189 """) 

190 

191 status = sa.Column(sa.Enum(PendingProductStatus), nullable=False, doc=""" 

192 Status code for the product record. 

193 """) 

194 

195 created = sa.Column(sa.DateTime(timezone=True), nullable=False, 

196 default=datetime.datetime.now, doc=""" 

197 Timestamp when the product record was created. 

198 """) 

199 

200 created_by_uuid = model.uuid_fk_column('user.uuid', nullable=False) 

201 created_by = orm.relationship( 

202 model.User, 

203 cascade_backrefs=False, 

204 doc=""" 

205 Reference to the 

206 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` who 

207 created the product record. 

208 """) 

209 

210 order_items = orm.relationship( 

211 'OrderItem', 

212 back_populates='pending_product', 

213 cascade_backrefs=False, 

214 doc=""" 

215 List of :class:`~sideshow.db.model.orders.OrderItem` records 

216 associated with this product. 

217 """) 

218 

219 new_order_batch_rows = orm.relationship( 

220 'NewOrderBatchRow', 

221 back_populates='pending_product', 

222 cascade_backrefs=False, 

223 doc=""" 

224 List of 

225 :class:`~sideshow.db.model.batch.neworder.NewOrderBatchRow` 

226 records associated with this product. 

227 """)