Coverage for .tox/coverage/lib/python3.11/site-packages/sideshow/db/model/products.py: 100%
43 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 Products
25"""
27import datetime
29import sqlalchemy as sa
30from sqlalchemy import orm
32from wuttjamaican.db import model
34from sideshow.enum import PendingProductStatus
37class ProductMixin:
38 """
39 Base class for product tables. This has shared columns, used by e.g.:
41 * :class:`LocalProduct`
42 * :class:`PendingProduct`
43 """
45 scancode = sa.Column(sa.String(length=14), nullable=True, doc="""
46 Scancode for the product, as string.
48 .. note::
50 This column allows 14 chars, so can store a full GPC with check
51 digit. However as of writing the actual format used here does
52 not matter to Sideshow logic; "anything" should work.
54 That may change eventually, depending on POS integration
55 scenarios that come up. Maybe a config option to declare
56 whether check digit should be included or not, etc.
57 """)
59 brand_name = sa.Column(sa.String(length=100), nullable=True, doc="""
60 Brand name for the product - up to 100 chars.
61 """)
63 description = sa.Column(sa.String(length=255), nullable=True, doc="""
64 Description for the product - up to 255 chars.
65 """)
67 size = sa.Column(sa.String(length=30), nullable=True, doc="""
68 Size of the product, as string - up to 30 chars.
69 """)
71 weighed = sa.Column(sa.Boolean(), nullable=True, doc="""
72 Flag indicating the product is sold by weight; default is null.
73 """)
75 department_id = sa.Column(sa.String(length=10), nullable=True, doc="""
76 ID of the department to which the product belongs, if known.
77 """)
79 department_name = sa.Column(sa.String(length=30), nullable=True, doc="""
80 Name of the department to which the product belongs, if known.
81 """)
83 special_order = sa.Column(sa.Boolean(), nullable=True, doc="""
84 Flag indicating the item is a "special order" - e.g. something not
85 normally carried by the store. Default is null.
86 """)
88 vendor_name = sa.Column(sa.String(length=50), nullable=True, doc="""
89 Name of vendor from which product may be purchased, if known. See
90 also :attr:`vendor_item_code`.
91 """)
93 vendor_item_code = sa.Column(sa.String(length=20), nullable=True, doc="""
94 Item code (SKU) to use when ordering this product from the vendor
95 identified by :attr:`vendor_name`, if known.
96 """)
98 case_size = sa.Column(sa.Numeric(precision=9, scale=4), nullable=True, doc="""
99 Case pack count for the product, if known.
100 """)
102 unit_cost = sa.Column(sa.Numeric(precision=9, scale=5), nullable=True, doc="""
103 Cost of goods amount for one "unit" (not "case") of the product,
104 as decimal to 4 places.
105 """)
107 unit_price_reg = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
108 Regular price for a "unit" of the product.
109 """)
111 notes = sa.Column(sa.Text(), nullable=True, doc="""
112 Arbitrary notes regarding the product, if applicable.
113 """)
115 @property
116 def full_description(self):
117 """ """
118 fields = [
119 self.brand_name or '',
120 self.description or '',
121 self.size or '']
122 fields = [f.strip() for f in fields if f.strip()]
123 return ' '.join(fields)
125 def __str__(self):
126 return self.full_description
129class LocalProduct(ProductMixin, model.Base):
130 """
131 This table contains the :term:`local product` records.
133 Sideshow will do customer lookups against this table by default,
134 unless it's configured to use :term:`external products <external
135 product>` instead.
137 Also by default, when a :term:`new order batch` with
138 :term:`pending product(s) <pending product>` is executed, new
139 record(s) will be added to this local products table, for lookup
140 next time.
141 """
142 __tablename__ = 'sideshow_product_local'
144 uuid = model.uuid_column()
146 external_id = sa.Column(sa.String(length=20), nullable=True, doc="""
147 ID of the true external product associated with this record, if
148 applicable.
149 """)
151 order_items = orm.relationship(
152 'OrderItem',
153 back_populates='local_product',
154 cascade_backrefs=False,
155 doc="""
156 List of :class:`~sideshow.db.model.orders.OrderItem` records
157 associated with this product.
158 """)
160 new_order_batch_rows = orm.relationship(
161 'NewOrderBatchRow',
162 back_populates='local_product',
163 cascade_backrefs=False,
164 doc="""
165 List of
166 :class:`~sideshow.db.model.batch.neworder.NewOrderBatchRow`
167 records associated with this product.
168 """)
171class PendingProduct(ProductMixin, model.Base):
172 """
173 This table contains the :term:`pending product` records, used when
174 creating an :term:`order` for new/unknown product(s).
176 Sideshow will automatically create and (hopefully) delete these
177 records as needed.
179 By default, when a :term:`new order batch` with pending product(s)
180 is executed, new record(s) will be added to the :term:`local
181 products <local product>` table, for lookup next time.
182 """
183 __tablename__ = 'sideshow_product_pending'
185 uuid = model.uuid_column()
187 product_id = sa.Column(sa.String(length=20), nullable=True, doc="""
188 ID of the true product associated with this record, if applicable.
189 """)
191 status = sa.Column(sa.Enum(PendingProductStatus), nullable=False, doc="""
192 Status code for the product record.
193 """)
195 created = sa.Column(sa.DateTime(timezone=True), nullable=False,
196 default=datetime.datetime.now, doc="""
197 Timestamp when the product record was created.
198 """)
200 created_by_uuid = model.uuid_fk_column('user.uuid', nullable=False)
201 created_by = orm.relationship(
202 model.User,
203 cascade_backrefs=False,
204 doc="""
205 Reference to the
206 :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` who
207 created the product record.
208 """)
210 order_items = orm.relationship(
211 'OrderItem',
212 back_populates='pending_product',
213 cascade_backrefs=False,
214 doc="""
215 List of :class:`~sideshow.db.model.orders.OrderItem` records
216 associated with this product.
217 """)
219 new_order_batch_rows = orm.relationship(
220 'NewOrderBatchRow',
221 back_populates='pending_product',
222 cascade_backrefs=False,
223 doc="""
224 List of
225 :class:`~sideshow.db.model.batch.neworder.NewOrderBatchRow`
226 records associated with this product.
227 """)