source: rattail/rattail/db/model/batch/purchase.py @ 6927d74

Last change on this file since 6927d74 was 6927d74, checked in by Lance Edgar <ledgar@…>, 6 months ago

Fix logic for calculating "credit total"

also copy receiving date from truck dump parent to child, and fix output of
str(PurchaseCredit)

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