source: rattail/rattail/db/model/batch/purchase.py @ 14800ef

Last change on this file since 14800ef was 14800ef, checked in by Lance Edgar <lance@…>, 20 months ago

Add item_entry field to all product-related batch rows

this is meant to capture the initial value from source data, which is then to
be used for product lookup / identification. useful for doing subsequent
lookups, i.e. during batch refresh, after product master is updated

  • Property mode set to 100644
File size: 12.9 KB
Line 
1# -*- coding: utf-8; -*-
2################################################################################
3#
4#  Rattail -- Retail Software Framework
5#  Copyright © 2010-2018 Lance Edgar
6#
7#  This file is part of Rattail.
8#
9#  Rattail is free software: you can redistribute it and/or modify it under the
10#  terms of the GNU General Public License as published by the Free Software
11#  Foundation, either version 3 of the License, or (at your option) any later
12#  version.
13#
14#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
15#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17#  details.
18#
19#  You should have received a copy of the GNU General Public License along with
20#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Models for purchase order batches
25"""
26
27from __future__ import unicode_literals, absolute_import
28
29import six
30import sqlalchemy as sa
31from sqlalchemy import orm
32from sqlalchemy.ext.declarative import declared_attr
33
34from rattail.db.model import (Base, uuid_column, BatchMixin, BatchRowMixin,
35                              PurchaseBase, PurchaseItemBase, PurchaseCreditBase,
36                              Purchase, PurchaseItem)
37from rattail.db.model.batch import filename_column
38from rattail.util import pretty_quantity
39
40
41class PurchaseBatch(BatchMixin, PurchaseBase, Base):
42    """
43    Hopefully generic batch used for entering new purchases into the system, etc.?
44    """
45    batch_key = 'purchase'
46    __tablename__ = 'purchase_batch'
47    __batchrow_class__ = 'PurchaseBatchRow'
48    model_title = "Purchasing Batch"
49    model_title_plural = "Purchasing Batches"
50
51    @declared_attr
52    def __table_args__(cls):
53        return cls.__batch_table_args__() + cls.__purchase_table_args__() + (
54            sa.ForeignKeyConstraint(['purchase_uuid'], ['purchase.uuid'], name='purchase_batch_fk_purchase'),
55            sa.ForeignKeyConstraint(['truck_dump_batch_uuid'], ['purchase_batch.uuid'], name='purchase_batch_fk_truck_dump_batch', use_alter=True),
56        )
57
58    STATUS_OK                   = 1
59    STATUS_UNKNOWN_PRODUCT      = 2
60    STATUS_TRUCKDUMP_UNCLAIMED  = 3
61    STATUS_TRUCKDUMP_CLAIMED    = 4
62    STATUS_UNKNOWN_COSTS        = 5
63
64    STATUS = {
65        STATUS_OK                       : "ok",
66        STATUS_UNKNOWN_PRODUCT          : "has unknown product(s)",
67        STATUS_UNKNOWN_COSTS            : "has unknown product cost(s)",
68        STATUS_TRUCKDUMP_UNCLAIMED      : "not yet fully claimed",
69        STATUS_TRUCKDUMP_CLAIMED        : "fully claimed by child(ren)",
70    }
71
72    purchase_uuid = sa.Column(sa.String(length=32), nullable=True)
73
74    purchase = orm.relationship(
75        Purchase,
76        doc="""
77        Reference to the purchase with which the batch is associated.  May be
78        null, e.g. in the case of a "new purchase" batch.
79        """,
80        backref=orm.backref(
81            'batches',
82            order_by='PurchaseBatch.id',
83            doc="""
84            List of batches associated with the purchase.
85            """))
86
87    mode = sa.Column(sa.Integer(), nullable=False, doc="""
88    Numeric "mode" for the purchase batch, to indicate new/receiving etc.
89    """)
90
91    invoice_file = filename_column(doc="Base name for the associated invoice file, if any.")
92
93    invoice_parser_key = sa.Column(sa.String(length=100), nullable=True, doc="""
94    The key of the parser used to read the contents of the invoice file.
95    """)
96
97    order_quantities_known = sa.Column(sa.Boolean(), nullable=True, doc="""
98    Flag indicating whether the order quantities were known at time of batch
99    creation / population.  Really this is only used for batches of 'receiving'
100    mode, to present a slightly different UI if order quantities were (not) known.
101    """)
102
103    truck_dump = sa.Column(sa.Boolean(), nullable=True, default=False, doc="""
104    Flag indicating whether a "receiving" batch is of the "truck dump"
105    persuasion, i.e.  it does not correspond to a single purchase order but
106    rather is assumed to represent multiple orders.
107    """)
108
109    truck_dump_batch_uuid = sa.Column(sa.String(length=32), nullable=True)
110    truck_dump_batch = orm.relationship(
111        'PurchaseBatch',
112        remote_side='PurchaseBatch.uuid',
113        doc="""
114        Reference to the "truck dump" receiving batch, for which the current
115        batch represents a single invoice which partially "consumes" the truck
116        dump.
117        """,
118        backref=orm.backref(
119            'truck_dump_children',
120            order_by='PurchaseBatch.id',
121            doc="""
122            List of batches which are "children" of the current batch, which is
123            assumed to be a truck dump.
124            """))
125
126    def is_truck_dump_parent(self):
127        """
128        Returns boolean indicating whether or not the batch is a "truck dump"
129        parent.
130        """
131        if self.truck_dump:
132            return True
133        return False
134
135    def is_truck_dump_child(self):
136        """
137        Returns boolean indicating whether or not the batch is a "truck dump"
138        child.
139        """
140        if self.truck_dump_batch:
141            return True
142        return False
143
144    def is_truck_dump_related(self):
145        """
146        Returns boolean indicating whether or not the batch is associated with
147        a "truck dump" in any way, i.e. is a parent or child of such.
148        """
149        if self.is_truck_dump_parent():
150            return True
151        if self.is_truck_dump_child():
152            return True
153        return False
154
155
156class PurchaseBatchRow(BatchRowMixin, PurchaseItemBase, Base):
157    """
158    Row of data within a purchase batch.
159    """
160    __tablename__ = 'purchase_batch_row'
161    __batch_class__ = PurchaseBatch
162
163    @declared_attr
164    def __table_args__(cls):
165        return cls.__batchrow_table_args__() + cls.__purchaseitem_table_args__() + (
166            sa.ForeignKeyConstraint(['item_uuid'], ['purchase_item.uuid'], name='purchase_batch_row_fk_item'),
167        )
168
169    STATUS_OK                           = 1
170    STATUS_PRODUCT_NOT_FOUND            = 2
171    STATUS_COST_NOT_FOUND               = 3
172    STATUS_CASE_QUANTITY_UNKNOWN        = 4
173    STATUS_INCOMPLETE                   = 5
174    STATUS_ORDERED_RECEIVED_DIFFER      = 6
175    STATUS_TRUCKDUMP_UNCLAIMED          = 7
176    STATUS_TRUCKDUMP_PARTCLAIMED        = 11
177    STATUS_TRUCKDUMP_CLAIMED            = 8
178    STATUS_TRUCKDUMP_OVERCLAIMED        = 9
179    STATUS_CASE_QUANTITY_DIFFERS        = 10
180
181    STATUS = {
182        STATUS_OK                       : "ok",
183        STATUS_PRODUCT_NOT_FOUND        : "product not found",
184        STATUS_COST_NOT_FOUND           : "product found but not cost",
185        STATUS_CASE_QUANTITY_UNKNOWN    : "case quantity not known",
186        STATUS_CASE_QUANTITY_DIFFERS    : "case quantity differs",
187        STATUS_INCOMPLETE               : "incomplete",
188        STATUS_ORDERED_RECEIVED_DIFFER  : "ordered / received differ",
189        STATUS_TRUCKDUMP_UNCLAIMED      : "not claimed by any child(ren)",
190        STATUS_TRUCKDUMP_PARTCLAIMED    : "partially claimed by child(ren)",
191        STATUS_TRUCKDUMP_CLAIMED        : "fully claimed by child(ren)",
192        STATUS_TRUCKDUMP_OVERCLAIMED    : "OVER claimed by child(ren)",
193    }
194
195    item_entry = sa.Column(sa.String(length=20), nullable=True, doc="""
196    Raw entry value, as obtained from the initial data source, and which is
197    used to locate the product within the system.  This raw value is preserved
198    in case the initial lookup fails and a refresh must attempt further
199    lookup(s) later.  Only used by certain batch handlers in practice.
200    """)
201
202    item_uuid = sa.Column(sa.String(length=32), nullable=True)
203
204    item = orm.relationship(
205        PurchaseItem,
206        doc="""
207        Reference to the purchase item with which the batch row is associated.
208        May be null, e.g. in the case of a "new purchase" batch.
209        """)
210
211
212class PurchaseBatchRowClaim(Base):
213    """
214    Represents the connection between a row(s) from a truck dump batch, and the
215    corresponding "child" batch row which claims it, as well as the claimed
216    quantities etc.
217    """
218    __tablename__ = 'purchase_batch_row_claim'
219    __table_args__ = (
220        sa.ForeignKeyConstraint(['claiming_row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_row_claim_fk_claiming_row'),
221        sa.ForeignKeyConstraint(['claimed_row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_row_claim_fk_claimed_row'),
222    )
223
224    uuid = uuid_column()
225
226    claiming_row_uuid = sa.Column(sa.String(length=32), nullable=False)
227    claiming_row = orm.relationship(
228        PurchaseBatchRow,
229        foreign_keys='PurchaseBatchRowClaim.claiming_row_uuid',
230        doc="""
231        Reference to the "child" row which is claiming some row from a truck
232        dump batch.
233        """,
234        backref=orm.backref(
235            'truck_dump_claims',
236            cascade='all, delete-orphan',
237            doc="""
238            List of claims which this "child" row makes against rows within a
239            truck dump batch.
240            """))
241
242    claimed_row_uuid = sa.Column(sa.String(length=32), nullable=False)
243    claimed_row = orm.relationship(
244        PurchaseBatchRow,
245        foreign_keys='PurchaseBatchRowClaim.claimed_row_uuid',
246        doc="""
247        Reference to the truck dump batch row which is claimed by the "child" row.
248        """,
249        backref=orm.backref(
250            'claims',
251            cascade='all, delete-orphan',
252            doc="""
253            List of claims made by "child" rows against this truck dump batch row.
254            """))
255
256    cases_received = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
257    Number of cases of product which were ultimately received, and are involved in the claim.
258    """)
259
260    units_received = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
261    Number of units of product which were ultimately received, and are involved in the claim.
262    """)
263
264    cases_damaged = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
265    Number of cases of product which were shipped damaged, and are involved in the claim.
266    """)
267
268    units_damaged = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
269    Number of units of product which were shipped damaged, and are involved in the claim.
270    """)
271
272    cases_expired = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
273    Number of cases of product which were shipped expired, and are involved in the claim.
274    """)
275
276    units_expired = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
277    Number of units of product which were shipped expired, and are involved in the claim.
278    """)
279
280    def is_empty(self):
281        """
282        Returns boolean indicating whether the claim is "empty" - i.e. if it
283        has zero or null for all of its quantity fields.
284        """
285        if self.cases_received:
286            return False
287        if self.units_received:
288            return False
289
290        if self.cases_damaged:
291            return False
292        if self.units_damaged:
293            return False
294
295        if self.cases_expired:
296            return False
297        if self.units_expired:
298            return False
299
300        return True
301
302
303@six.python_2_unicode_compatible
304class PurchaseBatchCredit(PurchaseCreditBase, Base):
305    """
306    Represents a working copy of purchase credit tied to a batch row.
307    """
308    __tablename__ = 'purchase_batch_credit'
309
310    @declared_attr
311    def __table_args__(cls):
312        return cls.__purchasecredit_table_args__() + (
313            sa.ForeignKeyConstraint(['row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_credit_fk_row'),
314        )
315
316    uuid = uuid_column()
317
318    row_uuid = sa.Column(sa.String(length=32), nullable=True)
319
320    row = orm.relationship(
321        PurchaseBatchRow,
322        doc="""
323        Reference to the batch row with which the credit is associated.
324        """,
325        backref=orm.backref(
326            'credits',
327            doc="""
328            List of :class:`PurchaseBatchCredit` instances for the row.
329            """))
330
331    def __str__(self):
332        if self.cases_shorted is not None and self.units_shorted is not None:
333            qty = "{} cases, {} units".format(
334                pretty_quantity(self.cases_shorted),
335                pretty_quantity(self.units_shorted))
336        elif self.cases_shorted is not None:
337            qty = "{} cases".format(pretty_quantity(self.cases_shorted))
338        elif self.units_shorted is not None:
339            qty = "{} units".format(pretty_quantity(self.units_shorted))
340        else:
341            qty = "??"
342        qty += " {}".format(self.credit_type)
343        if self.credit_type == 'expired' and self.expiration_date:
344            qty += " ({})".format(self.expiration_date)
345        if self.credit_type == 'mispick' and self.mispick_product:
346            qty += " ({})".format(self.mispick_product)
347        if self.invoice_total:
348            return "{} = ${:0.2f}".format(qty, self.invoice_total)
349        return qty
Note: See TracBrowser for help on using the repository browser.