Coverage for src/sideshow_corepos/batch/neworder.py: 100%

96 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-20 09:01 -0600

1# -*- coding: utf-8; -*- 

2################################################################################ 

3# 

4# Sideshow-COREPOS -- Case/Special Order Tracker for CORE-POS 

5# Copyright © 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""" 

24New Order Batch Handler for CORE-POS 

25""" 

26 

27import decimal 

28 

29import sqlalchemy as sa 

30from sqlalchemy import orm 

31 

32from sideshow.batch import neworder as base 

33 

34 

35class NewOrderBatchHandler(base.NewOrderBatchHandler): 

36 """ 

37 Custom :term:`handler` for :term:`new order batches <new order 

38 batch>` which can use CORE-POS as external data source for 

39 customers and products. 

40 

41 See parent class 

42 :class:`~sideshow:sideshow.batch.neworder.NewOrderBatchHandler` 

43 for more info. 

44 """ 

45 

46 def autocomplete_customers_external(self, session, term, user=None): 

47 """ """ 

48 corepos = self.app.get_corepos_handler() 

49 op_model = corepos.get_model_office_op() 

50 op_session = corepos.make_session_office_op() 

51 

52 # base query 

53 query = op_session.query(op_model.CustomerClassic)\ 

54 .join(op_model.MemberInfo, 

55 op_model.MemberInfo.card_number == op_model.CustomerClassic.card_number) 

56 

57 # filter query 

58 criteria = [] 

59 for word in term.split(): 

60 criteria.append(sa.or_( 

61 op_model.CustomerClassic.first_name.ilike(f'%{word}%'), 

62 op_model.CustomerClassic.last_name.ilike(f'%{word}%'))) 

63 query = query.filter(sa.and_(*criteria)) 

64 

65 # sort query 

66 query = query.order_by(op_model.CustomerClassic.first_name, 

67 op_model.CustomerClassic.last_name) 

68 

69 # get data 

70 # TODO: need max_results option 

71 customers = query.all() 

72 

73 # get results 

74 def result(customer): 

75 return {'value': str(customer.card_number), 

76 'label': str(customer)} 

77 results = [result(c) for c in customers] 

78 

79 op_session.close() 

80 return results 

81 

82 def refresh_batch_from_external_customer(self, batch): 

83 """ """ 

84 corepos = self.app.get_corepos_handler() 

85 op_model = corepos.get_model_office_op() 

86 op_session = corepos.make_session_office_op() 

87 

88 if not batch.customer_id.isdigit(): 

89 raise ValueError(f"invalid CORE-POS customer card number: {batch.customer_id}") 

90 

91 try: 

92 customer = op_session.query(op_model.CustomerClassic)\ 

93 .join(op_model.MemberInfo, 

94 op_model.MemberInfo.card_number == op_model.CustomerClassic.card_number)\ 

95 .filter(op_model.CustomerClassic.card_number == int(batch.customer_id))\ 

96 .filter(op_model.CustomerClassic.person_number == 1)\ 

97 .options(orm.joinedload(op_model.CustomerClassic.member_info))\ 

98 .one() 

99 except orm.exc.NoResultFound: 

100 raise ValueError(f"CORE-POS Customer not found: {batch.customer_id}") 

101 

102 batch.customer_name = str(customer) 

103 batch.phone_number = customer.member_info.phone 

104 batch.email_address = customer.member_info.email 

105 

106 op_session.close() 

107 

108 def autocomplete_products_external(self, session, term, user=None): 

109 """ """ 

110 corepos = self.app.get_corepos_handler() 

111 op_model = corepos.get_model_office_op() 

112 op_session = corepos.make_session_office_op() 

113 

114 # base query 

115 query = op_session.query(op_model.Product) 

116 

117 # filter query 

118 criteria = [] 

119 for word in term.split(): 

120 criteria.append(sa.or_( 

121 op_model.Product.brand.ilike(f'%{word}%'), 

122 op_model.Product.description.ilike(f'%{word}%'))) 

123 query = query.filter(sa.and_(*criteria)) 

124 

125 # sort query 

126 query = query.order_by(op_model.Product.brand, 

127 op_model.Product.description) 

128 

129 # get data 

130 # TODO: need max_results option 

131 products = query.all() 

132 

133 # get results 

134 def result(product): 

135 return {'value': product.upc, 

136 'label': self.app.make_full_name(product.brand, 

137 product.description, 

138 product.size)} 

139 results = [result(c) for c in products] 

140 

141 op_session.close() 

142 return results 

143 

144 def get_product_info_external(self, session, product_id, user=None): 

145 """ """ 

146 corepos = self.app.get_corepos_handler() 

147 op_model = corepos.get_model_office_op() 

148 op_session = corepos.make_session_office_op() 

149 

150 try: 

151 product = op_session.query(op_model.Product)\ 

152 .filter(op_model.Product.upc == product_id)\ 

153 .one() 

154 except orm.exc.NoResultFound: 

155 raise ValueError(f"CORE-POS Product not found: {product_id}") 

156 

157 data = { 

158 'product_id': product.upc, 

159 'scancode': product.upc, 

160 'brand_name': product.brand, 

161 'description': product.description, 

162 'size': product.size, 

163 'full_description': self.app.make_full_name(product.brand, 

164 product.description, 

165 product.size), 

166 'weighed': product.scale, 

167 'special_order': False, 

168 'department_id': product.department_number, 

169 'department_name': product.department.name if product.department else None, 

170 'case_size': self.get_case_size_for_external_product(product), 

171 'unit_price_reg': self.get_unit_price_reg_for_external_product(product), 

172 # TODO 

173 # 'vendor_name': product.vendor_name, 

174 # 'vendor_item_code': product.vendor_item_code, 

175 } 

176 

177 op_session.close() 

178 return data 

179 

180 def refresh_row_from_external_product(self, row): 

181 """ """ 

182 corepos = self.app.get_corepos_handler() 

183 op_model = corepos.get_model_office_op() 

184 op_session = corepos.make_session_office_op() 

185 

186 try: 

187 product = op_session.query(op_model.Product)\ 

188 .filter(op_model.Product.upc == row.product_id)\ 

189 .one() 

190 except orm.exc.NoResultFound: 

191 raise ValueError(f"CORE-POS Product not found: {row.product_id}") 

192 

193 row.product_scancode = product.upc 

194 row.product_brand = product.brand 

195 row.product_description = product.description 

196 row.product_size = product.size 

197 row.product_weighed = product.scale 

198 row.department_id = product.department_number 

199 row.department_name = product.department.name if product.department else None 

200 row.special_order = False 

201 

202 row.vendor_name = None 

203 row.vendor_item_code = None 

204 item = product.default_vendor_item 

205 if item: 

206 row.vendor_name = item.vendor.name if item.vendor else None 

207 row.vendor_item_code = item.sku 

208 

209 row.case_size = self.get_case_size_for_external_product(product) 

210 row.unit_cost = product.cost 

211 row.unit_price_reg = self.get_unit_price_reg_for_external_product(product) 

212 

213 op_session.close() 

214 

215 def get_case_size_for_external_product(self, product): 

216 """ """ 

217 if product.vendor_items: 

218 item = product.vendor_items[0] 

219 if item.units is not None: 

220 return decimal.Decimal(f'{item.units:0.4f}') 

221 

222 def get_unit_price_reg_for_external_product(self, product): 

223 """ """ 

224 if product.normal_price is not None: 

225 return decimal.Decimal(f'{product.normal_price:0.3f}')