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:
parent
a348755be2
commit
ac451757b4
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue