Changeset 5cff218 in rattail


Ignore:
Timestamp:
02/28/19 15:53:36 (8 weeks ago)
Author:
Lance Edgar <ledgar@…>
Branches:
master
Children:
277334e8
Parents:
f44a821
Message:

Add first implementation of receive_row() for purchase batch handler

this logic *should* be a copy/paste essentially, from tailbone

File:
1 edited

Legend:

Unmodified
Added
Removed
  • rattail/batch/purchase.py

    rf44a821 r5cff218  
    974974                self.enum.PURHASE_BATCH_MODE.get(batch.mode, "unknown ({})".format(batch.mode))))
    975975
    976     def receive_row(self, row, **kwargs):
     976    def receive_row(self, row, mode='received', cases=None, units=None, **kwargs):
    977977        """
    978978        This method is arguably the workhorse of the whole process. Callers
     
    980980        receiving workflow.
    981981
    982         :param row: Reference to the batch row which is to be updated with the
    983            given receiving data.  The row must exist, i.e. this method will not
    984            create a row for you.
    985 
    986         .. todo::
    987            ``**kwargs`` can include several things, some of which should
    988            probably be documented...
    989         """
    990         raise NotImplementedError
     982        Each call to this method must include the row to be updated, as well as
     983        the details of the update.  These details should reflect "changes"
     984        which are to be made, as opposed to "final values" for the row.  In
     985        other words if a row already has ``cases_received == 1`` and the user
     986        is receiving a second case, this method should be called like so::
     987
     988           handler.receive_row(row, mode='received', cases=1)
     989
     990        The row will be updated such that ``cases_received == 2``; the main
     991        point here is that the caller should *not* specify ``cases=2`` because
     992        it is the handler's job to "apply changes" from the caller.  (If the
     993        caller speficies ``cases=2`` then the row would end up with
     994        ``cases_received == 3``.)
     995
     996        For "undo" type adjustments, can just send a negative amount, and the
     997        handler will apply the changes as expected::
     998
     999           handler.receive_row(row, mode='received', cases=-1)
     1000
     1001        Note that each call must specify *either* a (non-empty) ``cases`` or
     1002        ``units`` value, but *not* both!
     1003
     1004        :param rattail.db.model.batch.purchase.PurchaseBatchRow row: Reference
     1005           to the batch row which is to be updated with the given receiving
     1006           data.  The row must exist, i.e. this method will not create a new
     1007           row for you.
     1008
     1009        :param str mode: Must be one of the receiving modes which are
     1010           "supported" according to the handler.  Possible modes include:
     1011
     1012           * ``'received'``
     1013           * ``'damaged'``
     1014           * ``'expired'``
     1015           * ``'mispick'``
     1016
     1017        :param decimal.Decimal cases: Case quantity for the update, if applicable.
     1018
     1019        :param decimal.Decimal units: Unit quantity for the update, if applicable.
     1020
     1021        :param datetime.date expiration_date: Expiration date for the update,
     1022           if applicable.  Only used if ``mode='expired'``.
     1023
     1024        This method exists mostly to consolidate the various logical steps which
     1025        must be taken for each new receiving input from the user.  Under the hood
     1026        it delegates to a few other methods:
     1027
     1028        * :meth:`receiving_update_row_attrs()`
     1029        * :meth:`receiving_update_row_credits()`
     1030        * :meth:`receiving_update_row_children()`
     1031        """
     1032        # make sure we have cases *or* units
     1033        if not (cases or units):
     1034            raise ValueError("must provide amount for cases *or* units")
     1035        if cases and units:
     1036            raise ValueError("must provide amount for cases *or* units (but not both)")
     1037
     1038        # make sure we have a receiving batch
     1039        if row.batch.mode != self.enum.PURCHASE_BATCH_MODE_RECEIVING:
     1040            raise NotImplementedError("receive_row() is only for receiving batches")
     1041
     1042        # update the given row
     1043        self.receiving_update_row_attrs(row, mode, cases, units)
     1044
     1045        # update the given row's credits
     1046        self.receiving_update_row_credits(row, mode, cases, units, **kwargs)
     1047
     1048        # update the given row's "children" (if this is truck dump parent)
     1049        self.receiving_update_row_children(row, mode, cases, units, **kwargs)
     1050
     1051    def receiving_update_row_attrs(self, row, mode, cases, units):
     1052        """
     1053        Apply a receiving update to the row's attributes.
     1054
     1055        Note that this should not be called directly; it is invoked as part of
     1056        :meth:`receive_row()`.
     1057        """
     1058        batch = row.batch
     1059
     1060        # add values as-is to existing case/unit amounts.  note
     1061        # that this can sometimes give us negative values!  e.g. if
     1062        # user scans 1 CS and then subtracts 2 EA, then we would
     1063        # have 1 / -2 for our counts.  but we consider that to be
     1064        # expected, and other logic must allow for the possibility
     1065        if cases:
     1066            setattr(row, 'cases_{}'.format(mode),
     1067                    (getattr(row, 'cases_{}'.format(mode)) or 0) + cases)
     1068        if units:
     1069            setattr(row, 'units_{}'.format(mode),
     1070                    (getattr(row, 'units_{}'.format(mode)) or 0) + units)
     1071
     1072        # undo any totals previously in effect for the row, then refresh.  this
     1073        # updates some totals as well as status for the row
     1074        if row.invoice_total:
     1075            batch.invoice_total -= row.invoice_total
     1076        self.refresh_row(row)
     1077
     1078    def receiving_update_row_credits(self, row, mode, cases, units, **kwargs):
     1079        """
     1080        Apply a receiving update to the row's credits, if applicable.
     1081
     1082        Note that this should not be called directly; it is invoked as part of
     1083        :meth:`receive_row()`.
     1084        """
     1085        batch = row.batch
     1086
     1087        # only certain modes should involve credits
     1088        if mode not in ('damaged', 'expired', 'mispick'):
     1089            return
     1090
     1091        # TODO: need to add mispick support obviously
     1092        if mode == 'mispick':
     1093            raise NotImplementedError("mispick credits not yet supported")
     1094
     1095        # always make new credit; never aggregate
     1096        credit = model.PurchaseBatchCredit()
     1097        credit.credit_type = mode
     1098        credit.store = batch.store
     1099        credit.vendor = batch.vendor
     1100        credit.date_ordered = batch.date_ordered
     1101        credit.date_shipped = batch.date_shipped
     1102        credit.date_received = batch.date_received
     1103        credit.invoice_number = batch.invoice_number
     1104        credit.invoice_date = batch.invoice_date
     1105        credit.product = row.product
     1106        credit.upc = row.upc
     1107        credit.vendor_item_code = row.vendor_code
     1108        credit.brand_name = row.brand_name
     1109        credit.description = row.description
     1110        credit.size = row.size
     1111        credit.department_number = row.department_number
     1112        credit.department_name = row.department_name
     1113        credit.case_quantity = row.case_quantity
     1114        credit.cases_shorted = cases
     1115        credit.units_shorted = units
     1116        credit.invoice_line_number = row.invoice_line_number
     1117        credit.invoice_case_cost = row.invoice_case_cost
     1118        credit.invoice_unit_cost = row.invoice_unit_cost
     1119        credit.invoice_total = row.invoice_total
     1120
     1121        # calculate credit total
     1122        # TODO: should this leverage case cost if present?
     1123        credit_units = self.get_units(credit.cases_shorted,
     1124                                      credit.units_shorted,
     1125                                      credit.case_quantity)
     1126        credit.credit_total = credit_units * (credit.invoice_unit_cost or 0)
     1127
     1128        # apply other attributes to credit, per caller kwargs
     1129        credit.product_discarded = kwargs.get('discarded')
     1130        if mode == 'expired':
     1131            credit.expiration_date = kwargs.get('expiration_date')
     1132        elif mode == 'mispick' and kwargs.get('mispick_product'):
     1133            mispick_product = kwargs['mispick_product']
     1134            credit.mispick_product = mispick_product
     1135            credit.mispick_upc = mispick_product.upc
     1136            if mispick_product.brand:
     1137                credit.mispick_brand_name = mispick_product.brand.name
     1138            credit.mispick_description = mispick_product.description
     1139            credit.mispick_size = mispick_product.size
     1140
     1141        # attach credit to row
     1142        row.credits.append(credit)
     1143
     1144    def receiving_update_row_children(self, row, mode, cases, units, **kwargs):
     1145        """
     1146        Apply a receiving update to the row's "children", if applicable.
     1147
     1148        Note that this should not be called directly; it is invoked as part of
     1149        :meth:`receive_row()`.
     1150        """
     1151        batch = row.batch
     1152
     1153        # updating row children is only applicable for truck dump parent, and
     1154        # even then only if "children first" workflow
     1155        if not batch.is_truck_dump_parent():
     1156            return
     1157        if not batch.truck_dump_children_first:
     1158            return
     1159
     1160        # TODO: this is *not* sufficient, but is all previous logic did
     1161        if row.product:
     1162            self.make_truck_dump_claims_for_parent_row(row)
    9911163
    9921164    def remove_row(self, row):
Note: See TracChangeset for help on using the changeset viewer.