Coverage for .tox/coverage/lib/python3.11/site-packages/sideshow/db/model/batch/neworder.py: 100%
74 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 07:16 -0500
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 07:16 -0500
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
26* :class:`NewOrderBatch`
27* :class:`NewOrderBatchRow`
28"""
30import sqlalchemy as sa
31from sqlalchemy import orm
32from sqlalchemy.ext.declarative import declared_attr
34from wuttjamaican.db import model
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`.
43 See also :class:`~sideshow.batch.neworder.NewOrderBatchHandler`
44 which is the default :term:`batch handler` for this :term:`batch
45 type`.
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'
53 batch_type = 'neworder'
54 """
55 Official :term:`batch type` key.
56 """
58 @declared_attr
59 def __table_args__(cls):
60 return cls.__default_table_args__() + (
61 sa.ForeignKeyConstraint(['local_customer_uuid'], ['sideshow_customer_local.uuid']),
62 sa.ForeignKeyConstraint(['pending_customer_uuid'], ['sideshow_customer_pending.uuid']),
63 )
65 STATUS_OK = 1
67 STATUS = {
68 STATUS_OK : "ok",
69 }
71 store_id = sa.Column(sa.String(length=10), nullable=True, doc="""
72 ID of the store to which the order pertains, if applicable.
73 """)
75 customer_id = sa.Column(sa.String(length=20), nullable=True, doc="""
76 Proper account ID for the :term:`external customer` to which the
77 order pertains, if applicable.
79 See also :attr:`local_customer` and :attr:`pending_customer`.
80 """)
82 local_customer_uuid = sa.Column(model.UUID(), nullable=True)
84 @declared_attr
85 def local_customer(cls):
86 return orm.relationship(
87 'LocalCustomer',
88 back_populates='new_order_batches',
89 doc="""
90 Reference to the
91 :class:`~sideshow.db.model.customers.LocalCustomer` record
92 for the order, if applicable.
94 See also :attr:`customer_id` and :attr:`pending_customer`.
95 """)
97 pending_customer_uuid = sa.Column(model.UUID(), nullable=True)
99 @declared_attr
100 def pending_customer(cls):
101 return orm.relationship(
102 'PendingCustomer',
103 back_populates='new_order_batches',
104 doc="""
105 Reference to the
106 :class:`~sideshow.db.model.customers.PendingCustomer`
107 record for the order, if applicable.
109 See also :attr:`customer_id` and :attr:`local_customer`.
110 """)
112 customer_name = sa.Column(sa.String(length=100), nullable=True, doc="""
113 Name for the customer account.
114 """)
116 phone_number = sa.Column(sa.String(length=20), nullable=True, doc="""
117 Phone number for the customer.
118 """)
120 email_address = sa.Column(sa.String(length=255), nullable=True, doc="""
121 Email address for the customer.
122 """)
124 total_price = sa.Column(sa.Numeric(precision=10, scale=3), nullable=True, doc="""
125 Full price (not including tax etc.) for all items on the order.
126 """)
129class NewOrderBatchRow(model.BatchRowMixin, model.Base):
130 """
131 Row of data within a :class:`NewOrderBatch`. Each row ultimately
132 becomes an :class:`~sideshow.db.model.orders.OrderItem`.
134 Generic row attributes (undocumented below) are inherited from
135 :class:`~wuttjamaican:wuttjamaican.db.model.batch.BatchRowMixin`.
136 """
137 __tablename__ = 'sideshow_batch_neworder_row'
138 __batch_class__ = NewOrderBatch
140 @declared_attr
141 def __table_args__(cls):
142 return cls.__default_table_args__() + (
143 sa.ForeignKeyConstraint(['local_product_uuid'], ['sideshow_product_local.uuid']),
144 sa.ForeignKeyConstraint(['pending_product_uuid'], ['sideshow_product_pending.uuid']),
145 )
147 STATUS_OK = 1
148 """
149 This is the default value for :attr:`status_code`. All rows are
150 considered "OK" if they have either a :attr:`product_id` or
151 :attr:`pending_product`.
152 """
154 STATUS_MISSING_PRODUCT = 2
155 """
156 Status code indicating the row has no :attr:`product_id` or
157 :attr:`pending_product` set.
158 """
160 STATUS_MISSING_ORDER_QTY = 3
161 """
162 Status code indicating the row has no :attr:`order_qty` and/or
163 :attr:`order_uom` set.
164 """
166 STATUS = {
167 STATUS_OK : "ok",
168 STATUS_MISSING_PRODUCT : "missing product",
169 STATUS_MISSING_ORDER_QTY : "missing order qty/uom",
170 }
171 """
172 Dict of possible status code -> label options.
173 """
175 product_id = sa.Column(sa.String(length=20), nullable=True, doc="""
176 Proper ID for the :term:`external product` which the order item
177 represents, if applicable.
179 See also :attr:`local_product` and :attr:`pending_product`.
180 """)
182 local_product_uuid = sa.Column(model.UUID(), nullable=True)
184 @declared_attr
185 def local_product(cls):
186 return orm.relationship(
187 'LocalProduct',
188 back_populates='new_order_batch_rows',
189 doc="""
190 Reference to the
191 :class:`~sideshow.db.model.products.LocalProduct` record
192 for the order item, if applicable.
194 See also :attr:`product_id` and :attr:`pending_product`.
195 """)
197 pending_product_uuid = sa.Column(model.UUID(), nullable=True)
199 @declared_attr
200 def pending_product(cls):
201 return orm.relationship(
202 'PendingProduct',
203 back_populates='new_order_batch_rows',
204 doc="""
205 Reference to the
206 :class:`~sideshow.db.model.products.PendingProduct` record
207 for the order item, if applicable.
209 See also :attr:`product_id` and :attr:`local_product`.
210 """)
212 product_scancode = sa.Column(sa.String(length=14), nullable=True, doc="""
213 Scancode for the product, as string.
215 .. note::
217 This column allows 14 chars, so can store a full GPC with check
218 digit. However as of writing the actual format used here does
219 not matter to Sideshow logic; "anything" should work.
221 That may change eventually, depending on POS integration
222 scenarios that come up. Maybe a config option to declare
223 whether check digit should be included or not, etc.
224 """)
226 product_brand = sa.Column(sa.String(length=100), nullable=True, doc="""
227 Brand name for the product - up to 100 chars.
228 """)
230 product_description = sa.Column(sa.String(length=255), nullable=True, doc="""
231 Description for the product - up to 255 chars.
232 """)
234 product_size = sa.Column(sa.String(length=30), nullable=True, doc="""
235 Size of the product, as string - up to 30 chars.
236 """)
238 product_weighed = sa.Column(sa.Boolean(), nullable=True, doc="""
239 Flag indicating the product is sold by weight; default is null.
240 """)
242 department_id = sa.Column(sa.String(length=10), nullable=True, doc="""
243 ID of the department to which the product belongs, if known.
244 """)
246 department_name = sa.Column(sa.String(length=30), nullable=True, doc="""
247 Name of the department to which the product belongs, if known.
248 """)
250 special_order = sa.Column(sa.Boolean(), nullable=True, doc="""
251 Flag indicating the item is a "special order" - e.g. something not
252 normally carried by the store. Default is null.
253 """)
255 vendor_name = sa.Column(sa.String(length=50), nullable=True, doc="""
256 Name of vendor from which product may be purchased, if known. See
257 also :attr:`vendor_item_code`.
258 """)
260 vendor_item_code = sa.Column(sa.String(length=20), nullable=True, doc="""
261 Item code (SKU) to use when ordering this product from the vendor
262 identified by :attr:`vendor_name`, if known.
263 """)
265 case_size = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
266 Case pack count for the product, if known.
268 If this is not set, then customer cannot order a "case" of the item.
269 """)
271 order_qty = sa.Column(sa.Numeric(precision=10, scale=4), nullable=False, doc="""
272 Quantity (as decimal) of product being ordered.
274 This must be interpreted along with :attr:`order_uom` to determine
275 the *complete* order quantity, e.g. "2 cases".
276 """)
278 order_uom = sa.Column(sa.String(length=10), nullable=False, doc="""
279 Code indicating the unit of measure for product being ordered.
281 This should be one of the codes from
282 :data:`~sideshow.enum.ORDER_UOM`.
284 Sideshow will treat :data:`~sideshow.enum.ORDER_UOM_CASE`
285 differently but :data:`~sideshow.enum.ORDER_UOM_UNIT` and others
286 are all treated the same (i.e. "unit" is assumed).
287 """)
289 unit_cost = sa.Column(sa.Numeric(precision=9, scale=5), nullable=True, doc="""
290 Cost of goods amount for one "unit" (not "case") of the product,
291 as decimal to 4 places.
292 """)
294 unit_price_reg = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
295 Regular price for the item unit. Unless a sale is in effect,
296 :attr:`unit_price_quoted` will typically match this value.
297 """)
299 unit_price_sale = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
300 Sale price for the item unit, if applicable. If set, then
301 :attr:`unit_price_quoted` will typically match this value. See
302 also :attr:`sale_ends`.
303 """)
305 sale_ends = sa.Column(sa.DateTime(timezone=True), nullable=True, doc="""
306 End date/time for the sale in effect, if any.
308 This is only relevant if :attr:`unit_price_sale` is set.
309 """)
311 unit_price_quoted = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
312 Quoted price for the item unit. This is the "effective" unit
313 price, which is used to calculate :attr:`total_price`.
315 This price does *not* reflect the :attr:`discount_percent`. It
316 normally should match either :attr:`unit_price_reg` or
317 :attr:`unit_price_sale`.
319 See also :attr:`case_price_quoted`, if applicable.
320 """)
322 case_price_quoted = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
323 Quoted price for a "case" of the item, if applicable.
325 This is mostly for display purposes; :attr:`unit_price_quoted` is
326 used for calculations.
327 """)
329 discount_percent = sa.Column(sa.Numeric(precision=5, scale=3), nullable=True, doc="""
330 Discount percent to apply when calculating :attr:`total_price`, if
331 applicable.
332 """)
334 total_price = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
335 Full price (not including tax etc.) which the customer is quoted
336 for the order item.
338 This is calculated using values from:
340 * :attr:`unit_price_quoted`
341 * :attr:`order_qty`
342 * :attr:`order_uom`
343 * :attr:`case_size`
344 * :attr:`discount_percent`
345 """)
347 def __str__(self):
348 return str(self.pending_product or self.product_description or "")