Coverage for src/sideshow/db/model/batch/neworder.py: 100%
64 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-06 15:04 -0600
« 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 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(['pending_customer_uuid'], ['sideshow_pending_customer.uuid']),
62 )
64 STATUS_OK = 1
66 STATUS = {
67 STATUS_OK : "ok",
68 }
70 store_id = sa.Column(sa.String(length=10), nullable=True, doc="""
71 ID of the store to which the order pertains, if applicable.
72 """)
74 customer_id = sa.Column(sa.String(length=20), nullable=True, doc="""
75 ID of the proper customer account to which the order pertains, if
76 applicable.
78 This will be set only when an "existing" customer account can be
79 selected for the order. See also :attr:`pending_customer`.
80 """)
82 pending_customer_uuid = sa.Column(model.UUID(), nullable=True)
84 @declared_attr
85 def pending_customer(cls):
86 return orm.relationship(
87 'PendingCustomer',
88 back_populates='new_order_batches',
89 doc="""
90 Reference to the
91 :class:`~sideshow.db.model.customers.PendingCustomer`
92 record for the order, if applicable.
94 This is set only when making an order for a "new /
95 unknown" customer. See also :attr:`customer_id`.
96 """)
98 customer_name = sa.Column(sa.String(length=100), nullable=True, doc="""
99 Name for the customer account.
100 """)
102 phone_number = sa.Column(sa.String(length=20), nullable=True, doc="""
103 Phone number for the customer.
104 """)
106 email_address = sa.Column(sa.String(length=255), nullable=True, doc="""
107 Email address for the customer.
108 """)
110 total_price = sa.Column(sa.Numeric(precision=10, scale=3), nullable=True, doc="""
111 Full price (not including tax etc.) for all items on the order.
112 """)
115class NewOrderBatchRow(model.BatchRowMixin, model.Base):
116 """
117 Row of data within a :class:`NewOrderBatch`. Each row ultimately
118 becomes an :class:`~sideshow.db.model.orders.OrderItem`.
120 Generic row attributes (undocumented below) are inherited from
121 :class:`~wuttjamaican:wuttjamaican.db.model.batch.BatchRowMixin`.
122 """
123 __tablename__ = 'sideshow_batch_neworder_row'
124 __batch_class__ = NewOrderBatch
126 @declared_attr
127 def __table_args__(cls):
128 return cls.__default_table_args__() + (
129 sa.ForeignKeyConstraint(['pending_product_uuid'], ['sideshow_pending_product.uuid']),
130 )
132 STATUS_OK = 1
133 """
134 This is the default value for :attr:`status_code`. All rows are
135 considered "OK" if they have either a :attr:`product_id` or
136 :attr:`pending_product`.
137 """
139 STATUS_MISSING_PRODUCT = 2
140 """
141 Status code indicating the row has no :attr:`product_id` or
142 :attr:`pending_product` set.
143 """
145 STATUS_MISSING_ORDER_QTY = 3
146 """
147 Status code indicating the row has no :attr:`order_qty` and/or
148 :attr:`order_uom` set.
149 """
151 STATUS = {
152 STATUS_OK : "ok",
153 STATUS_MISSING_PRODUCT : "missing product",
154 STATUS_MISSING_ORDER_QTY : "missing order qty/uom",
155 }
156 """
157 Dict of possible status code -> label options.
158 """
160 product_id = sa.Column(sa.String(length=20), nullable=True, doc="""
161 ID of the true product which the order item represents, if
162 applicable.
164 This will be set only when an "existing" product can be selected
165 for the order. See also :attr:`pending_product`.
166 """)
168 pending_product_uuid = sa.Column(model.UUID(), nullable=True)
170 @declared_attr
171 def pending_product(cls):
172 return orm.relationship(
173 'PendingProduct',
174 back_populates='new_order_batch_rows',
175 doc="""
176 Reference to the
177 :class:`~sideshow.db.model.products.PendingProduct` record
178 for the order item, if applicable.
180 This is set only when making an order for a "new /
181 unknown" product. See also :attr:`product_id`.
182 """)
184 product_scancode = sa.Column(sa.String(length=14), nullable=True, doc="""
185 Scancode for the product, as string.
187 .. note::
189 This column allows 14 chars, so can store a full GPC with check
190 digit. However as of writing the actual format used here does
191 not matter to Sideshow logic; "anything" should work.
193 That may change eventually, depending on POS integration
194 scenarios that come up. Maybe a config option to declare
195 whether check digit should be included or not, etc.
196 """)
198 product_brand = sa.Column(sa.String(length=100), nullable=True, doc="""
199 Brand name for the product - up to 100 chars.
200 """)
202 product_description = sa.Column(sa.String(length=255), nullable=True, doc="""
203 Description for the product - up to 255 chars.
204 """)
206 product_size = sa.Column(sa.String(length=30), nullable=True, doc="""
207 Size of the product, as string - up to 30 chars.
208 """)
210 product_weighed = sa.Column(sa.Boolean(), nullable=True, doc="""
211 Flag indicating the product is sold by weight; default is null.
212 """)
214 department_id = sa.Column(sa.String(length=10), nullable=True, doc="""
215 ID of the department to which the product belongs, if known.
216 """)
218 department_name = sa.Column(sa.String(length=30), nullable=True, doc="""
219 Name of the department to which the product belongs, if known.
220 """)
222 special_order = sa.Column(sa.Boolean(), nullable=True, doc="""
223 Flag indicating the item is a "special order" - e.g. something not
224 normally carried by the store. Default is null.
225 """)
227 case_size = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
228 Case pack count for the product, if known.
230 If this is not set, then customer cannot order a "case" of the item.
231 """)
233 order_qty = sa.Column(sa.Numeric(precision=10, scale=4), nullable=False, doc="""
234 Quantity (as decimal) of product being ordered.
236 This must be interpreted along with :attr:`order_uom` to determine
237 the *complete* order quantity, e.g. "2 cases".
238 """)
240 order_uom = sa.Column(sa.String(length=10), nullable=False, doc="""
241 Code indicating the unit of measure for product being ordered.
243 This should be one of the codes from
244 :data:`~sideshow.enum.ORDER_UOM`.
246 Sideshow will treat :data:`~sideshow.enum.ORDER_UOM_CASE`
247 differently but :data:`~sideshow.enum.ORDER_UOM_UNIT` and others
248 are all treated the same (i.e. "unit" is assumed).
249 """)
251 unit_cost = sa.Column(sa.Numeric(precision=9, scale=5), nullable=True, doc="""
252 Cost of goods amount for one "unit" (not "case") of the product,
253 as decimal to 4 places.
254 """)
256 unit_price_reg = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
257 Regular price for the item unit. Unless a sale is in effect,
258 :attr:`unit_price_quoted` will typically match this value.
259 """)
261 unit_price_sale = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
262 Sale price for the item unit, if applicable. If set, then
263 :attr:`unit_price_quoted` will typically match this value. See
264 also :attr:`sale_ends`.
265 """)
267 sale_ends = sa.Column(sa.DateTime(timezone=True), nullable=True, doc="""
268 End date/time for the sale in effect, if any.
270 This is only relevant if :attr:`unit_price_sale` is set.
271 """)
273 unit_price_quoted = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
274 Quoted price for the item unit. This is the "effective" unit
275 price, which is used to calculate :attr:`total_price`.
277 This price does *not* reflect the :attr:`discount_percent`. It
278 normally should match either :attr:`unit_price_reg` or
279 :attr:`unit_price_sale`.
281 See also :attr:`case_price_quoted`, if applicable.
282 """)
284 case_price_quoted = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
285 Quoted price for a "case" of the item, if applicable.
287 This is mostly for display purposes; :attr:`unit_price_quoted` is
288 used for calculations.
289 """)
291 discount_percent = sa.Column(sa.Numeric(precision=5, scale=3), nullable=True, doc="""
292 Discount percent to apply when calculating :attr:`total_price`, if
293 applicable.
294 """)
296 total_price = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
297 Full price (not including tax etc.) which the customer is quoted
298 for the order item.
300 This is calculated using values from:
302 * :attr:`unit_price_quoted`
303 * :attr:`order_qty`
304 * :attr:`order_uom`
305 * :attr:`case_size`
306 * :attr:`discount_percent`
307 """)
309 def __str__(self):
310 return str(self.pending_product or self.product_description or "")