From 340a177a29bc3d359b975a7362a749aaf687f13e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 13 Dec 2021 17:53:14 -0600 Subject: [PATCH] Overhaul desktop views for receiving, for efficiency still could use even more i'm sure, but this takes advantage of buefy to add dialogs etc. from the "view receiving batch row" page. this batch no longer allows direct edit of rows but that's hopefully for the better. --- tailbone/forms/core.py | 5 +- tailbone/templates/appsettings.mako | 6 +- tailbone/templates/formposter.mako | 12 +- tailbone/templates/grids/buefy.mako | 2 +- tailbone/templates/master/edit_row.mako | 4 +- tailbone/templates/master/view_row.mako | 2 +- tailbone/templates/page.mako | 1 + tailbone/templates/receiving/view.mako | 24 +- tailbone/templates/receiving/view_row.mako | 652 +++++++++++++++++--- tailbone/templates/themes/falafel/base.mako | 3 +- tailbone/views/batch/core.py | 25 +- tailbone/views/master.py | 12 +- tailbone/views/purchasing/batch.py | 83 ++- tailbone/views/purchasing/receiving.py | 340 ++++++++-- 14 files changed, 1014 insertions(+), 157 deletions(-) diff --git a/tailbone/forms/core.py b/tailbone/forms/core.py index 949222bc..f194e53e 100644 --- a/tailbone/forms/core.py +++ b/tailbone/forms/core.py @@ -848,7 +848,10 @@ class Form(object): return '' # TODO: fair bit of duplication here, should merge with deform.mako - label = HTML.tag('label', self.get_label(field_name), for_=field_name) + label = kwargs.get('label') + if not label: + label = self.get_label(field_name) + label = HTML.tag('label', label, for_=field_name) field = self.render_field_value(field_name) or '' field_div = HTML.tag('div', class_='field', c=[field]) contents = [label, field_div] diff --git a/tailbone/templates/appsettings.mako b/tailbone/templates/appsettings.mako index 79b2d952..dbe747bf 100644 --- a/tailbone/templates/appsettings.mako +++ b/tailbone/templates/appsettings.mako @@ -145,14 +145,14 @@
+ + {{ formButtonText }} - -
diff --git a/tailbone/templates/formposter.mako b/tailbone/templates/formposter.mako index 47c6ffd3..6fc6eadc 100644 --- a/tailbone/templates/formposter.mako +++ b/tailbone/templates/formposter.mako @@ -6,7 +6,7 @@ let FormPosterMixin = { methods: { - submitForm(action, params, success) { + submitForm(action, params, success, failure) { let csrftoken = ${json.dumps(request.session.get_csrf_token() or request.session.new_csrf_token())|n} @@ -21,18 +21,24 @@ } else { this.$buefy.toast.open({ - message: "Failed to send feedback: " + response.data.error, + message: "Submit failed: " + response.data.error, type: 'is-danger', duration: 4000, // 4 seconds }) + if (failure) { + failure(response) + } } }, response => { this.$buefy.toast.open({ - message: "Failed to submit form! (unknown server error)", + message: "Submit failed! (unknown server error)", type: 'is-danger', duration: 4000, // 4 seconds }) + if (failure) { + failure(response) + } }) }, }, diff --git a/tailbone/templates/grids/buefy.mako b/tailbone/templates/grids/buefy.mako index 08cb2969..70ce04f3 100644 --- a/tailbone/templates/grids/buefy.mako +++ b/tailbone/templates/grids/buefy.mako @@ -203,7 +203,7 @@ % for action in grid.main_actions + grid.more_actions: <%def name="context_menu_items()"> -
  • ${h.link_to("Back to {}".format(model_title), index_url)}
  • +
  • ${h.link_to("Back to {}".format(parent_model_title), parent_url)}
  • % if master.rows_viewable and request.has_perm('{}.view'.format(row_permission_prefix)):
  • ${h.link_to("View this {}".format(row_model_title), row_action_url('view', instance))}
  • % endif diff --git a/tailbone/templates/master/view_row.mako b/tailbone/templates/master/view_row.mako index 66756c3e..29a77497 100644 --- a/tailbone/templates/master/view_row.mako +++ b/tailbone/templates/master/view_row.mako @@ -12,7 +12,7 @@ % if master.rows_editable and instance_editable and request.has_perm('{}.edit'.format(permission_prefix)):
  • ${h.link_to("Edit this {}".format(model_title), action_url('edit', instance))}
  • % endif - % if master.rows_deletable and instance_deletable and request.has_perm('{}.delete'.format(permission_prefix)): + % if instance_deletable and master.has_perm('delete_row'):
  • ${h.link_to("Delete this {}".format(model_title), action_url('delete', instance))}
  • % endif % if rows_creatable and request.has_perm('{}.create'.format(permission_prefix)): diff --git a/tailbone/templates/page.mako b/tailbone/templates/page.mako index 2d8227d4..321e60d7 100644 --- a/tailbone/templates/page.mako +++ b/tailbone/templates/page.mako @@ -32,6 +32,7 @@ let ThisPage = { template: '#this-page-template', + mixins: [FormPosterMixin], computed: {}, methods: {}, } diff --git a/tailbone/templates/receiving/view.mako b/tailbone/templates/receiving/view.mako index 0fe3636a..01b93724 100644 --- a/tailbone/templates/receiving/view.mako +++ b/tailbone/templates/receiving/view.mako @@ -284,7 +284,19 @@ <%def name="object_helpers()"> - ${parent.object_helpers()} + ${self.render_status_breakdown()} + + % if use_buefy and master.handler.has_purchase_order(batch) and master.handler.has_invoice_file(batch): +
    +

    PO vs. Invoice

    +
    + ${po_vs_invoice_breakdown_grid.render_buefy_table_element(data_prop='poVsInvoiceBreakdownData', empty_labels=True)|n} +
    +
    + % endif + + ${self.render_execute_helper()} + % if master.has_perm('auto_receive') and master.can_auto_receive(batch):
    @@ -292,7 +304,9 @@
    % if use_buefy: + @click="autoReceiveShowDialog = true" + icon-pack="fas" + icon-left="check"> Auto-Receive All Items % else: @@ -334,7 +348,7 @@ :disabled="autoReceiveSubmitting" @click="autoReceiveSubmitting = true" icon-pack="fas" - icon-left="arrow-circle-right"> + icon-left="check"> {{ autoReceiveSubmitting ? "Working, please wait..." : "Auto-Receive All Items" }} ${h.end_form()} @@ -352,6 +366,10 @@ ThisPageData.autoReceiveShowDialog = false ThisPageData.autoReceiveSubmitting = false + % if po_vs_invoice_breakdown_grid is not Undefined: + ThisPageData.poVsInvoiceBreakdownData = ${json.dumps(po_vs_invoice_breakdown_grid.get_buefy_data()['data'])|n} + % endif + diff --git a/tailbone/templates/receiving/view_row.mako b/tailbone/templates/receiving/view_row.mako index d1c35c5b..bee71475 100644 --- a/tailbone/templates/receiving/view_row.mako +++ b/tailbone/templates/receiving/view_row.mako @@ -5,9 +5,37 @@ ${parent.extra_styles()} @@ -30,9 +58,20 @@ <%def name="page_content()"> % if use_buefy: - - ${form.render_field_readonly('sequence')} - ${form.render_field_readonly('status_code')} + + + + {{ rowData.sequence }} + + + + {{ rowData.status }} + + + + {{ rowData.invoice_total_calculated }} + +
    @@ -42,18 +81,23 @@
    - % if not row.product: + % if row.product: + ${form.render_field_readonly('upc')} + ${form.render_field_readonly('product')} + % else: ${form.render_field_readonly('item_entry')} + ${form.render_field_readonly('upc')} + ${form.render_field_readonly('brand_name')} + ${form.render_field_readonly('description')} + ${form.render_field_readonly('size')} % endif - ${form.render_field_readonly('upc')} - ${form.render_field_readonly('product')} ${form.render_field_readonly('vendor_code')} ${form.render_field_readonly('case_quantity')} ${form.render_field_readonly('catalog_unit_cost')}
    % if image_url:
    - ${h.image(image_url, "Product Image")} + ${h.image(image_url, "Product Image", width=150, height=150)}
    % endif
    @@ -64,88 +108,351 @@

    Quantities

    - ${form.render_field_readonly('ordered')} - ${form.render_field_readonly('shipped')} - ${form.render_field_readonly('received')} - ${form.render_field_readonly('damaged')} - ${form.render_field_readonly('expired')} - ${form.render_field_readonly('mispick')} +
    + + + {{ rowData.ordered }} + + +
    + + + {{ rowData.shipped }} + + +
    + + + {{ rowData.received }} + + + + {{ rowData.damaged }} + + + + {{ rowData.expired }} + + + + {{ rowData.mispick }} + + + + {{ rowData.missing }} + -
    - - - -
    -
    -
    - - -
    - -
    - - - -
    + + + + + + + + + + + + +
    + + % if master.batch_handler.has_purchase_order(batch): + + % endif + + % if master.batch_handler.has_invoice_file(batch): + + % endif + +
    + % else: ## legacy / not buefy ${parent.page_content()} @@ -164,6 +471,211 @@ alert("TODO: not yet implemented") } + ThisPageData.rowData = ${json.dumps(row_context)|n} + ThisPageData.possibleReceivingModes = ${json.dumps(possible_receiving_modes)|n} + ThisPageData.possibleCreditTypes = ${json.dumps(possible_credit_types)|n} + + ThisPageData.accountForProductShowDialog = false + ThisPageData.accountForProductMode = null + ThisPageData.accountForProductQuantity = null + ThisPageData.accountForProductUOM = 'units' + ThisPageData.accountForProductExpiration = null + ThisPageData.accountForProductSubmitting = false + + ThisPage.computed.accountForProductTotalUnits = function() { + return this.renderQuantity(this.accountForProductQuantity, + this.accountForProductUOM) + } + + ThisPage.computed.accountForProductSubmitDisabled = function() { + if (!this.accountForProductMode) { + return true + } + if (this.accountForProductMode == 'expired' && !this.accountForProductExpiration) { + return true + } + if (!this.accountForProductQuantity) { + return true + } + if (this.accountForProductSubmitting) { + return true + } + return false + } + + ThisPage.methods.accountForProductInit = function() { + this.accountForProductMode = 'received' + this.accountForProductExpiration = null + this.accountForProductQuantity = null + this.accountForProductUOM = 'units' + this.accountForProductShowDialog = true + } + + ThisPage.methods.accountForProductUOMClicked = function(uom) { + + // TODO: this does not seem to work as expected..even though + // the code appears to be correct + this.$nextTick(() => { + this.$refs.accountForProductQuantityInput.focus() + }) + } + + ThisPage.methods.accountForProductSubmit = function() { + + let qty = parseFloat(this.accountForProductQuantity) + if (qty == NaN || !qty) { + this.$buefy.toast.open({ + message: "You must enter a quantity.", + type: 'is-warning', + duration: 4000, // 4 seconds + }) + return + } + + if (this.accountForProductMode != 'received' && qty < 0) { + this.$buefy.toast.open({ + message: "Negative amounts are only allowed for the \"received\" state.", + type: 'is-warning', + duration: 4000, // 4 seconds + }) + return + } + + this.accountForProductSubmitting = true + let url = '${url('{}.receive_row'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid)}' + let params = { + mode: this.accountForProductMode, + quantity: {cases: null, units: null}, + expiration_date: this.accountForProductExpiration, + } + + if (this.accountForProductUOM == 'cases') { + params.quantity.cases = this.accountForProductQuantity + } else { + params.quantity.units = this.accountForProductQuantity + } + + this.submitForm(url, params, response => { + this.rowData = response.data.row + this.accountForProductSubmitting = false + this.accountForProductShowDialog = false + }, response => { + this.accountForProductSubmitting = false + }) + } + + ThisPageData.declareCreditShowDialog = false + ThisPageData.declareCreditType = null + ThisPageData.declareCreditExpiration = null + ThisPageData.declareCreditQuantity = null + ThisPageData.declareCreditUOM = 'units' + ThisPageData.declareCreditSubmitting = false + + ThisPage.methods.renderQuantity = function(qty, uom) { + qty = parseFloat(qty) + if (qty == NaN) { + return "n/a" + } + if (uom == 'cases') { + qty *= this.rowData.case_quantity + } + if (qty == NaN) { + return "n/a" + } + if (qty == 1) { + return "1 unit" + } + if (qty == -1) { + return "-1 unit" + } + if (Math.round(qty) == qty) { + return qty.toString() + " units" + } + return qty.toFixed(4) + " units" + } + + ThisPage.computed.declareCreditTotalUnits = function() { + return this.renderQuantity(this.declareCreditQuantity, + this.declareCreditUOM) + } + + ThisPage.computed.declareCreditSubmitDisabled = function() { + if (!this.declareCreditType) { + return true + } + if (this.declareCreditType == 'expired' && !this.declareCreditExpiration) { + return true + } + if (!this.declareCreditQuantity) { + return true + } + if (this.declareCreditSubmitting) { + return true + } + return false + } + + ThisPage.methods.declareCreditInit = function() { + this.declareCreditType = null + this.declareCreditExpiration = null + if (this.rowData.cases_received) { + this.declareCreditQuantity = this.rowData.cases_received + this.declareCreditUOM = 'cases' + } else { + this.declareCreditQuantity = this.rowData.units_received + this.declareCreditUOM = 'units' + } + this.declareCreditShowDialog = true + } + + ThisPage.methods.declareCreditSubmit = function() { + this.declareCreditSubmitting = true + let url = '${url('{}.declare_credit'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid)}' + let params = { + credit_type: this.declareCreditType, + cases: null, + units: null, + expiration_date: this.declareCreditExpiration, + } + + if (this.declareCreditUOM == 'cases') { + params.cases = this.declareCreditQuantity + } else { + params.units = this.declareCreditQuantity + } + + this.submitForm(url, params, response => { + this.rowData = response.data.row + this.declareCreditSubmitting = false + this.declareCreditShowDialog = false + }, response => { + this.declareCreditSubmitting = false + }) + } + + ThisPageData.removeCreditShowDialog = false + ThisPageData.removeCreditRow = {} + ThisPageData.removeCreditSubmitting = false + + ThisPage.methods.removeCreditInit = function(row) { + this.removeCreditRow = row + this.removeCreditShowDialog = true + } + + ThisPage.methods.removeCreditSubmit = function() { + this.removeCreditSubmitting = true + let url = '${url('{}.undeclare_credit'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid)}' + let params = { + uuid: this.removeCreditRow.uuid, + } + + this.submitForm(url, params, response => { + this.rowData = response.data.row + this.removeCreditSubmitting = false + this.removeCreditShowDialog = false + }) + } + diff --git a/tailbone/templates/themes/falafel/base.mako b/tailbone/templates/themes/falafel/base.mako index bf8f5ee7..2c2dd2ce 100644 --- a/tailbone/templates/themes/falafel/base.mako +++ b/tailbone/templates/themes/falafel/base.mako @@ -31,6 +31,8 @@ + ${declare_formposter_mixin()} + ${self.body()}
    @@ -517,7 +519,6 @@ <%def name="declare_whole_page_vars()"> - ${declare_formposter_mixin()} ${h.javascript_link(request.static_url('tailbone:static/themes/falafel/js/tailbone.feedback.js') + '?ver={}'.format(tailbone.__version__))}