source: rattail/rattail/db/model/batch/purchase.py @ 41450bbe

Last change on this file since 41450bbe was 41450bbe, checked in by Lance Edgar <lance@…>, 21 months ago

Add new "partially claimed" status for truck dump parent batch rows

row starts off with "no claims" then "partially claimed" then "fully claimed"

  • Property mode set to 100644
File size: 12.5 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_uuid = sa.Column(sa.String(length=32), nullable=True)
196
197    item = orm.relationship(
198        PurchaseItem,
199        doc="""
200        Reference to the purchase item with which the batch row is associated.
201        May be null, e.g. in the case of a "new purchase" batch.
202        """)
203
204
205class PurchaseBatchRowClaim(Base):
206    """
207    Represents the connection between a row(s) from a truck dump batch, and the
208    corresponding "child" batch row which claims it, as well as the claimed
209    quantities etc.
210    """
211    __tablename__ = 'purchase_batch_row_claim'
212    __table_args__ = (
213        sa.ForeignKeyConstraint(['claiming_row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_row_claim_fk_claiming_row'),
214        sa.ForeignKeyConstraint(['claimed_row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_row_claim_fk_claimed_row'),
215    )
216
217    uuid = uuid_column()
218
219    claiming_row_uuid = sa.Column(sa.String(length=32), nullable=False)
220    claiming_row = orm.relationship(
221        PurchaseBatchRow,
222        foreign_keys='PurchaseBatchRowClaim.claiming_row_uuid',
223        doc="""
224        Reference to the "child" row which is claiming some row from a truck
225        dump batch.
226        """,
227        backref=orm.backref(
228            'truck_dump_claims',
229            cascade='all, delete-orphan',
230            doc="""
231            List of claims which this "child" row makes against rows within a
232            truck dump batch.
233            """))
234
235    claimed_row_uuid = sa.Column(sa.String(length=32), nullable=False)
236    claimed_row = orm.relationship(
237        PurchaseBatchRow,
238        foreign_keys='PurchaseBatchRowClaim.claimed_row_uuid',
239        doc="""
240        Reference to the truck dump batch row which is claimed by the "child" row.
241        """,
242        backref=orm.backref(
243            'claims',
244            cascade='all, delete-orphan',
245            doc="""
246            List of claims made by "child" rows against this truck dump batch row.
247            """))
248
249    cases_received = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
250    Number of cases of product which were ultimately received, and are involved in the claim.
251    """)
252
253    units_received = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
254    Number of units of product which were ultimately received, and are involved in the claim.
255    """)
256
257    cases_damaged = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
258    Number of cases of product which were shipped damaged, and are involved in the claim.
259    """)
260
261    units_damaged = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
262    Number of units of product which were shipped damaged, and are involved in the claim.
263    """)
264
265    cases_expired = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
266    Number of cases of product which were shipped expired, and are involved in the claim.
267    """)
268
269    units_expired = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc="""
270    Number of units of product which were shipped expired, and are involved in the claim.
271    """)
272
273    def is_empty(self):
274        """
275        Returns boolean indicating whether the claim is "empty" - i.e. if it
276        has zero or null for all of its quantity fields.
277        """
278        if self.cases_received:
279            return False
280        if self.units_received:
281            return False
282
283        if self.cases_damaged:
284            return False
285        if self.units_damaged:
286            return False
287
288        if self.cases_expired:
289            return False
290        if self.units_expired:
291            return False
292
293        return True
294
295
296@six.python_2_unicode_compatible
297class PurchaseBatchCredit(PurchaseCreditBase, Base):
298    """
299    Represents a working copy of purchase credit tied to a batch row.
300    """
301    __tablename__ = 'purchase_batch_credit'
302
303    @declared_attr
304    def __table_args__(cls):
305        return cls.__purchasecredit_table_args__() + (
306            sa.ForeignKeyConstraint(['row_uuid'], ['purchase_batch_row.uuid'], name='purchase_batch_credit_fk_row'),
307        )
308
309    uuid = uuid_column()
310
311    row_uuid = sa.Column(sa.String(length=32), nullable=True)
312
313    row = orm.relationship(
314        PurchaseBatchRow,
315        doc="""
316        Reference to the batch row with which the credit is associated.
317        """,
318        backref=orm.backref(
319            'credits',
320            doc="""
321            List of :class:`PurchaseBatchCredit` instances for the row.
322            """))
323
324    def __str__(self):
325        if self.cases_shorted is not None and self.units_shorted is not None:
326            qty = "{} cases, {} units".format(
327                pretty_quantity(self.cases_shorted),
328                pretty_quantity(self.units_shorted))
329        elif self.cases_shorted is not None:
330            qty = "{} cases".format(pretty_quantity(self.cases_shorted))
331        elif self.units_shorted is not None:
332            qty = "{} units".format(pretty_quantity(self.units_shorted))
333        else:
334            qty = "??"
335        qty += " {}".format(self.credit_type)
336        if self.credit_type == 'expired' and self.expiration_date:
337            qty += " ({})".format(self.expiration_date)
338        if self.credit_type == 'mispick' and self.mispick_product:
339            qty += " ({})".format(self.mispick_product)
340        if self.invoice_total:
341            return "{} = ${:0.2f}".format(qty, self.invoice_total)
342        return qty
Note: See TracBrowser for help on using the repository browser.