From cf8b0282a52efb97881f627ebf8dac72656bb6db Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 23 Feb 2020 20:12:17 -0600 Subject: [PATCH] Add `update_row_quantity()`, `order_row()` methods for purchase batch handler --- docs/api/rattail/batch/purchase.rst | 4 ++ rattail/batch/purchase.py | 105 ++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/docs/api/rattail/batch/purchase.rst b/docs/api/rattail/batch/purchase.rst index d45e1788..bde1c9b8 100644 --- a/docs/api/rattail/batch/purchase.rst +++ b/docs/api/rattail/batch/purchase.rst @@ -19,6 +19,8 @@ .. automethod:: refresh_row + .. automethod:: order_row + .. automethod:: receive_row .. automethod:: receiving_update_row_attrs @@ -31,6 +33,8 @@ .. automethod:: receiving_find_best_child_row + .. automethod:: update_row_quantity + .. automethod:: update_row_cost .. automethod:: refresh diff --git a/rattail/batch/purchase.py b/rattail/batch/purchase.py index 58977aa0..4fda81b0 100644 --- a/rattail/batch/purchase.py +++ b/rattail/batch/purchase.py @@ -1553,6 +1553,111 @@ class PurchaseBatchHandler(BatchHandler): session.flush() self.refresh_row(row) + def update_row_quantity(self, row, **kwargs): + """ + Update quantity value(s) for the given row, and calculate new totals + accordingly. This will handle updating the row as well as the batch, + as necessary. Which kwargs this method accepts, and which values are + updated, will depend on the batch mode. + + *Ordering Mode* + + Possible kwargs are: + + * ``cases_ordered`` + * ``units_ordered`` + + Logic will figure out the "diff" between the given quantites, and the + row's existing values at the time. It then invokes :meth:`order_row()` + with the diff values. + """ + batch = row.batch + + if batch.mode == self.enum.PURCHASE_BATCH_MODE_ORDERING: + if 'cases_ordered' in kwargs: + cases_diff = kwargs['cases_ordered'] - (row.cases_ordered or 0) + if cases_diff: + self.order_row(row, cases=cases_diff) + if 'units_ordered' in kwargs: + units_diff = kwargs['units_ordered'] - (row.units_ordered or 0) + if units_diff: + self.order_row(row, units=units_diff) + + def order_row(self, row, cases=None, units=None, **kwargs): + """ + This method is conceptually similar to :meth:`receive_row()` and, while + the latter is more "necessary" than this one is, this method tries to + match its style just for consistency. Callers may or may not need this + method directly, but are welcome to use it. + + Each call to this method must include the row to be updated, as well as + the details of the update. These details should reflect "changes" + which are to be made, as opposed to "final values" for the row. In + other words if a row already has ``cases_ordered == 1`` and the user + is ordering a second case, this method should be called like so:: + + handler.order_row(row, cases=1) + + The row will be updated such that ``cases_ordered == 2``; the main + point here is that the caller should *not* specify ``cases=2`` because + it is the handler's job to "apply changes" from the caller. (If the + caller speficies ``cases=2`` then the row would end up with + ``cases_ordered == 3``.) + + See also :meth:`update_row_quantity()` which allows the caller to + specify the final values instead. + + For "undo" type adjustments, caller can just send a negative amount, + and the handler will apply the changes as expected:: + + handler.order_row(row, cases=-1) + + Note that each call must specify *either* a (non-empty) ``cases`` or + ``units`` value, but *not* both! If you need to adjust both then you + must make two separate calls. + + :param ~rattail.db.model.batch.purchase.PurchaseBatchRow row: Batch row + which is to be updated with the given order data. The row must + exist, i.e. this method will not create a new row for you. + + :param ~decimal.Decimal cases: Case quantity for the update, if applicable. + + :param ~decimal.Decimal units: Unit quantity for the update, if applicable. + """ + # make sure we have cases *or* units + if not (cases or units): + raise ValueError("must provide amount for cases *or* units") + if cases and units: + raise ValueError("must provide amount for cases *or* units (but not both)") + + batch = row.batch + + # make sure we have a (non-executed) ordering batch + if batch.mode != self.enum.PURCHASE_BATCH_MODE_ORDERING: + raise NotImplementedError("order_row() is only for ordering batches") + if batch.executed: + raise NotImplementedError("order_row() is only for *non-executed* batches") + + # add values as-is to existing case/unit amounts. + # TODO: what if this gives us negative values? + if cases: + row.cases_ordered = (row.cases_ordered or 0) + cases + if units: + row.units_ordered = (row.units_ordered or 0) + units + + # TODO: pretty sure this isn't needed? + # # refresh row status etc. + # self.refresh_row(row) + + # update calculated PO totals + po_amount = 0 + if cases: + po_amount += cases * (row.case_quantity or 0) * (row.po_unit_cost or 0) + if units: + po_amount += units * (row.po_unit_cost or 0) + row.po_total_calculated = (row.po_total_calculated or 0) + po_amount + batch.po_total_calculated = (batch.po_total_calculated or 0) + po_amount + def populate_credit(self, credit, row): """ Populate all basic attributes for the given credit, from the given row.