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
« 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"""
27import decimal
29import sqlalchemy as sa
30from sqlalchemy import orm
32from sideshow.batch import neworder as base
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.
41 See parent class
42 :class:`~sideshow:sideshow.batch.neworder.NewOrderBatchHandler`
43 for more info.
44 """
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()
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)
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))
65 # sort query
66 query = query.order_by(op_model.CustomerClassic.first_name,
67 op_model.CustomerClassic.last_name)
69 # get data
70 # TODO: need max_results option
71 customers = query.all()
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]
79 op_session.close()
80 return results
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()
88 if not batch.customer_id.isdigit():
89 raise ValueError(f"invalid CORE-POS customer card number: {batch.customer_id}")
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}")
102 batch.customer_name = str(customer)
103 batch.phone_number = customer.member_info.phone
104 batch.email_address = customer.member_info.email
106 op_session.close()
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()
114 # base query
115 query = op_session.query(op_model.Product)
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))
125 # sort query
126 query = query.order_by(op_model.Product.brand,
127 op_model.Product.description)
129 # get data
130 # TODO: need max_results option
131 products = query.all()
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]
141 op_session.close()
142 return results
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()
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}")
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 }
177 op_session.close()
178 return data
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()
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}")
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
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
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)
213 op_session.close()
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}')
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}')