Coverage for .tox/coverage/lib/python3.11/site-packages/sideshow/db/model/batch/neworder.py: 100%
72 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-09 12:58 -0600
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-09 12:58 -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(['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 case_size = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
256 Case pack count for the product, if known.
258 If this is not set, then customer cannot order a "case" of the item.
259 """)
261 order_qty = sa.Column(sa.Numeric(precision=10, scale=4), nullable=False, doc="""
262 Quantity (as decimal) of product being ordered.
264 This must be interpreted along with :attr:`order_uom` to determine
265 the *complete* order quantity, e.g. "2 cases".
266 """)
268 order_uom = sa.Column(sa.String(length=10), nullable=False, doc="""
269 Code indicating the unit of measure for product being ordered.
271 This should be one of the codes from
272 :data:`~sideshow.enum.ORDER_UOM`.
274 Sideshow will treat :data:`~sideshow.enum.ORDER_UOM_CASE`
275 differently but :data:`~sideshow.enum.ORDER_UOM_UNIT` and others
276 are all treated the same (i.e. "unit" is assumed).
277 """)
279 unit_cost = sa.Column(sa.Numeric(precision=9, scale=5), nullable=True, doc="""
280 Cost of goods amount for one "unit" (not "case") of the product,
281 as decimal to 4 places.
282 """)
284 unit_price_reg = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
285 Regular price for the item unit. Unless a sale is in effect,
286 :attr:`unit_price_quoted` will typically match this value.
287 """)
289 unit_price_sale = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
290 Sale price for the item unit, if applicable. If set, then
291 :attr:`unit_price_quoted` will typically match this value. See
292 also :attr:`sale_ends`.
293 """)
295 sale_ends = sa.Column(sa.DateTime(timezone=True), nullable=True, doc="""
296 End date/time for the sale in effect, if any.
298 This is only relevant if :attr:`unit_price_sale` is set.
299 """)
301 unit_price_quoted = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
302 Quoted price for the item unit. This is the "effective" unit
303 price, which is used to calculate :attr:`total_price`.
305 This price does *not* reflect the :attr:`discount_percent`. It
306 normally should match either :attr:`unit_price_reg` or
307 :attr:`unit_price_sale`.
309 See also :attr:`case_price_quoted`, if applicable.
310 """)
312 case_price_quoted = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
313 Quoted price for a "case" of the item, if applicable.
315 This is mostly for display purposes; :attr:`unit_price_quoted` is
316 used for calculations.
317 """)
319 discount_percent = sa.Column(sa.Numeric(precision=5, scale=3), nullable=True, doc="""
320 Discount percent to apply when calculating :attr:`total_price`, if
321 applicable.
322 """)
324 total_price = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
325 Full price (not including tax etc.) which the customer is quoted
326 for the order item.
328 This is calculated using values from:
330 * :attr:`unit_price_quoted`
331 * :attr:`order_qty`
332 * :attr:`order_uom`
333 * :attr:`case_size`
334 * :attr:`discount_percent`
335 """)
337 def __str__(self):
338 return str(self.pending_product or self.product_description or "")