source: rattail/rattail/db/model/batch/purchase.py

v0.9.126
Last change on this file was aebc82f, checked in by Lance Edgar <lance@…>, 5 weeks ago

Grow item_entry field for batches, to accommodate product_uuid

  • Property mode set to 100644
File size: 14.8 KB
Line 
1# -*- coding: utf-8; -*-
2################################################################################
3#
4#  Rattail -- Retail Software Framework
5#  Copyright © 2010-2020 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, BatchMixin, BatchRowMixin,
35                              PurchaseBase, PurchaseItemBase, PurchaseCreditBase,
36                              Purchase, PurchaseItem)
37from rattail.db.core import uuid_column, filename_column
38
39
40class PurchaseBatch(BatchMixin, PurchaseBase, Base):
41    """
42    Hopefully generic batch used for entering new purchases into the system, etc.?
43    """
44    batch_key = 'purchase'
45    __tablename__ = 'purchase_batch'
46    __batchrow_class__ = 'PurchaseBatchRow'
47    model_title = "Purchasing Batch"
48    model_title_plural = "Purchasing Batches"
49
50    @declared_attr
51    def __table_args__(cls):
52        return cls.__batch_table_args__() + cls.__purchase_table_args__() + (
53            sa.ForeignKeyConstraint(['purchase_uuid'], ['purchase.uuid'], name='purchase_batch_fk_purchase'),
54            sa.ForeignKeyConstraint(['truck_dump_batch_uuid'], ['purchase_batch.uuid'], name='purchase_batch_fk_truck_dump_batch', use_alter=True),
55        )
56
57    STATUS_OK                   = 1
58    STATUS_UNKNOWN_PRODUCT      = 2
59    STATUS_TRUCKDUMP_UNCLAIMED  = 3
60    STATUS_TRUCKDUMP_CLAIMED    = 4
61    STATUS_UNKNOWN_COSTS        = 5
62
63    STATUS = {
64        STATUS_OK                       : "ok",
65        STATUS_UNKNOWN_PRODUCT          : "has unknown product(s)",
66        STATUS_UNKNOWN_COSTS            : "has unknown product cost(s)",
67        STATUS_TRUCKDUMP_UNCLAIMED      : "not yet fully claimed",
68        STATUS_TRUCKDUMP_CLAIMED        : "fully claimed by child(ren)",
69    }
70
71    purchase_uuid = sa.Column(sa.String(length=32), nullable=True)
72
73    purchase = orm.relationship(
74        Purchase,
75        doc="""
76        Reference to the purchase with which the batch is associated.  May be
77        null, e.g. in the case of a "new purchase" batch.
78        """,
79        backref=orm.backref(
80            'batches',
81            order_by='PurchaseBatch.id',
82            doc="""
83            List of batches associated with the purchase.
84            """))
85
86    mode = sa.Column(sa.Integer(), nullable=False, doc="""
87    Numeric "mode" for the purchase batch, to indicate new/receiving etc.
88    """)
89
90    invoice_file = filename_column(doc="Base name for the associated invoice file, if any.")
91
92    invoice_parser_key = sa.Column(sa.String(length=100), nullable=True, doc="""
93    The key of the parser used to read the contents of the invoice file.
94    """)
95
96    order_quantities_known = sa.Column(sa.Boolean(), nullable=True, doc="""
97    Flag indicating whether the order quantities were known at time of batch
98    creation / population.  Really this is only used for batches of 'receiving'
99    mode, to present a slightly different UI if order quantities were (not) known.
100    """)
101
102    po_total_calculated = sa.Column(sa.Numeric(precision=8, scale=2), nullable=True, doc="""
103    Calculated total for the purchase, per quantity ordered thus far.  See also
104    :attr:`po_total`.
105    """)
106
107    invoice_total_calculated = sa.Column(sa.Numeric(precision=8, scale=2), nullable=True, doc="""
108    Calculated total for the invoice, per quantity received thus far.  See also
109    :attr:`invoice_total`.
110    """)
111
112    truck_dump = sa.Column(sa.Boolean(), nullable=True, default=False, doc="""
113    Flag indicating whether a "receiving" batch is of the "truck dump"
114    persuasion, i.e.  it does not correspond to a single purchase order but
115    rather is assumed to represent multiple orders.
116    """)
117
118    truck_dump_children_first = sa.Column(sa.Boolean(), nullable=True, default=False, doc="""
119    If batch is a "truck dump parent", this flag indicates whether its
120    "children" are to be attached *first* as opposed to *last*.  If the flag is
121    true, all children must be attached prior to the receiving process.  If
122    flag is false, receiving must happen first, and then all children attached
123    at the end.
124    """)
125
126    truck_dump_ready = sa.Column(sa.Boolean(), nullable=True, default=False, doc="""
127    If batch is a "truck dump parent", this flag indicates whether it is
128    "ready" for the actual receiving process.  If children are to be attached
129    first, this flag should not be set until all children are attached.  If
130    children are last, this flag should be set immediately upon batch creation.
131    """)
132
133    truck_dump_status = sa.Column(sa.Integer(), nullable=True, doc="""
134    Truck dump status code for the batch.  Only relevant for truck dump parent
135    batches.  Indicates whether this parent has been fully claimed by children
136    yet, etc.
137    """)
138
139    receiving_complete = sa.Column(sa.Boolean(), nullable=True, doc="""
140    Flag to indicate whether the "receiving" process is complete for the batch.
141    Not applicable to all batches of course; depends on handler logic etc.
142    """)
143
144    truck_dump_batch_uuid = sa.Column(sa.String(length=32), nullable=True)
145    truck_dump_batch = orm.relationship(
146        'PurchaseBatch',
147        remote_side='PurchaseBatch.uuid',
148        doc="""
149        Reference to the "truck dump" receiving batch, for which the current
150        batch represents a single invoice which partially "consumes" the truck
151        dump.
152        """,
153        backref=orm.backref(
154            'truck_dump_children',
155            order_by='PurchaseBatch.id',
156            doc="""
157            List of batches which are "children" of the current batch, which is
158            assumed to be a truck dump.
159            """))
160
161    def is_truck_dump_parent(self):
162        """
163        Returns boolean indicating whether or not the batch is a "truck dump"
164        parent.
165        """
166        if self.truck_dump:
167            return True
168        return False
169
170    def is_truck_dump_child(self):
171        """
172        Returns boolean indicating whether or not the batch is a "truck dump"
173        child.
174        """
175        if self.truck_dump_batch:
176            return True
177        return False
178
179    def is_truck_dump_related(self):
180        """
181        Returns boolean indicating whether or not the batch is associated with
182        a "truck dump" in any way, i.e. is a parent or child of such.
183        """
184        if self.is_truck_dump_parent():
185            return True
186        if self.is_truck_dump_child():
187            return True
188        return False
189
190
191class PurchaseBatchRow(BatchRowMixin, PurchaseItemBase, Base):
192    """
193    Row of data within a purchase batch.
194    """
195    __tablename__ = 'purchase_batch_row'
196    __batch_class__ = PurchaseBatch
197
198    @declared_attr
199    def __table_args__(cls):
200        return cls.__batchrow_table_args__() + cls.__purchaseitem_table_args__() + (
201            sa.ForeignKeyConstraint(['item_uuid'], ['purchase_item.uuid'], name='purchase_batch_row_fk_item'),
202        )
203
204    STATUS_OK                           = 1
205    STATUS_PRODUCT_NOT_FOUND            = 2
206    STATUS_COST_NOT_FOUND               = 3
207    STATUS_CASE_QUANTITY_UNKNOWN        = 4
208    STATUS_INCOMPLETE                   = 5
209    STATUS_ORDERED_RECEIVED_DIFFER      = 6
210    STATUS_TRUCKDUMP_UNCLAIMED          = 7
211    STATUS_TRUCKDUMP_CLAIMED            = 8
212    STATUS_TRUCKDUMP_OVERCLAIMED        = 9
213    STATUS_CASE_QUANTITY_DIFFERS        = 10
214    STATUS_TRUCKDUMP_PARTCLAIMED        = 11
215    STATUS_OUT_OF_STOCK                 = 12
216
217    STATUS = {
218        STATUS_OK                       : "ok",
219        STATUS_PRODUCT_NOT_FOUND        : "product not found",
220        STATUS_COST_NOT_FOUND           : "product found but not cost",
221        STATUS_CASE_QUANTITY_UNKNOWN    : "case quantity not known",
222        STATUS_CASE_QUANTITY_DIFFERS    : "case quantity differs",
223        STATUS_INCOMPLETE               : "incomplete",
224        STATUS_ORDERED_RECEIVED_DIFFER  : "shipped / received differ",
225        STATUS_TRUCKDUMP_UNCLAIMED      : "not claimed by any child(ren)",
226        STATUS_TRUCKDUMP_PARTCLAIMED    : "partially claimed by child(ren)",
227        STATUS_TRUCKDUMP_CLAIMED        : "fully claimed by child(ren)",
228        STATUS_TRUCKDUMP_OVERCLAIMED    : "OVER claimed by child(ren)",
229        STATUS_OUT_OF_STOCK             : "out of stock",
230    }
231
232    item_entry = sa.Column(sa.String(length=32), nullable=True, doc="""
233    Raw entry value, as obtained from the initial data source, and which is
234    used to locate the product within the system.  This raw value is preserved
235    in case the initial lookup fails and a refresh must attempt further
236    lookup(s) later.  Only used by certain batch handlers in practice.
237    """)
238
239    item_uuid = sa.Column(sa.String(length=32), nullable=True)
240
241    item = orm.relationship(
242        PurchaseItem,
243        doc="""
244        Reference to the purchase item with which the batch row is associated.
245        May be null, e.g. in the case of a "new purchase" batch.
246        """)
247
248    po_total_calculated = sa.Column(sa.Numeric(precision=7, scale=2), nullable=True, doc="""
249    Calculated total cost for line item, per quantity ordered thus far.  See
250    also :attr:`po_total`.
251    """)
252
253    catalog_cost_confirmed = sa.Column(sa.Boolean(), nullable=True, doc="""
254    Flag indicating that the "catalog" cost for this row has been confirmed,
255    e.g. by a user.
256    """)
257
258    invoice_cost_confirmed = sa.Column(sa.Boolean(), nullable=True, doc="""
259    Flag indicating that the invoice cost for this row has been "confirmed",
260    e.g. by a user.
261    """)
262
263    invoice_total_calculated = sa.Column(sa.Numeric(precision=7, scale=2), nullable=True, doc="""
264    Calculated total cost for line item, per quantity received thus far.  See
265    also :attr:`invoice_total`.
266    """)
267
268    truck_dump_status = sa.Column(sa.Integer(), nullable=True, doc="""
269    Truck dump status code for the row.  Only relevant for truck dump parent
270    batches.  Indicates whether this parent row has been fully claimed by
271    children yet, etc.
272    """)
273
274
275class PurchaseBatchRowClaim(Base):
276    """
277    Represents the connection between a row(s) from a truck dump batch, and the
278    corresponding "child" batch row which claims it, as well as the claimed
279    quantities etc.
280    """
281    __tablename__ = 'purchase_batch_row_claim'
282    __table_args__ = (
283        sa.ForeignKeyConstraint(['claiming_row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_row_claim_fk_claiming_row'),
284        sa.ForeignKeyConstraint(['claimed_row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_row_claim_fk_claimed_row'),
285    )
286
287    uuid = uuid_column()
288
289    claiming_row_uuid = sa.Column(sa.String(length=32), nullable=False)
290    claiming_row = orm.relationship(
291        PurchaseBatchRow,
292        foreign_keys='PurchaseBatchRowClaim.claiming_row_uuid',
293        doc="""
294        Reference to the "child" row which is claiming some row from a truck
295        dump batch.
296        """,
297        backref=orm.backref(
298            'truck_dump_claims',
299            cascade='all, delete-orphan',
300            doc="""
301            List of claims which this "child" row makes against rows within a
302            truck dump batch.
303            """))
304
305    claimed_row_uuid = sa.Column(sa.String(length=32), nullable=False)
306    claimed_row = orm.relationship(
307        PurchaseBatchRow,
308        foreign_keys='PurchaseBatchRowClaim.claimed_row_uuid',
309        doc="""
310        Reference to the truck dump batch row which is claimed by the "child" row.
311        """,
312        backref=orm.backref(
313            'claims',
314            cascade='all, delete-orphan',
315            doc="""
316            List of claims made by "child" rows against this truck dump batch row.
317            """))
318
319    cases_received = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
320    Number of cases of product which were ultimately received, and are involved in the claim.
321    """)
322
323    units_received = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
324    Number of units of product which were ultimately received, and are involved in the claim.
325    """)
326
327    cases_damaged = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
328    Number of cases of product which were shipped damaged, and are involved in the claim.
329    """)
330
331    units_damaged = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
332    Number of units of product which were shipped damaged, and are involved in the claim.
333    """)
334
335    cases_expired = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
336    Number of cases of product which were shipped expired, and are involved in the claim.
337    """)
338
339    units_expired = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
340    Number of units of product which were shipped expired, and are involved in the claim.
341    """)
342
343    # TODO: should have fields for mispick here too, right?
344
345    def is_empty(self):
346        """
347        Returns boolean indicating whether the claim is "empty" - i.e. if it
348        has zero or null for all of its quantity fields.
349        """
350        if self.cases_received:
351            return False
352        if self.units_received:
353            return False
354
355        if self.cases_damaged:
356            return False
357        if self.units_damaged:
358            return False
359
360        if self.cases_expired:
361            return False
362        if self.units_expired:
363            return False
364
365        return True
366
367
368class PurchaseBatchCredit(PurchaseCreditBase, Base):
369    """
370    Represents a working copy of purchase credit tied to a batch row.
371    """
372    __tablename__ = 'purchase_batch_credit'
373
374    @declared_attr
375    def __table_args__(cls):
376        return cls.__purchasecredit_table_args__() + (
377            sa.ForeignKeyConstraint(['row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_credit_fk_row'),
378        )
379
380    uuid = uuid_column()
381
382    row_uuid = sa.Column(sa.String(length=32), nullable=True)
383
384    row = orm.relationship(
385        PurchaseBatchRow,
386        doc="""
387        Reference to the batch row with which the credit is associated.
388        """,
389        backref=orm.backref(
390            'credits',
391            cascade='all, delete-orphan',
392            doc="""
393            List of :class:`PurchaseBatchCredit` instances for the row.
394            """))
Note: See TracBrowser for help on using the repository browser.