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

36 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 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 PendingProduct(model.Base): 

38 """ 

39 A "pending" product record, used when entering an :term:`order 

40 item` for new/unknown product. 

41 """ 

42 __tablename__ = 'sideshow_pending_product' 

43 

44 uuid = model.uuid_column() 

45 

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

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

48 """) 

49 

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

51 Scancode for the product, as string. 

52 

53 .. note:: 

54 

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

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

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

58 

59 That may change eventually, depending on POS integration 

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

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

62 """) 

63 

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

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

66 """) 

67 

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

69 Description for the product - up to 255 chars. 

70 """) 

71 

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

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

74 """) 

75 

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

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

78 """) 

79 

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

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

82 """) 

83 

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

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

86 """) 

87 

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

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

90 normally carried by the store. Default is null. 

91 """) 

92 

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

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

95 also :attr:`vendor_item_code`. 

96 """) 

97 

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

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

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

101 """) 

102 

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

104 Case pack count for the product, if known. 

105 """) 

106 

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

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

109 as decimal to 4 places. 

110 """) 

111 

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

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

114 """) 

115 

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

117 Arbitrary notes regarding the product, if applicable. 

118 """) 

119 

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

121 Status code for the product record. 

122 """) 

123 

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

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

126 Timestamp when the product record was created. 

127 """) 

128 

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

130 created_by = orm.relationship( 

131 model.User, 

132 cascade_backrefs=False, 

133 doc=""" 

134 Reference to the 

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

136 created the product record. 

137 """) 

138 

139 order_items = orm.relationship( 

140 'OrderItem', 

141 # TODO 

142 # order_by='NewOrderBatchRow.id.desc()', 

143 cascade_backrefs=False, 

144 back_populates='pending_product', 

145 doc=""" 

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

147 associated with this product. 

148 """) 

149 

150 new_order_batch_rows = orm.relationship( 

151 'NewOrderBatchRow', 

152 # TODO 

153 # order_by='NewOrderBatchRow.id.desc()', 

154 cascade_backrefs=False, 

155 back_populates='pending_product', 

156 doc=""" 

157 List of 

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

159 records associated with this product. 

160 """) 

161 

162 @property 

163 def full_description(self): 

164 """ """ 

165 fields = [ 

166 self.brand_name or '', 

167 self.description or '', 

168 self.size or ''] 

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

170 return ' '.join(fields) 

171 

172 def __str__(self): 

173 return self.full_description