From fc830f60e8f2276d80bf6697fff2df02b761e9f2 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 24 Feb 2020 12:36:47 -0600 Subject: [PATCH] Tweak `worksheet_update()` of ordering batch view, to leverage handler specifically this is to make use of handler's `update_row_quantity()` method, when user enters new order quantities via worksheet --- docs/api/views/purchasing.ordering.rst | 13 +++ docs/index.rst | 1 + tailbone/templates/ordering/worksheet.mako | 20 ++-- tailbone/views/purchasing/ordering.py | 118 +++++++++++++++------ 4 files changed, 112 insertions(+), 40 deletions(-) create mode 100644 docs/api/views/purchasing.ordering.rst diff --git a/docs/api/views/purchasing.ordering.rst b/docs/api/views/purchasing.ordering.rst new file mode 100644 index 00000000..7dffd964 --- /dev/null +++ b/docs/api/views/purchasing.ordering.rst @@ -0,0 +1,13 @@ + +``tailbone.views.purchasing.ordering`` +====================================== + +.. automodule:: tailbone.views.purchasing.ordering + +.. autoclass:: OrderingBatchView + + .. autoattribute:: model_class + + .. autoattribute:: default_handler_spec + + .. automethod:: worksheet_update diff --git a/docs/index.rst b/docs/index.rst index 4fd9bdd7..ffa516e9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -53,6 +53,7 @@ Package API: api/views/core api/views/master api/views/purchasing.batch + api/views/purchasing.ordering Documentation To-Do diff --git a/tailbone/templates/ordering/worksheet.mako b/tailbone/templates/ordering/worksheet.mako index 214e29a2..7e5dbe79 100644 --- a/tailbone/templates/ordering/worksheet.mako +++ b/tailbone/templates/ordering/worksheet.mako @@ -36,10 +36,16 @@ if (data.error) { alert(data.error); } else { - row.find('input[name^="cases_ordered_"]').val(data.row_cases_ordered); - row.find('input[name^="units_ordered_"]').val(data.row_units_ordered); - row.find('td.po-total').html(data.row_po_total); - $('.po-total .field').html(data.batch_po_total); + if (data.row_cases_ordered || data.row_units_ordered) { + row.find('input[name^="cases_ordered_"]').val(data.row_cases_ordered); + row.find('input[name^="units_ordered_"]').val(data.row_units_ordered); + row.find('td.po-total').html(data.row_po_total_calculated); + } else { + row.find('input[name^="cases_ordered_"]').val(''); + row.find('input[name^="units_ordered_"]').val(''); + row.find('td.po-total').html(''); + } + $('.po-total .field').html(data.batch_po_total_calculated); } submitting = false; }); @@ -151,7 +157,8 @@
-
$${'{:0,.2f}'.format(batch.po_total or 0)}
+ ## TODO: should not fall back to po_total +
$${'{:0,.2f}'.format(batch.po_total_calculated or batch.po_total or 0)}
@@ -270,7 +277,8 @@ ${h.end_form()} ${h.text('units_ordered_{}'.format(cost.uuid), value=int(cost._batchrow.units_ordered or 0) if cost._batchrow else None)} - ${'${:0,.2f}'.format(cost._batchrow.po_total or 0) if cost._batchrow else ''} + ## TODO: should not fall back to po_total + ${'${:0,.2f}'.format(cost._batchrow.po_total_calculated or cost._batchrow.po_total or 0) if cost._batchrow else ''} ${self.extra_td(cost)} % endfor diff --git a/tailbone/views/purchasing/ordering.py b/tailbone/views/purchasing/ordering.py index 9437b40d..68ec9f9d 100644 --- a/tailbone/views/purchasing/ordering.py +++ b/tailbone/views/purchasing/ordering.py @@ -43,7 +43,7 @@ from tailbone.views.purchasing import PurchasingBatchView class OrderingBatchView(PurchasingBatchView): """ - Master view for purchase order batches. + Master view for "ordering" batches. """ route_prefix = 'ordering' url_prefix = '/ordering' @@ -58,6 +58,33 @@ class OrderingBatchView(PurchasingBatchView): mobile_rows_deletable = True has_worksheet = True + labels = { + 'po_total_calculated': "PO Total", + } + + form_fields = [ + 'id', + 'store', + 'buyer', + 'vendor', + 'department', + 'purchase', + 'vendor_email', + 'vendor_fax', + 'vendor_contact', + 'vendor_phone', + 'date_ordered', + 'po_number', + 'po_total_calculated', + 'notes', + 'created', + 'created_by', + 'status_code', + 'complete', + 'executed', + 'executed_by', + ] + mobile_form_fields = [ 'vendor', 'department', @@ -73,6 +100,10 @@ class OrderingBatchView(PurchasingBatchView): 'executed_by', ] + row_labels = { + 'po_total_calculated': "PO Total", + } + row_grid_columns = [ 'sequence', 'upc', @@ -84,7 +115,7 @@ class OrderingBatchView(PurchasingBatchView): 'units_ordered', # 'cases_received', # 'units_received', - 'po_total', + 'po_total_calculated', # 'invoice_total', # 'credits', 'status_code', @@ -227,7 +258,24 @@ class OrderingBatchView(PurchasingBatchView): def worksheet_update(self): """ - Handles AJAX requests to update current batch, from Order Form view. + Handles AJAX requests to update the order quantities for some row + within the current batch, from the worksheet view. POST data should + include: + + * ``product_uuid`` + * ``cases_ordered`` + * ``units_ordered`` + + If a row already exists for the given product, it will be updated; + otherwise a new row is created for the product and then that is + updated. The handler's + :meth:`~rattail:rattail.batch.purchase.PurchaseBatchHandler.update_row_quantity()` + method is invoked to update the row. + + However, if both of the quantities given are empty, and a row exists + for the given product, then that row is removed from the batch, instead + of being updated. If a matching row is not found, it will not be + created. """ batch = self.get_instance() @@ -250,41 +298,43 @@ class OrderingBatchView(PurchasingBatchView): if not product: return {'error': "Product not found"} - row = None - rows = [r for r in batch.data_rows if r.product_uuid == uuid] - if rows: - assert len(rows) == 1 - row = rows[0] - if row.po_total and not row.removed: - batch.po_total -= row.po_total - if cases_ordered or units_ordered: - row.cases_ordered = cases_ordered or None - row.units_ordered = units_ordered or None - if row.removed: - row.removed = False - batch.rowcount += 1 - self.handler.refresh_row(row) - if row.po_unit_cost: - row.po_total = row.po_unit_cost * self.handler.get_units_ordered(row) - batch.po_total = (batch.po_total or 0) + row.po_total - else: - row.removed = True + # first we find out which existing row(s) match the given product + matches = [row for row in batch.active_rows() + if row.product_uuid == product.uuid] + if matches and len(matches) != 1: + raise RuntimeError("found too many ({}) matches for product {} in batch {}".format( + len(matches), product.uuid, batch.uuid)) - elif cases_ordered or units_ordered: - row = model.PurchaseBatchRow() - row.product = product - row.cases_ordered = cases_ordered or None - row.units_ordered = units_ordered or None - self.handler.add_row(batch, row) - if row.po_unit_cost: - row.po_total = row.po_unit_cost * self.handler.get_units_ordered(row) - batch.po_total = (batch.po_total or 0) + row.po_total + row = None + if cases_ordered or units_ordered: + + # make a new row if necessary + if matches: + row = matches[0] + else: + row = self.handler.make_row() + row.product = product + self.handler.add_row(batch, row) + + # update row quantities + self.handler.update_row_quantity(row, cases_ordered=cases_ordered, + units_ordered=units_ordered) + + else: # empty order quantities + + # remove row if present + if matches: + row = matches[0] + self.handler.do_remove_row(row) + row = None return { - 'row_cases_ordered': '' if not row or row.removed else int(row.cases_ordered or 0), - 'row_units_ordered': '' if not row or row.removed else int(row.units_ordered or 0), - 'row_po_total': '' if not row or row.removed else '${:0,.2f}'.format(row.po_total or 0), + 'row_cases_ordered': int(row.cases_ordered or 0) if row else None, + 'row_units_ordered': int(row.units_ordered or 0) if row else None, + 'row_po_total': '${:0,.2f}'.format(row.po_total or 0) if row else None, + 'row_po_total_calculated': '${:0,.2f}'.format(row.po_total_calculated or 0) if row else None, 'batch_po_total': '${:0,.2f}'.format(batch.po_total or 0), + 'batch_po_total_calculated': '${:0,.2f}'.format(batch.po_total_calculated or 0), } def render_mobile_listitem(self, batch, i):