source: rattail/rattail/db/model/batch/purchase.py @ 7b4d418

Last change on this file since 7b4d418 was 7b4d418, checked in by Lance Edgar <ledgar@…>, 13 months ago

Add "calculated" invoice total for receiving row, batch

so then invoice_total is meant to reflect the "original" total as obtained
from the invoice proper, whereas invoice_total_calculated is up to us

  • Property mode set to 100644
File size: 13.8 KB
Line 
1# -*- coding: utf-8; -*-
2################################################################################
3#
4#  Rattail -- Retail Software Framework
5#  Copyright © 2010-2019 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    invoice_total_calculated = sa.Column(sa.Numeric(precision=8, scale=2), nullable=True, doc="""
103    Calculated invoice total, per quantity received thus far (if applicable).
104    """)
105
106    truck_dump = sa.Column(sa.Boolean(), nullable=True, default=False, doc="""
107    Flag indicating whether a "receiving" batch is of the "truck dump"
108    persuasion, i.e.  it does not correspond to a single purchase order but
109    rather is assumed to represent multiple orders.
110    """)
111
112    truck_dump_children_first = sa.Column(sa.Boolean(), nullable=True, default=False, doc="""
113    If batch is a "truck dump parent", this flag indicates whether its
114    "children" are to be attached *first* as opposed to *last*.  If the flag is
115    true, all children must be attached prior to the receiving process.  If
116    flag is false, receiving must happen first, and then all children attached
117    at the end.
118    """)
119
120    truck_dump_ready = sa.Column(sa.Boolean(), nullable=True, default=False, doc="""
121    If batch is a "truck dump parent", this flag indicates whether it is
122    "ready" for the actual receiving process.  If children are to be attached
123    first, this flag should not be set until all children are attached.  If
124    children are last, this flag should be set immediately upon batch creation.
125    """)
126
127    truck_dump_status = sa.Column(sa.Integer(), nullable=True, doc="""
128    Truck dump status code for the row.  Only relevant for truck dump parent
129    batches.  Indicates whether this parent has been fully claimed by children
130    yet, etc.
131    """)
132
133    truck_dump_batch_uuid = sa.Column(sa.String(length=32), nullable=True)
134    truck_dump_batch = orm.relationship(
135        'PurchaseBatch',
136        remote_side='PurchaseBatch.uuid',
137        doc="""
138        Reference to the "truck dump" receiving batch, for which the current
139        batch represents a single invoice which partially "consumes" the truck
140        dump.
141        """,
142        backref=orm.backref(
143            'truck_dump_children',
144            order_by='PurchaseBatch.id',
145            doc="""
146            List of batches which are "children" of the current batch, which is
147            assumed to be a truck dump.
148            """))
149
150    def is_truck_dump_parent(self):
151        """
152        Returns boolean indicating whether or not the batch is a "truck dump"
153        parent.
154        """
155        if self.truck_dump:
156            return True
157        return False
158
159    def is_truck_dump_child(self):
160        """
161        Returns boolean indicating whether or not the batch is a "truck dump"
162        child.
163        """
164        if self.truck_dump_batch:
165            return True
166        return False
167
168    def is_truck_dump_related(self):
169        """
170        Returns boolean indicating whether or not the batch is associated with
171        a "truck dump" in any way, i.e. is a parent or child of such.
172        """
173        if self.is_truck_dump_parent():
174            return True
175        if self.is_truck_dump_child():
176            return True
177        return False
178
179
180class PurchaseBatchRow(BatchRowMixin, PurchaseItemBase, Base):
181    """
182    Row of data within a purchase batch.
183    """
184    __tablename__ = 'purchase_batch_row'
185    __batch_class__ = PurchaseBatch
186
187    @declared_attr
188    def __table_args__(cls):
189        return cls.__batchrow_table_args__() + cls.__purchaseitem_table_args__() + (
190            sa.ForeignKeyConstraint(['item_uuid'], ['purchase_item.uuid'], name='purchase_batch_row_fk_item'),
191        )
192
193    STATUS_OK                           = 1
194    STATUS_PRODUCT_NOT_FOUND            = 2
195    STATUS_COST_NOT_FOUND               = 3
196    STATUS_CASE_QUANTITY_UNKNOWN        = 4
197    STATUS_INCOMPLETE                   = 5
198    STATUS_ORDERED_RECEIVED_DIFFER      = 6
199    STATUS_TRUCKDUMP_UNCLAIMED          = 7
200    STATUS_TRUCKDUMP_CLAIMED            = 8
201    STATUS_TRUCKDUMP_OVERCLAIMED        = 9
202    STATUS_CASE_QUANTITY_DIFFERS        = 10
203    STATUS_TRUCKDUMP_PARTCLAIMED        = 11
204    STATUS_OUT_OF_STOCK                 = 12
205
206    STATUS = {
207        STATUS_OK                       : "ok",
208        STATUS_PRODUCT_NOT_FOUND        : "product not found",
209        STATUS_COST_NOT_FOUND           : "product found but not cost",
210        STATUS_CASE_QUANTITY_UNKNOWN    : "case quantity not known",
211        STATUS_CASE_QUANTITY_DIFFERS    : "case quantity differs",
212        STATUS_INCOMPLETE               : "incomplete",
213        STATUS_ORDERED_RECEIVED_DIFFER  : "ordered / received differ",
214        STATUS_TRUCKDUMP_UNCLAIMED      : "not claimed by any child(ren)",
215        STATUS_TRUCKDUMP_PARTCLAIMED    : "partially claimed by child(ren)",
216        STATUS_TRUCKDUMP_CLAIMED        : "fully claimed by child(ren)",
217        STATUS_TRUCKDUMP_OVERCLAIMED    : "OVER claimed by child(ren)",
218        STATUS_OUT_OF_STOCK             : "out of stock",
219    }
220
221    item_entry = sa.Column(sa.String(length=20), nullable=True, doc="""
222    Raw entry value, as obtained from the initial data source, and which is
223    used to locate the product within the system.  This raw value is preserved
224    in case the initial lookup fails and a refresh must attempt further
225    lookup(s) later.  Only used by certain batch handlers in practice.
226    """)
227
228    item_uuid = sa.Column(sa.String(length=32), nullable=True)
229
230    item = orm.relationship(
231        PurchaseItem,
232        doc="""
233        Reference to the purchase item with which the batch row is associated.
234        May be null, e.g. in the case of a "new purchase" batch.
235        """)
236
237    invoice_total_calculated = sa.Column(sa.Numeric(precision=7, scale=2), nullable=True, doc="""
238    Calculated total cost for line item, per quantity received thus far.  See
239    also :attr:`invoice_total`.
240    """)
241
242    truck_dump_status = sa.Column(sa.Integer(), nullable=True, doc="""
243    Truck dump status code for the row.  Only relevant for truck dump parent
244    batches.  Indicates whether this parent row has been fully claimed by
245    children yet, etc.
246    """)
247
248
249class PurchaseBatchRowClaim(Base):
250    """
251    Represents the connection between a row(s) from a truck dump batch, and the
252    corresponding "child" batch row which claims it, as well as the claimed
253    quantities etc.
254    """
255    __tablename__ = 'purchase_batch_row_claim'
256    __table_args__ = (
257        sa.ForeignKeyConstraint(['claiming_row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_row_claim_fk_claiming_row'),
258        sa.ForeignKeyConstraint(['claimed_row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_row_claim_fk_claimed_row'),
259    )
260
261    uuid = uuid_column()
262
263    claiming_row_uuid = sa.Column(sa.String(length=32), nullable=False)
264    claiming_row = orm.relationship(
265        PurchaseBatchRow,
266        foreign_keys='PurchaseBatchRowClaim.claiming_row_uuid',
267        doc="""
268        Reference to the "child" row which is claiming some row from a truck
269        dump batch.
270        """,
271        backref=orm.backref(
272            'truck_dump_claims',
273            cascade='all, delete-orphan',
274            doc="""
275            List of claims which this "child" row makes against rows within a
276            truck dump batch.
277            """))
278
279    claimed_row_uuid = sa.Column(sa.String(length=32), nullable=False)
280    claimed_row = orm.relationship(
281        PurchaseBatchRow,
282        foreign_keys='PurchaseBatchRowClaim.claimed_row_uuid',
283        doc="""
284        Reference to the truck dump batch row which is claimed by the "child" row.
285        """,
286        backref=orm.backref(
287            'claims',
288            cascade='all, delete-orphan',
289            doc="""
290            List of claims made by "child" rows against this truck dump batch row.
291            """))
292
293    cases_received = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
294    Number of cases of product which were ultimately received, and are involved in the claim.
295    """)
296
297    units_received = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
298    Number of units of product which were ultimately received, and are involved in the claim.
299    """)
300
301    cases_damaged = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
302    Number of cases of product which were shipped damaged, and are involved in the claim.
303    """)
304
305    units_damaged = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
306    Number of units of product which were shipped damaged, and are involved in the claim.
307    """)
308
309    cases_expired = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
310    Number of cases of product which were shipped expired, and are involved in the claim.
311    """)
312
313    units_expired = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
314    Number of units of product which were shipped expired, and are involved in the claim.
315    """)
316
317    # TODO: should have fields for mispick here too, right?
318
319    def is_empty(self):
320        """
321        Returns boolean indicating whether the claim is "empty" - i.e. if it
322        has zero or null for all of its quantity fields.
323        """
324        if self.cases_received:
325            return False
326        if self.units_received:
327            return False
328
329        if self.cases_damaged:
330            return False
331        if self.units_damaged:
332            return False
333
334        if self.cases_expired:
335            return False
336        if self.units_expired:
337            return False
338
339        return True
340
341
342class PurchaseBatchCredit(PurchaseCreditBase, Base):
343    """
344    Represents a working copy of purchase credit tied to a batch row.
345    """
346    __tablename__ = 'purchase_batch_credit'
347
348    @declared_attr
349    def __table_args__(cls):
350        return cls.__purchasecredit_table_args__() + (
351            sa.ForeignKeyConstraint(['row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_credit_fk_row'),
352        )
353
354    uuid = uuid_column()
355
356    row_uuid = sa.Column(sa.String(length=32), nullable=True)
357
358    row = orm.relationship(
359        PurchaseBatchRow,
360        doc="""
361        Reference to the batch row with which the credit is associated.
362        """,
363        backref=orm.backref(
364            'credits',
365            doc="""
366            List of :class:`PurchaseBatchCredit` instances for the row.
367            """))
Note: See TracBrowser for help on using the repository browser.