Add support for editing "claim" quantities for truck dump child row

at least i think this gets it all...guess we'll see
This commit is contained in:
Lance Edgar 2018-08-07 15:19:38 -05:00
parent a348755be2
commit ac451757b4

View file

@ -237,19 +237,23 @@ class ReceivingBatchView(PurchasingBatchView):
'credits', 'credits',
] ]
# convenience list of all quantity attributes involved for a truck dump claim
claim_keys = [
'cases_received',
'units_received',
'cases_damaged',
'units_damaged',
'cases_expired',
'units_expired',
]
@property @property
def batch_mode(self): def batch_mode(self):
return self.enum.PURCHASE_BATCH_MODE_RECEIVING return self.enum.PURCHASE_BATCH_MODE_RECEIVING
def row_editable(self, row):
batch = row.batch
if batch.truck_dump_batch:
return False
return True
def row_deletable(self, row): def row_deletable(self, row):
batch = row.batch batch = row.batch
if batch.truck_dump: if batch.is_truck_dump_parent():
return True return True
return False return False
@ -681,6 +685,245 @@ class ReceivingBatchView(PurchasingBatchView):
f.set_readonly('po_total') f.set_readonly('po_total')
f.set_readonly('invoice_total') f.set_readonly('invoice_total')
def validate_row_form(self, form):
# if normal validation fails, stop there
if not super(ReceivingBatchView, self).validate_row_form(form):
return False
# if user is editing row from truck dump child, then we must further
# validate the form to ensure whatever new amounts they've requested
# would in fact fall within the bounds of what is available from the
# truck dump parent batch...
if self.editing:
batch = self.get_instance()
if batch.is_truck_dump_child():
old_row = self.get_row_instance()
case_quantity = old_row.case_quantity
# get all "existing" (old) claim amounts
old_claims = {}
for claim in old_row.truck_dump_claims:
for key in self.claim_keys:
amount = getattr(claim, key)
if amount is not None:
old_claims[key] = old_claims.get(key, 0) + amount
# get all "proposed" (new) claim amounts
new_claims = {}
for key in self.claim_keys:
amount = form.validated[key]
if amount is not colander.null and amount is not None:
# do not allow user to request a negative claim amount
if amount < 0:
self.request.session.flash("Cannot claim a negative amount for: {}".format(key), 'error')
return False
new_claims[key] = amount
# figure out what changes are actually being requested
claim_diff = {}
for key in new_claims:
if key not in old_claims:
claim_diff[key] = new_claims[key]
elif new_claims[key] != old_claims[key]:
claim_diff[key] = new_claims[key] - old_claims[key]
# do not allow user to request a negative claim amount
if claim_diff[key] < (0 - old_claims[key]):
self.request.session.flash("Cannot claim a negative amount for: {}".format(key), 'error')
return False
for key in old_claims:
if key not in new_claims:
claim_diff[key] = 0 - old_claims[key]
# find all rows from truck dump parent which "may" pertain to child row
# TODO: perhaps would need to do a more "loose" match on UPC also?
if not old_row.product_uuid:
raise NotImplementedError("Don't (yet) know how to handle edit for row with no product")
parent_rows = [row for row in batch.truck_dump_batch.active_rows()
if row.product_uuid == old_row.product_uuid]
# get existing "confirmed" and "claimed" amounts for all
# (possibly related) truck dump parent rows
confirmed = {}
claimed = {}
for parent_row in parent_rows:
for key in self.claim_keys:
amount = getattr(parent_row, key)
if amount is not None:
confirmed[key] = confirmed.get(key, 0) + amount
for claim in parent_row.claims:
for key in self.claim_keys:
amount = getattr(claim, key)
if amount is not None:
claimed[key] = claimed.get(key, 0) + amount
# now to see if user's request is possible, given what is
# available...
# first we must (pretend to) "relinquish" any claims which are
# to be reduced or eliminated, according to our diff
for key, amount in claim_diff.items():
if amount < 0:
amount = abs(amount) # make positive, for more readable math
if key not in claimed or claimed[key] < amount:
self.request.session.flash("Cannot relinquish more claims than the "
"parent batch has to offer.", 'error')
return False
claimed[key] -= amount
# next we must determine if any "new" requests would increase
# the claim(s) beyond what is available
for key, amount in claim_diff.items():
if amount > 0:
claimed[key] = claimed.get(key, 0) + amount
if key not in confirmed or confirmed[key] < claimed[key]:
self.request.session.flash("Cannot request to claim more product than "
"is available in Truck Dump Parent batch", 'error')
return False
# looks like the claim diff is all good, so let's attach that
# to the form now and then pick this up again in save()
form._claim_diff = claim_diff
# all validation went ok
return True
def save_edit_row_form(self, form):
batch = self.get_instance()
row = self.objectify(form)
# editing a row for truck dump child batch can be complicated...
if batch.is_truck_dump_child():
# grab the claim diff which we attached to the form during validation
claim_diff = form._claim_diff
# first we must "relinquish" any claims which are to be reduced or
# eliminated, according to our diff
for key, amount in claim_diff.items():
if amount < 0:
amount = abs(amount) # make positive, for more readable math
# we'd prefer to find an exact match, i.e. there was a 1CS
# claim and our diff said to reduce by 1CS
matches = [claim for claim in row.truck_dump_claims
if getattr(claim, key) == amount]
if matches:
claim = matches[0]
setattr(claim, key, None)
else:
# but if no exact match(es) then we'll just whittle
# away at whatever (smallest) claims we do find
possible = [claim for claim in row.truck_dump_claims
if getattr(claim, key) is not None]
for claim in sorted(possible, key=lambda claim: getattr(claim, key)):
previous = getattr(claim, key)
if previous:
if previous >= amount:
if (previous - amount):
setattr(claim, key, previous - amount)
else:
setattr(claim, key, None)
amount = 0
break
else:
setattr(claim, key, None)
amount -= previous
if amount:
raise NotImplementedError("Had leftover amount when \"relinquishing\" claim(s)")
# next we must stake all new claim(s) as requested, per our diff
for key, amount in claim_diff.items():
if amount > 0:
# if possible, we'd prefer to add to an existing claim
# which already has an amount for this key
existing = [claim for claim in row.truck_dump_claims
if getattr(claim, key) is not None]
if existing:
claim = existing[0]
setattr(claim, key, getattr(claim, key) + amount)
# next we'd prefer to add to an existing claim, of any kind
elif row.truck_dump_claims:
claim = row.truck_dump_claims[0]
setattr(claim, key, getattr(claim, key) + amount)
else:
# otherwise we must create a new claim...
# find all rows from truck dump parent which "may" pertain to child row
# TODO: perhaps would need to do a more "loose" match on UPC also?
if not row.product_uuid:
raise NotImplementedError("Don't (yet) know how to handle edit for row with no product")
parent_rows = [parent_row for parent_row in batch.truck_dump_batch.active_rows()
if parent_row.product_uuid == row.product_uuid]
# remove any parent rows which are fully claimed
# TODO: should perhaps leverage actual amounts for this, instead
parent_rows = [parent_row for parent_row in parent_rows
if parent_row.status_code != parent_row.STATUS_TRUCKDUMP_CLAIMED]
# try to find a parent row which is exact match on claim amount
matches = [parent_row for parent_row in parent_rows
if getattr(parent_row, key) == amount]
if matches:
# make the claim against first matching parent row
claim = model.PurchaseBatchRowClaim()
claim.claimed_row = parent_rows[0]
setattr(claim, key, amount)
row.truck_dump_claims.append(claim)
else:
# but if no exact match(es) then we'll just whittle
# away at whatever (smallest) parent rows we do find
for parent_row in sorted(parent_rows, lambda prow: getattr(prow, key)):
available = getattr(parent_row, key) - sum([getattr(claim, key) for claim in parent_row.claims])
if available:
if available >= amount:
# make claim against this parent row, making it fully claimed
claim = model.PurchaseBatchRowClaim()
claim.claimed_row = parent_row
setattr(claim, key, amount)
row.truck_dump_claims.append(claim)
amount = 0
break
else:
# make partial claim against this parent row
claim = model.PurchaseBatchRowClaim()
claim.claimed_row = parent_row
setattr(claim, key, available)
row.truck_dump_claims.append(claim)
amount -= available
if amount:
raise NotImplementedError("Had leftover amount when \"staking\" claim(s)")
# now we must be sure to refresh all truck dump parent batch rows
# which were affected. but along with that we also should purge
# any empty claims, i.e. those which were fully relinquished
pending_refresh = set()
for claim in list(row.truck_dump_claims):
parent_row = claim.claimed_row
if claim.is_empty():
row.truck_dump_claims.remove(claim)
self.Session.flush()
pending_refresh.add(parent_row)
for parent_row in pending_refresh:
self.handler.refresh_row(parent_row)
self.handler.refresh_batch_status(batch.truck_dump_batch)
self.after_edit_row(row)
self.Session.flush()
return row
def redirect_after_edit_row(self, row, mobile=False):
return self.redirect(self.get_row_action_url('view', row, mobile=mobile))
def render_mobile_row_listitem(self, row, i): def render_mobile_row_listitem(self, row, i):
key = self.render_product_key_value(row) key = self.render_product_key_value(row)
description = row.product.full_description if row.product else row.description description = row.product.full_description if row.product else row.description