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__))}