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.
This commit is contained in:
parent
2f676774e9
commit
340a177a29
|
@ -848,7 +848,10 @@ class Form(object):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
# TODO: fair bit of duplication here, should merge with deform.mako
|
# 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 = self.render_field_value(field_name) or ''
|
||||||
field_div = HTML.tag('div', class_='field', c=[field])
|
field_div = HTML.tag('div', class_='field', c=[field])
|
||||||
contents = [label, field_div]
|
contents = [label, field_div]
|
||||||
|
|
|
@ -145,14 +145,14 @@
|
||||||
</div><!-- card -->
|
</div><!-- card -->
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
<once-button tag="a" href="${form.cancel_url}"
|
||||||
|
text="Cancel">
|
||||||
|
</once-button>
|
||||||
<b-button type="is-primary"
|
<b-button type="is-primary"
|
||||||
native-type="submit"
|
native-type="submit"
|
||||||
:disabled="formSubmitting">
|
:disabled="formSubmitting">
|
||||||
{{ formButtonText }}
|
{{ formButtonText }}
|
||||||
</b-button>
|
</b-button>
|
||||||
<once-button tag="a" href="${form.cancel_url}"
|
|
||||||
text="Cancel">
|
|
||||||
</once-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div><!-- app-wrapper -->
|
</div><!-- app-wrapper -->
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
let FormPosterMixin = {
|
let FormPosterMixin = {
|
||||||
methods: {
|
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}
|
let csrftoken = ${json.dumps(request.session.get_csrf_token() or request.session.new_csrf_token())|n}
|
||||||
|
|
||||||
|
@ -21,18 +21,24 @@
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.$buefy.toast.open({
|
this.$buefy.toast.open({
|
||||||
message: "Failed to send feedback: " + response.data.error,
|
message: "Submit failed: " + response.data.error,
|
||||||
type: 'is-danger',
|
type: 'is-danger',
|
||||||
duration: 4000, // 4 seconds
|
duration: 4000, // 4 seconds
|
||||||
})
|
})
|
||||||
|
if (failure) {
|
||||||
|
failure(response)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}, response => {
|
}, response => {
|
||||||
this.$buefy.toast.open({
|
this.$buefy.toast.open({
|
||||||
message: "Failed to submit form! (unknown server error)",
|
message: "Submit failed! (unknown server error)",
|
||||||
type: 'is-danger',
|
type: 'is-danger',
|
||||||
duration: 4000, // 4 seconds
|
duration: 4000, // 4 seconds
|
||||||
})
|
})
|
||||||
|
if (failure) {
|
||||||
|
failure(response)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -203,7 +203,7 @@
|
||||||
% for action in grid.main_actions + grid.more_actions:
|
% for action in grid.main_actions + grid.more_actions:
|
||||||
<a v-if="props.row._action_url_${action.key}"
|
<a v-if="props.row._action_url_${action.key}"
|
||||||
:href="props.row._action_url_${action.key}"
|
:href="props.row._action_url_${action.key}"
|
||||||
class="grid-action${' has-text-danger' if action.key == 'delete' else ''}"
|
class="grid-action${' has-text-danger' if action.key == 'delete' else ''} ${action.link_class or ''}"
|
||||||
% if action.click_handler:
|
% if action.click_handler:
|
||||||
@click.prevent="${action.click_handler}"
|
@click.prevent="${action.click_handler}"
|
||||||
% endif
|
% endif
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8; -*-
|
||||||
<%inherit file="/master/edit.mako" />
|
<%inherit file="/master/edit.mako" />
|
||||||
|
|
||||||
<%def name="context_menu_items()">
|
<%def name="context_menu_items()">
|
||||||
<li>${h.link_to("Back to {}".format(model_title), index_url)}</li>
|
<li>${h.link_to("Back to {}".format(parent_model_title), parent_url)}</li>
|
||||||
% if master.rows_viewable and request.has_perm('{}.view'.format(row_permission_prefix)):
|
% if master.rows_viewable and request.has_perm('{}.view'.format(row_permission_prefix)):
|
||||||
<li>${h.link_to("View this {}".format(row_model_title), row_action_url('view', instance))}</li>
|
<li>${h.link_to("View this {}".format(row_model_title), row_action_url('view', instance))}</li>
|
||||||
% endif
|
% endif
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
% if master.rows_editable and instance_editable and request.has_perm('{}.edit'.format(permission_prefix)):
|
% if master.rows_editable and instance_editable and request.has_perm('{}.edit'.format(permission_prefix)):
|
||||||
<li>${h.link_to("Edit this {}".format(model_title), action_url('edit', instance))}</li>
|
<li>${h.link_to("Edit this {}".format(model_title), action_url('edit', instance))}</li>
|
||||||
% endif
|
% 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'):
|
||||||
<li>${h.link_to("Delete this {}".format(model_title), action_url('delete', instance))}</li>
|
<li>${h.link_to("Delete this {}".format(model_title), action_url('delete', instance))}</li>
|
||||||
% endif
|
% endif
|
||||||
% if rows_creatable and request.has_perm('{}.create'.format(permission_prefix)):
|
% if rows_creatable and request.has_perm('{}.create'.format(permission_prefix)):
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
let ThisPage = {
|
let ThisPage = {
|
||||||
template: '#this-page-template',
|
template: '#this-page-template',
|
||||||
|
mixins: [FormPosterMixin],
|
||||||
computed: {},
|
computed: {},
|
||||||
methods: {},
|
methods: {},
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,7 +284,19 @@
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="object_helpers()">
|
<%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):
|
||||||
|
<div class="object-helper">
|
||||||
|
<h3>PO vs. Invoice</h3>
|
||||||
|
<div class="object-helper-content">
|
||||||
|
${po_vs_invoice_breakdown_grid.render_buefy_table_element(data_prop='poVsInvoiceBreakdownData', empty_labels=True)|n}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
${self.render_execute_helper()}
|
||||||
|
|
||||||
% if master.has_perm('auto_receive') and master.can_auto_receive(batch):
|
% if master.has_perm('auto_receive') and master.can_auto_receive(batch):
|
||||||
|
|
||||||
<div class="object-helper">
|
<div class="object-helper">
|
||||||
|
@ -292,7 +304,9 @@
|
||||||
<div class="object-helper-content">
|
<div class="object-helper-content">
|
||||||
% if use_buefy:
|
% if use_buefy:
|
||||||
<b-button type="is-primary"
|
<b-button type="is-primary"
|
||||||
@click="autoReceiveShowDialog = true">
|
@click="autoReceiveShowDialog = true"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="check">
|
||||||
Auto-Receive All Items
|
Auto-Receive All Items
|
||||||
</b-button>
|
</b-button>
|
||||||
% else:
|
% else:
|
||||||
|
@ -334,7 +348,7 @@
|
||||||
:disabled="autoReceiveSubmitting"
|
:disabled="autoReceiveSubmitting"
|
||||||
@click="autoReceiveSubmitting = true"
|
@click="autoReceiveSubmitting = true"
|
||||||
icon-pack="fas"
|
icon-pack="fas"
|
||||||
icon-left="arrow-circle-right">
|
icon-left="check">
|
||||||
{{ autoReceiveSubmitting ? "Working, please wait..." : "Auto-Receive All Items" }}
|
{{ autoReceiveSubmitting ? "Working, please wait..." : "Auto-Receive All Items" }}
|
||||||
</b-button>
|
</b-button>
|
||||||
${h.end_form()}
|
${h.end_form()}
|
||||||
|
@ -352,6 +366,10 @@
|
||||||
ThisPageData.autoReceiveShowDialog = false
|
ThisPageData.autoReceiveShowDialog = false
|
||||||
ThisPageData.autoReceiveSubmitting = 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
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,37 @@
|
||||||
${parent.extra_styles()}
|
${parent.extra_styles()}
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
% if use_buefy:
|
% if use_buefy:
|
||||||
|
|
||||||
nav.panel {
|
nav.panel {
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-fields {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fields .field.is-horizontal {
|
||||||
|
margin-left: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fields .field.is-horizontal .field-label .label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-form-fields {
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-form-fields .field.is-horizontal .field-label .label {
|
||||||
|
text-align: left;
|
||||||
|
width: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-credit .field.is-horizontal .field-label .label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
% endif
|
% endif
|
||||||
</style>
|
</style>
|
||||||
</%def>
|
</%def>
|
||||||
|
@ -30,9 +58,20 @@
|
||||||
<%def name="page_content()">
|
<%def name="page_content()">
|
||||||
% if use_buefy:
|
% if use_buefy:
|
||||||
|
|
||||||
<b-field grouped>
|
<b-field grouped class="header-fields">
|
||||||
${form.render_field_readonly('sequence')}
|
|
||||||
${form.render_field_readonly('status_code')}
|
<b-field label="Sequence" horizontal>
|
||||||
|
{{ rowData.sequence }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Status" horizontal>
|
||||||
|
{{ rowData.status }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Calculated Total" horizontal>
|
||||||
|
{{ rowData.invoice_total_calculated }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
|
@ -42,18 +81,23 @@
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
<div>
|
<div>
|
||||||
% if not row.product:
|
% if row.product:
|
||||||
${form.render_field_readonly('item_entry')}
|
|
||||||
% endif
|
|
||||||
${form.render_field_readonly('upc')}
|
${form.render_field_readonly('upc')}
|
||||||
${form.render_field_readonly('product')}
|
${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('vendor_code')}
|
${form.render_field_readonly('vendor_code')}
|
||||||
${form.render_field_readonly('case_quantity')}
|
${form.render_field_readonly('case_quantity')}
|
||||||
${form.render_field_readonly('catalog_unit_cost')}
|
${form.render_field_readonly('catalog_unit_cost')}
|
||||||
</div>
|
</div>
|
||||||
% if image_url:
|
% if image_url:
|
||||||
<div class="is-pulled-right">
|
<div class="is-pulled-right">
|
||||||
${h.image(image_url, "Product Image")}
|
${h.image(image_url, "Product Image", width=150, height=150)}
|
||||||
</div>
|
</div>
|
||||||
% endif
|
% endif
|
||||||
</div>
|
</div>
|
||||||
|
@ -64,88 +108,351 @@
|
||||||
<p class="panel-heading">Quantities</p>
|
<p class="panel-heading">Quantities</p>
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
<div>
|
<div>
|
||||||
${form.render_field_readonly('ordered')}
|
<div class="quantity-form-fields">
|
||||||
${form.render_field_readonly('shipped')}
|
|
||||||
${form.render_field_readonly('received')}
|
<b-field label="Ordered" horizontal>
|
||||||
${form.render_field_readonly('damaged')}
|
{{ rowData.ordered }}
|
||||||
${form.render_field_readonly('expired')}
|
</b-field>
|
||||||
${form.render_field_readonly('mispick')}
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<b-field label="Shipped" horizontal>
|
||||||
|
{{ rowData.shipped }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<b-field label="Received" horizontal
|
||||||
|
v-if="rowData.received">
|
||||||
|
{{ rowData.received }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Damaged" horizontal
|
||||||
|
v-if="rowData.damaged">
|
||||||
|
{{ rowData.damaged }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Expired" horizontal
|
||||||
|
v-if="rowData.expired">
|
||||||
|
{{ rowData.expired }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Mispick" horizontal
|
||||||
|
v-if="rowData.mispick">
|
||||||
|
{{ rowData.mispick }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Missing" horizontal
|
||||||
|
v-if="rowData.missing">
|
||||||
|
{{ rowData.missing }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
<div class="buttons">
|
|
||||||
<once-button type="is-primary"
|
|
||||||
tag="a" href="${url('{}.receive_row'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid)}"
|
|
||||||
icon-left="download"
|
|
||||||
text="Receive Product">
|
|
||||||
</once-button>
|
|
||||||
<once-button type="is-primary"
|
|
||||||
tag="a" href="${url('{}.declare_credit'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid)}"
|
|
||||||
icon-left="thumbs-down"
|
|
||||||
text="Declare Credit">
|
|
||||||
</once-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
% if master.has_perm('edit_row') and master.row_editable(row):
|
||||||
|
<div class="buttons">
|
||||||
|
<b-button type="is-primary"
|
||||||
|
@click="accountForProductInit()"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="check">
|
||||||
|
Account for Product
|
||||||
|
</b-button>
|
||||||
|
<b-button type="is-warning"
|
||||||
|
@click="declareCreditInit()"
|
||||||
|
:disabled="!rowData.received"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="thumbs-down">
|
||||||
|
Declare Credit
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<b-modal has-modal-card
|
||||||
|
:active.sync="accountForProductShowDialog">
|
||||||
|
<div class="modal-card">
|
||||||
|
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Account for Product</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="modal-card-body">
|
||||||
|
|
||||||
|
<p class="block">
|
||||||
|
This is for declaring that you have encountered some
|
||||||
|
amount of the product. Ideally you will just
|
||||||
|
"receive" it normally, but you can indicate a "credit"
|
||||||
|
state if there is something amiss.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field label="Case Qty.">
|
||||||
|
<span class="control">
|
||||||
|
{{ rowData.case_quantity }}
|
||||||
|
</span>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<span class="control">
|
||||||
|
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<b-field label="Product State"
|
||||||
|
:type="accountForProductMode ? null : 'is-danger'">
|
||||||
|
<b-select v-model="accountForProductMode">
|
||||||
|
<option v-for="mode in possibleReceivingModes"
|
||||||
|
:key="mode"
|
||||||
|
:value="mode">
|
||||||
|
{{ mode }}
|
||||||
|
</option>
|
||||||
|
</b-select>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Expiration Date"
|
||||||
|
v-show="accountForProductMode == 'expired'"
|
||||||
|
:type="accountForProductExpiration ? null : 'is-danger'">
|
||||||
|
<tailbone-datepicker v-model="accountForProductExpiration">
|
||||||
|
</tailbone-datepicker>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<div class="level">
|
||||||
|
<div class="level-left">
|
||||||
|
|
||||||
|
<div class="level-item">
|
||||||
|
<b-input v-model="accountForProductQuantity"
|
||||||
|
type="number" step="0.0001"
|
||||||
|
ref="accountForProductQuantityInput">
|
||||||
|
</b-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="level-item">
|
||||||
|
<b-field>
|
||||||
|
<b-radio-button v-model="accountForProductUOM"
|
||||||
|
@click.native="accountForProductUOMClicked('units')"
|
||||||
|
native-value="units">
|
||||||
|
Units
|
||||||
|
</b-radio-button>
|
||||||
|
<b-radio-button v-model="accountForProductUOM"
|
||||||
|
@click.native="accountForProductUOMClicked('cases')"
|
||||||
|
native-value="cases">
|
||||||
|
Cases
|
||||||
|
</b-radio-button>
|
||||||
|
</b-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="level-item"
|
||||||
|
v-if="accountForProductUOM == 'cases' && accountForProductQuantity">
|
||||||
|
= {{ accountForProductTotalUnits }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<b-button @click="accountForProductShowDialog = false">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
<b-button type="is-primary"
|
||||||
|
@click="accountForProductSubmit()"
|
||||||
|
:disabled="accountForProductSubmitDisabled"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="check">
|
||||||
|
{{ accountForProductSubmitting ? "Working, please wait..." : "Account for Product" }}
|
||||||
|
</b-button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
|
||||||
|
<b-modal has-modal-card
|
||||||
|
:active.sync="declareCreditShowDialog">
|
||||||
|
<div class="modal-card">
|
||||||
|
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Declare Credit</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="modal-card-body">
|
||||||
|
|
||||||
|
<p class="block">
|
||||||
|
This is for <span class="is-italic">converting</span>
|
||||||
|
some amount you <span class="is-italic">already
|
||||||
|
received</span>, and now declaring there is something
|
||||||
|
wrong with it.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field label="Received">
|
||||||
|
<span class="control">
|
||||||
|
{{ rowData.received }}
|
||||||
|
</span>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<span class="control">
|
||||||
|
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<b-field label="Credit Type"
|
||||||
|
:type="declareCreditType ? null : 'is-danger'">
|
||||||
|
<b-select v-model="declareCreditType">
|
||||||
|
<option v-for="typ in possibleCreditTypes"
|
||||||
|
:key="typ"
|
||||||
|
:value="typ">
|
||||||
|
{{ typ }}
|
||||||
|
</option>
|
||||||
|
</b-select>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Expiration Date"
|
||||||
|
v-show="declareCreditType == 'expired'"
|
||||||
|
:type="declareCreditExpiration ? null : 'is-danger'">
|
||||||
|
<tailbone-datepicker v-model="declareCreditExpiration">
|
||||||
|
</tailbone-datepicker>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<div class="level">
|
||||||
|
<div class="level-left">
|
||||||
|
|
||||||
|
<div class="level-item">
|
||||||
|
<b-input v-model="declareCreditQuantity"
|
||||||
|
type="number" step="0.0001"
|
||||||
|
ref="declareCreditQuantityInput">
|
||||||
|
</b-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="level-item">
|
||||||
|
<b-field>
|
||||||
|
<b-radio-button v-model="declareCreditUOM"
|
||||||
|
@click.native="declareCreditUOMClicked('units')"
|
||||||
|
native-value="units">
|
||||||
|
Units
|
||||||
|
</b-radio-button>
|
||||||
|
<b-radio-button v-model="declareCreditUOM"
|
||||||
|
@click.native="declareCreditUOMClicked('cases')"
|
||||||
|
native-value="cases">
|
||||||
|
Cases
|
||||||
|
</b-radio-button>
|
||||||
|
</b-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="level-item"
|
||||||
|
v-if="declareCreditUOM == 'cases' && declareCreditQuantity">
|
||||||
|
= {{ declareCreditTotalUnits }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<b-button @click="declareCreditShowDialog = false">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
<b-button type="is-warning"
|
||||||
|
@click="declareCreditSubmit()"
|
||||||
|
:disabled="declareCreditSubmitDisabled"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="thumbs-down">
|
||||||
|
{{ declareCreditSubmitting ? "Working, please wait..." : "Declare this Credit" }}
|
||||||
|
</b-button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
|
||||||
|
<nav class="panel" >
|
||||||
|
<p class="panel-heading">Credits</p>
|
||||||
|
<div class="panel-block">
|
||||||
|
<div>
|
||||||
|
${form.render_field_value('credits')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<b-modal has-modal-card
|
||||||
|
:active.sync="removeCreditShowDialog">
|
||||||
|
<div class="modal-card remove-credit">
|
||||||
|
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Un-Declare Credit</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="modal-card-body">
|
||||||
|
|
||||||
|
<p class="block">
|
||||||
|
If you un-declare this credit, the quantity below will
|
||||||
|
be added back to the
|
||||||
|
<span class="has-text-weight-bold">Received</span> tally.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<b-field label="Credit Type" horizontal>
|
||||||
|
{{ removeCreditRow.credit_type }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Quantity" horizontal>
|
||||||
|
{{ removeCreditRow.shorted }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<b-button @click="removeCreditShowDialog = false">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
<b-button type="is-danger"
|
||||||
|
@click="removeCreditSubmit()"
|
||||||
|
:disabled="removeCreditSubmitting"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="trash">
|
||||||
|
{{ removeCreditSubmitting ? "Working, please wait..." : "Un-Declare this Credit" }}
|
||||||
|
</b-button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
|
|
||||||
|
% if master.batch_handler.has_purchase_order(batch):
|
||||||
<nav class="panel" >
|
<nav class="panel" >
|
||||||
<p class="panel-heading">Purchase Order</p>
|
<p class="panel-heading">Purchase Order</p>
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
<div>
|
<div>
|
||||||
${form.render_field_readonly('po_line_number')}
|
${form.render_field_readonly('po_line_number')}
|
||||||
${form.render_field_readonly('po_unit_cost')}
|
${form.render_field_readonly('po_unit_cost')}
|
||||||
|
${form.render_field_readonly('po_case_size')}
|
||||||
${form.render_field_readonly('po_total')}
|
${form.render_field_readonly('po_total')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if master.batch_handler.has_invoice_file(batch):
|
||||||
<nav class="panel" >
|
<nav class="panel" >
|
||||||
<p class="panel-heading">Invoice</p>
|
<p class="panel-heading">Invoice</p>
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
<div>
|
<div>
|
||||||
${form.render_field_readonly('invoice_line_number')}
|
${form.render_field_readonly('invoice_line_number')}
|
||||||
${form.render_field_readonly('invoice_unit_cost')}
|
${form.render_field_readonly('invoice_unit_cost')}
|
||||||
% if master.has_perm('edit_row'):
|
${form.render_field_readonly('invoice_case_size')}
|
||||||
<div class="is-pulled-right">
|
${form.render_field_readonly('invoice_total', label="Invoice Total")}
|
||||||
<once-button type="is-primary"
|
|
||||||
tag="a" href="${master.get_row_action_url('edit', row)}"
|
|
||||||
## @click="editUnitCost()"
|
|
||||||
## icon-pack="fas"
|
|
||||||
icon-left="edit"
|
|
||||||
text="Edit Unit Cost">
|
|
||||||
</once-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
% endif
|
% endif
|
||||||
${form.render_field_readonly('invoice_cost_confirmed')}
|
|
||||||
<div class="is-pulled-right">
|
|
||||||
<b-button type="is-primary"
|
|
||||||
@click="confirmUnitCost()"
|
|
||||||
icon-pack="fas"
|
|
||||||
icon-left="check">
|
|
||||||
Confirm Unit Cost
|
|
||||||
</b-button>
|
|
||||||
</div>
|
|
||||||
${form.render_field_readonly('invoice_total')}
|
|
||||||
${form.render_field_readonly('invoice_total_calculated')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="panel" >
|
|
||||||
<p class="panel-heading">Credits</p>
|
|
||||||
<div class="panel-block">
|
|
||||||
<div>
|
|
||||||
${form.render_field_readonly('credits')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
% else:
|
% else:
|
||||||
## legacy / not buefy
|
## legacy / not buefy
|
||||||
${parent.page_content()}
|
${parent.page_content()}
|
||||||
|
@ -164,6 +471,211 @@
|
||||||
alert("TODO: not yet implemented")
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
${declare_formposter_mixin()}
|
||||||
|
|
||||||
${self.body()}
|
${self.body()}
|
||||||
|
|
||||||
<div id="whole-page-app">
|
<div id="whole-page-app">
|
||||||
|
@ -517,7 +519,6 @@
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="declare_whole_page_vars()">
|
<%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__))}
|
${h.javascript_link(request.static_url('tailbone:static/themes/falafel/js/tailbone.feedback.js') + '?ver={}'.format(tailbone.__version__))}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,9 @@ class BatchMasterView(MasterView):
|
||||||
|
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
super(BatchMasterView, self).__init__(request)
|
super(BatchMasterView, self).__init__(request)
|
||||||
self.handler = self.get_handler()
|
self.batch_handler = self.get_handler()
|
||||||
|
# TODO: deprecate / remove this (?)
|
||||||
|
self.handler = self.batch_handler
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_handler_factory(cls, rattail_config):
|
def get_handler_factory(cls, rattail_config):
|
||||||
|
@ -1149,19 +1151,28 @@ class BatchMasterView(MasterView):
|
||||||
"""
|
"""
|
||||||
Batch rows are editable only until batch is complete or executed.
|
Batch rows are editable only until batch is complete or executed.
|
||||||
"""
|
"""
|
||||||
|
if not (self.rows_editable or self.rows_editable_but_not_directly):
|
||||||
|
return False
|
||||||
|
|
||||||
batch = self.get_parent(row)
|
batch = self.get_parent(row)
|
||||||
return self.rows_editable and not batch.executed and not batch.complete
|
if batch.complete or batch.executed:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def row_deletable(self, row):
|
def row_deletable(self, row):
|
||||||
"""
|
"""
|
||||||
Batch rows are deletable only until batch is complete or executed.
|
Batch rows are deletable only until batch is complete or executed.
|
||||||
"""
|
"""
|
||||||
if self.rows_deletable:
|
if not self.rows_deletable:
|
||||||
batch = self.get_parent(row)
|
|
||||||
if not batch.executed and not batch.complete:
|
|
||||||
return True
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
batch = self.get_parent(row)
|
||||||
|
if batch.complete or batch.executed:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def template_kwargs_view_row(self, **kwargs):
|
def template_kwargs_view_row(self, **kwargs):
|
||||||
kwargs['batch_model_title'] = kwargs['parent_model_title']
|
kwargs['batch_model_title'] = kwargs['parent_model_title']
|
||||||
# TODO: should these be set somewhere else?
|
# TODO: should these be set somewhere else?
|
||||||
|
|
|
@ -166,6 +166,7 @@ class MasterView(View):
|
||||||
rows_viewable = True
|
rows_viewable = True
|
||||||
rows_creatable = False
|
rows_creatable = False
|
||||||
rows_editable = False
|
rows_editable = False
|
||||||
|
rows_editable_but_not_directly = False
|
||||||
rows_deletable = False
|
rows_deletable = False
|
||||||
rows_deletable_speedbump = True
|
rows_deletable_speedbump = True
|
||||||
rows_bulk_deletable = False
|
rows_bulk_deletable = False
|
||||||
|
@ -3852,6 +3853,7 @@ class MasterView(View):
|
||||||
return self.render_to_response('edit_row', {
|
return self.render_to_response('edit_row', {
|
||||||
'instance': row,
|
'instance': row,
|
||||||
'row_parent': parent,
|
'row_parent': parent,
|
||||||
|
'parent_model_title': self.get_model_title(),
|
||||||
'parent_title': self.get_instance_title(parent),
|
'parent_title': self.get_instance_title(parent),
|
||||||
'parent_url': self.get_action_url('view', parent),
|
'parent_url': self.get_action_url('view', parent),
|
||||||
'parent_instance': parent,
|
'parent_instance': parent,
|
||||||
|
@ -3884,6 +3886,8 @@ class MasterView(View):
|
||||||
considered "deletable". Returns ``True`` by default; override as
|
considered "deletable". Returns ``True`` by default; override as
|
||||||
necessary.
|
necessary.
|
||||||
"""
|
"""
|
||||||
|
if not self.rows_deletable:
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def delete_row_object(self, row):
|
def delete_row_object(self, row):
|
||||||
|
@ -4099,6 +4103,7 @@ class MasterView(View):
|
||||||
config_title = cls.get_config_title()
|
config_title = cls.get_config_title()
|
||||||
if cls.has_rows:
|
if cls.has_rows:
|
||||||
row_model_title = cls.get_row_model_title()
|
row_model_title = cls.get_row_model_title()
|
||||||
|
row_model_title_plural = cls.get_row_model_title_plural()
|
||||||
|
|
||||||
config.add_tailbone_permission_group(permission_prefix, model_title_plural, overwrite=False)
|
config.add_tailbone_permission_group(permission_prefix, model_title_plural, overwrite=False)
|
||||||
|
|
||||||
|
@ -4386,9 +4391,10 @@ class MasterView(View):
|
||||||
|
|
||||||
# edit row
|
# edit row
|
||||||
if cls.has_rows:
|
if cls.has_rows:
|
||||||
if cls.rows_editable:
|
if cls.rows_editable or cls.rows_editable_but_not_directly:
|
||||||
config.add_tailbone_permission(permission_prefix, '{}.edit_row'.format(permission_prefix),
|
config.add_tailbone_permission(permission_prefix, '{}.edit_row'.format(permission_prefix),
|
||||||
"Edit individual {} rows".format(model_title))
|
"Edit individual {}".format(row_model_title_plural))
|
||||||
|
if cls.rows_editable:
|
||||||
config.add_route('{}.edit_row'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/edit'.format(url_prefix))
|
config.add_route('{}.edit_row'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/edit'.format(url_prefix))
|
||||||
config.add_view(cls, attr='edit_row', route_name='{}.edit_row'.format(route_prefix),
|
config.add_view(cls, attr='edit_row', route_name='{}.edit_row'.format(route_prefix),
|
||||||
permission='{}.edit_row'.format(permission_prefix))
|
permission='{}.edit_row'.format(permission_prefix))
|
||||||
|
@ -4397,7 +4403,7 @@ class MasterView(View):
|
||||||
if cls.has_rows:
|
if cls.has_rows:
|
||||||
if cls.rows_deletable:
|
if cls.rows_deletable:
|
||||||
config.add_tailbone_permission(permission_prefix, '{}.delete_row'.format(permission_prefix),
|
config.add_tailbone_permission(permission_prefix, '{}.delete_row'.format(permission_prefix),
|
||||||
"Delete individual {} rows".format(model_title))
|
"Delete individual {}".format(row_model_title_plural))
|
||||||
config.add_route('{}.delete_row'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/delete'.format(url_prefix))
|
config.add_route('{}.delete_row'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/delete'.format(url_prefix))
|
||||||
config.add_view(cls, attr='delete_row', route_name='{}.delete_row'.format(route_prefix),
|
config.add_view(cls, attr='delete_row', route_name='{}.delete_row'.format(route_prefix),
|
||||||
permission='{}.delete_row'.format(permission_prefix))
|
permission='{}.delete_row'.format(permission_prefix))
|
||||||
|
|
|
@ -99,8 +99,10 @@ class PurchasingBatchView(BatchMasterView):
|
||||||
'upc': "UPC",
|
'upc': "UPC",
|
||||||
'item_id': "Item ID",
|
'item_id': "Item ID",
|
||||||
'brand_name': "Brand",
|
'brand_name': "Brand",
|
||||||
|
'case_quantity': "Case Size",
|
||||||
'po_line_number': "PO Line Number",
|
'po_line_number': "PO Line Number",
|
||||||
'po_unit_cost': "PO Unit Cost",
|
'po_unit_cost': "PO Unit Cost",
|
||||||
|
'po_case_size': "PO Case Size",
|
||||||
'po_total': "PO Total",
|
'po_total': "PO Total",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +146,9 @@ class PurchasingBatchView(BatchMasterView):
|
||||||
'mispick',
|
'mispick',
|
||||||
'cases_mispick',
|
'cases_mispick',
|
||||||
'units_mispick',
|
'units_mispick',
|
||||||
|
'missing',
|
||||||
|
'cases_missing',
|
||||||
|
'units_missing',
|
||||||
'po_line_number',
|
'po_line_number',
|
||||||
'po_unit_cost',
|
'po_unit_cost',
|
||||||
'po_total',
|
'po_total',
|
||||||
|
@ -710,8 +715,11 @@ class PurchasingBatchView(BatchMasterView):
|
||||||
f.set_renderer('damaged', self.render_row_quantity)
|
f.set_renderer('damaged', self.render_row_quantity)
|
||||||
f.set_renderer('expired', self.render_row_quantity)
|
f.set_renderer('expired', self.render_row_quantity)
|
||||||
f.set_renderer('mispick', self.render_row_quantity)
|
f.set_renderer('mispick', self.render_row_quantity)
|
||||||
|
f.set_renderer('missing', self.render_row_quantity)
|
||||||
|
|
||||||
f.set_type('case_quantity', 'quantity')
|
f.set_type('case_quantity', 'quantity')
|
||||||
|
f.set_type('po_case_size', 'quantity')
|
||||||
|
f.set_type('invoice_case_size', 'quantity')
|
||||||
f.set_type('cases_ordered', 'quantity')
|
f.set_type('cases_ordered', 'quantity')
|
||||||
f.set_type('units_ordered', 'quantity')
|
f.set_type('units_ordered', 'quantity')
|
||||||
f.set_type('cases_shipped', 'quantity')
|
f.set_type('cases_shipped', 'quantity')
|
||||||
|
@ -724,6 +732,8 @@ class PurchasingBatchView(BatchMasterView):
|
||||||
f.set_type('units_expired', 'quantity')
|
f.set_type('units_expired', 'quantity')
|
||||||
f.set_type('cases_mispick', 'quantity')
|
f.set_type('cases_mispick', 'quantity')
|
||||||
f.set_type('units_mispick', 'quantity')
|
f.set_type('units_mispick', 'quantity')
|
||||||
|
f.set_type('cases_missing', 'quantity')
|
||||||
|
f.set_type('units_missing', 'quantity')
|
||||||
|
|
||||||
# currency fields
|
# currency fields
|
||||||
# nb. we only show "total" fields as currency, but not case or
|
# nb. we only show "total" fields as currency, but not case or
|
||||||
|
@ -746,6 +756,7 @@ class PurchasingBatchView(BatchMasterView):
|
||||||
|
|
||||||
# credits
|
# credits
|
||||||
f.set_readonly('credits')
|
f.set_readonly('credits')
|
||||||
|
if self.viewing:
|
||||||
f.set_renderer('credits', self.render_row_credits)
|
f.set_renderer('credits', self.render_row_credits)
|
||||||
|
|
||||||
if self.creating:
|
if self.creating:
|
||||||
|
@ -786,35 +797,57 @@ class PurchasingBatchView(BatchMasterView):
|
||||||
app = self.get_rattail_app()
|
app = self.get_rattail_app()
|
||||||
cases = getattr(row, 'cases_{}'.format(field))
|
cases = getattr(row, 'cases_{}'.format(field))
|
||||||
units = getattr(row, 'units_{}'.format(field))
|
units = getattr(row, 'units_{}'.format(field))
|
||||||
if cases and units:
|
return app.render_cases_units(cases, units)
|
||||||
return "{} cases + {} units".format(app.render_quantity(cases),
|
|
||||||
app.render_quantity(units))
|
|
||||||
if cases and not units:
|
|
||||||
return "{} cases".format(app.render_quantity(cases))
|
|
||||||
if units and not cases:
|
|
||||||
return "{} units".format(app.render_quantity(units))
|
|
||||||
|
|
||||||
def render_row_credits(self, row, field):
|
|
||||||
if not row.credits:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
def make_row_credits_grid(self, row):
|
||||||
|
use_buefy = self.get_use_buefy()
|
||||||
route_prefix = self.get_route_prefix()
|
route_prefix = self.get_route_prefix()
|
||||||
columns = [
|
factory = self.get_grid_factory()
|
||||||
'credit_type',
|
|
||||||
'cases_shorted',
|
g = factory(
|
||||||
'units_shorted',
|
|
||||||
'credit_total',
|
|
||||||
]
|
|
||||||
g = grids.Grid(
|
|
||||||
key='{}.row_credits'.format(route_prefix),
|
key='{}.row_credits'.format(route_prefix),
|
||||||
data=row.credits,
|
data=[] if use_buefy else row.credits,
|
||||||
columns=columns,
|
columns=[
|
||||||
labels={'credit_type': "Type",
|
'credit_type',
|
||||||
|
# 'cases_shorted',
|
||||||
|
# 'units_shorted',
|
||||||
|
'shorted',
|
||||||
|
'credit_total',
|
||||||
|
'expiration_date',
|
||||||
|
# 'mispick_upc',
|
||||||
|
# 'mispick_brand_name',
|
||||||
|
# 'mispick_description',
|
||||||
|
# 'mispick_size',
|
||||||
|
],
|
||||||
|
labels={
|
||||||
|
'credit_type': "Type",
|
||||||
'cases_shorted': "Cases",
|
'cases_shorted': "Cases",
|
||||||
'units_shorted': "Units"})
|
'units_shorted': "Units",
|
||||||
|
'shorted': "Quantity",
|
||||||
|
'credit_total': "Total",
|
||||||
|
'mispick_upc': "Mispick UPC",
|
||||||
|
'mispick_brand_name': "MP Brand",
|
||||||
|
'mispick_description': "MP Description",
|
||||||
|
'mispick_size': "MP Size",
|
||||||
|
})
|
||||||
|
|
||||||
g.set_type('cases_shorted', 'quantity')
|
g.set_type('cases_shorted', 'quantity')
|
||||||
g.set_type('units_shorted', 'quantity')
|
g.set_type('units_shorted', 'quantity')
|
||||||
g.set_type('credit_total', 'currency')
|
g.set_type('credit_total', 'currency')
|
||||||
|
|
||||||
|
return g
|
||||||
|
|
||||||
|
def render_row_credits(self, row, field):
|
||||||
|
use_buefy = self.get_use_buefy()
|
||||||
|
if not use_buefy and not row.credits:
|
||||||
|
return
|
||||||
|
|
||||||
|
g = self.make_row_credits_grid(row)
|
||||||
|
|
||||||
|
if use_buefy:
|
||||||
|
return HTML.literal(
|
||||||
|
g.render_buefy_table_element(data_prop='rowData.credits'))
|
||||||
|
else:
|
||||||
return HTML.literal(g.render_grid())
|
return HTML.literal(g.render_grid())
|
||||||
|
|
||||||
# def item_lookup(self, value, field=None):
|
# def item_lookup(self, value, field=None):
|
||||||
|
|
|
@ -51,6 +51,21 @@ from tailbone.views.purchasing import PurchasingBatchView
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
POSSIBLE_RECEIVING_MODES = [
|
||||||
|
'received',
|
||||||
|
'damaged',
|
||||||
|
'expired',
|
||||||
|
# 'mispick',
|
||||||
|
'missing',
|
||||||
|
]
|
||||||
|
|
||||||
|
POSSIBLE_CREDIT_TYPES = [
|
||||||
|
'damaged',
|
||||||
|
'expired',
|
||||||
|
# 'mispick',
|
||||||
|
'missing',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ReceivingBatchView(PurchasingBatchView):
|
class ReceivingBatchView(PurchasingBatchView):
|
||||||
"""
|
"""
|
||||||
|
@ -63,7 +78,9 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
index_title = "Receiving"
|
index_title = "Receiving"
|
||||||
downloadable = True
|
downloadable = True
|
||||||
bulk_deletable = True
|
bulk_deletable = True
|
||||||
rows_editable = True
|
rows_editable = False
|
||||||
|
rows_editable_but_not_directly = True
|
||||||
|
rows_deletable = True
|
||||||
|
|
||||||
default_uom_is_case = True
|
default_uom_is_case = True
|
||||||
|
|
||||||
|
@ -181,13 +198,18 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
'mispick',
|
'mispick',
|
||||||
'cases_mispick',
|
'cases_mispick',
|
||||||
'units_mispick',
|
'units_mispick',
|
||||||
|
'missing',
|
||||||
|
'cases_missing',
|
||||||
|
'units_missing',
|
||||||
'catalog_unit_cost',
|
'catalog_unit_cost',
|
||||||
'po_line_number',
|
'po_line_number',
|
||||||
'po_unit_cost',
|
'po_unit_cost',
|
||||||
|
'po_case_size',
|
||||||
'po_total',
|
'po_total',
|
||||||
'invoice_line_number',
|
'invoice_line_number',
|
||||||
'invoice_unit_cost',
|
'invoice_unit_cost',
|
||||||
'invoice_cost_confirmed',
|
'invoice_cost_confirmed',
|
||||||
|
'invoice_case_size',
|
||||||
'invoice_total',
|
'invoice_total',
|
||||||
'invoice_total_calculated',
|
'invoice_total_calculated',
|
||||||
'status_code',
|
'status_code',
|
||||||
|
@ -322,17 +344,14 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
return self.render_to_response('create', context)
|
return self.render_to_response('create', context)
|
||||||
|
|
||||||
def row_deletable(self, row):
|
def row_deletable(self, row):
|
||||||
|
|
||||||
|
# first run it through the normal logic, if that doesn't like
|
||||||
|
# it then we won't either
|
||||||
|
if not super(ReceivingBatchView, self).row_deletable(row):
|
||||||
|
return False
|
||||||
|
|
||||||
batch = row.batch
|
batch = row.batch
|
||||||
|
|
||||||
# don't allow if master view has disabled that entirely
|
|
||||||
if not self.rows_deletable:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# can never delete rows for complete/executed batches
|
|
||||||
# TODO: not so sure about the 'complete' part though..?
|
|
||||||
if batch.executed or batch.complete:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# can always delete rows from truck dump parent
|
# can always delete rows from truck dump parent
|
||||||
if batch.is_truck_dump_parent():
|
if batch.is_truck_dump_parent():
|
||||||
return True
|
return True
|
||||||
|
@ -362,7 +381,7 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
super(ReceivingBatchView, self).configure_form(f)
|
super(ReceivingBatchView, self).configure_form(f)
|
||||||
model = self.model
|
model = self.model
|
||||||
batch = f.model_instance
|
batch = f.model_instance
|
||||||
allow_truck_dump = self.handler.allow_truck_dump_receiving()
|
allow_truck_dump = self.batch_handler.allow_truck_dump_receiving()
|
||||||
workflow = self.request.matchdict.get('workflow_key')
|
workflow = self.request.matchdict.get('workflow_key')
|
||||||
route_prefix = self.get_route_prefix()
|
route_prefix = self.get_route_prefix()
|
||||||
use_buefy = self.get_use_buefy()
|
use_buefy = self.get_use_buefy()
|
||||||
|
@ -472,9 +491,9 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
and self.purchase_order_fieldname == 'purchase'):
|
and self.purchase_order_fieldname == 'purchase'):
|
||||||
if use_buefy:
|
if use_buefy:
|
||||||
f.replace('purchase', 'purchase_uuid')
|
f.replace('purchase', 'purchase_uuid')
|
||||||
purchases = self.handler.get_eligible_purchases(
|
purchases = self.batch_handler.get_eligible_purchases(
|
||||||
vendor, self.enum.PURCHASE_BATCH_MODE_RECEIVING)
|
vendor, self.enum.PURCHASE_BATCH_MODE_RECEIVING)
|
||||||
values = [(p.uuid, self.handler.render_eligible_purchase(p))
|
values = [(p.uuid, self.batch_handler.render_eligible_purchase(p))
|
||||||
for p in purchases]
|
for p in purchases]
|
||||||
f.set_widget('purchase_uuid', dfwidget.SelectWidget(values=values))
|
f.set_widget('purchase_uuid', dfwidget.SelectWidget(values=values))
|
||||||
f.set_label('purchase_uuid', "Purchase Order")
|
f.set_label('purchase_uuid', "Purchase Order")
|
||||||
|
@ -497,12 +516,11 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
f.remove('invoice_total_calculated')
|
f.remove('invoice_total_calculated')
|
||||||
|
|
||||||
# hide all invoice fields if batch does not have invoice file
|
# hide all invoice fields if batch does not have invoice file
|
||||||
if not self.creating and not self.handler.has_invoice_file(batch):
|
if not self.creating and not self.batch_handler.has_invoice_file(batch):
|
||||||
f.remove('invoice_file',
|
f.remove('invoice_file',
|
||||||
'invoice_date',
|
'invoice_date',
|
||||||
'invoice_number',
|
'invoice_number',
|
||||||
'invoice_total',
|
'invoice_total')
|
||||||
'invoice_total_calculated')
|
|
||||||
|
|
||||||
# receiving_complete
|
# receiving_complete
|
||||||
if self.creating:
|
if self.creating:
|
||||||
|
@ -517,9 +535,12 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
'invoice_parser_key')
|
'invoice_parser_key')
|
||||||
|
|
||||||
elif workflow == 'from_invoice':
|
elif workflow == 'from_invoice':
|
||||||
f.remove('truck_dump_batch_uuid')
|
|
||||||
f.set_required('invoice_file')
|
f.set_required('invoice_file')
|
||||||
f.set_required('invoice_parser_key')
|
f.set_required('invoice_parser_key')
|
||||||
|
f.remove('truck_dump_batch_uuid',
|
||||||
|
'po_number',
|
||||||
|
'invoice_date',
|
||||||
|
'invoice_number')
|
||||||
|
|
||||||
elif workflow == 'from_po':
|
elif workflow == 'from_po':
|
||||||
f.remove('truck_dump_batch_uuid',
|
f.remove('truck_dump_batch_uuid',
|
||||||
|
@ -531,9 +552,13 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
'invoice_number')
|
'invoice_number')
|
||||||
|
|
||||||
elif workflow == 'from_po_with_invoice':
|
elif workflow == 'from_po_with_invoice':
|
||||||
f.remove('truck_dump_batch_uuid')
|
|
||||||
f.set_required('invoice_file')
|
f.set_required('invoice_file')
|
||||||
f.set_required('invoice_parser_key')
|
f.set_required('invoice_parser_key')
|
||||||
|
f.remove('truck_dump_batch_uuid',
|
||||||
|
'date_ordered',
|
||||||
|
'po_number',
|
||||||
|
'invoice_date',
|
||||||
|
'invoice_number')
|
||||||
|
|
||||||
elif workflow == 'truck_dump_children_first':
|
elif workflow == 'truck_dump_children_first':
|
||||||
f.remove('truck_dump_batch_uuid',
|
f.remove('truck_dump_batch_uuid',
|
||||||
|
@ -614,16 +639,92 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
def make_po_vs_invoice_breakdown(self, batch):
|
||||||
|
"""
|
||||||
|
Returns a simple breakdown as list of 2-tuples, each of which
|
||||||
|
has the display title as first member, and number of rows as
|
||||||
|
second member.
|
||||||
|
"""
|
||||||
|
grouped = {}
|
||||||
|
labels = OrderedDict([
|
||||||
|
('both', "Found in both PO and Invoice"),
|
||||||
|
('po_not_invoice', "Found in PO but not Invoice"),
|
||||||
|
('invoice_not_po', "Found in Invoice but not PO"),
|
||||||
|
('neither', "Not found in PO nor Invoice"),
|
||||||
|
])
|
||||||
|
|
||||||
|
for row in batch.active_rows():
|
||||||
|
if row.po_line_number and not row.invoice_line_number:
|
||||||
|
grouped.setdefault('po_not_invoice', []).append(row)
|
||||||
|
elif row.invoice_line_number and not row.po_line_number:
|
||||||
|
grouped.setdefault('invoice_not_po', []).append(row)
|
||||||
|
elif row.po_line_number and row.invoice_line_number:
|
||||||
|
grouped.setdefault('both', []).append(row)
|
||||||
|
else:
|
||||||
|
grouped.setdefault('neither', []).append(row)
|
||||||
|
|
||||||
|
breakdown = []
|
||||||
|
|
||||||
|
for key, label in labels.items():
|
||||||
|
if key in grouped:
|
||||||
|
breakdown.append({
|
||||||
|
'title': label,
|
||||||
|
'count': len(grouped[key]),
|
||||||
|
})
|
||||||
|
|
||||||
|
return breakdown
|
||||||
|
|
||||||
|
def template_kwargs_view(self, **kwargs):
|
||||||
|
kwargs = super(ReceivingBatchView, self).template_kwargs_view(**kwargs)
|
||||||
|
batch = kwargs['instance']
|
||||||
|
|
||||||
|
if self.handler.has_purchase_order(batch) and self.handler.has_invoice_file(batch):
|
||||||
|
breakdown = self.make_po_vs_invoice_breakdown(batch)
|
||||||
|
|
||||||
|
factory = self.get_grid_factory()
|
||||||
|
kwargs['po_vs_invoice_breakdown_grid'] = factory(
|
||||||
|
'batch_po_vs_invoice_breakdown',
|
||||||
|
data=breakdown,
|
||||||
|
columns=['title', 'count'])
|
||||||
|
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_context_credits(self, row):
|
||||||
|
app = self.get_rattail_app()
|
||||||
|
credits_data = []
|
||||||
|
for credit in row.credits:
|
||||||
|
credits_data.append({
|
||||||
|
'uuid': credit.uuid,
|
||||||
|
'credit_type': credit.credit_type,
|
||||||
|
'expiration_date': six.text_type(credit.expiration_date) if credit.expiration_date else None,
|
||||||
|
'cases_shorted': app.render_quantity(credit.cases_shorted),
|
||||||
|
'units_shorted': app.render_quantity(credit.units_shorted),
|
||||||
|
'shorted': app.render_cases_units(credit.cases_shorted,
|
||||||
|
credit.units_shorted),
|
||||||
|
'credit_total': app.render_currency(credit.credit_total),
|
||||||
|
'mispick_upc': '-',
|
||||||
|
'mispick_brand_name': '-',
|
||||||
|
'mispick_description': '-',
|
||||||
|
'mispick_size': '-',
|
||||||
|
})
|
||||||
|
return credits_data
|
||||||
|
|
||||||
def template_kwargs_view_row(self, **kwargs):
|
def template_kwargs_view_row(self, **kwargs):
|
||||||
kwargs = super(ReceivingBatchView, self).template_kwargs_view_row(**kwargs)
|
kwargs = super(ReceivingBatchView, self).template_kwargs_view_row(**kwargs)
|
||||||
|
use_buefy = self.get_use_buefy()
|
||||||
app = self.get_rattail_app()
|
app = self.get_rattail_app()
|
||||||
handler = app.get_products_handler()
|
products_handler = app.get_products_handler()
|
||||||
row = kwargs['instance']
|
row = kwargs['instance']
|
||||||
|
|
||||||
if row.product:
|
if row.product:
|
||||||
kwargs['image_url'] = handler.get_image_url(row.product)
|
kwargs['image_url'] = products_handler.get_image_url(row.product)
|
||||||
elif row.upc:
|
elif row.upc:
|
||||||
kwargs['image_url'] = handler.get_image_url(upc=row.upc)
|
kwargs['image_url'] = products_handler.get_image_url(upc=row.upc)
|
||||||
|
|
||||||
|
if use_buefy:
|
||||||
|
kwargs['row_context'] = self.get_context_row(row)
|
||||||
|
kwargs['possible_receiving_modes'] = POSSIBLE_RECEIVING_MODES
|
||||||
|
kwargs['possible_credit_types'] = POSSIBLE_CREDIT_TYPES
|
||||||
|
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
@ -849,6 +950,24 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
if row.product and row.product.is_pack_item():
|
if row.product and row.product.is_pack_item():
|
||||||
return self.get_row_action_url('transform_unit', row)
|
return self.get_row_action_url('transform_unit', row)
|
||||||
|
|
||||||
|
def make_row_credits_grid(self, row):
|
||||||
|
|
||||||
|
# first make grid like normal
|
||||||
|
g = super(ReceivingBatchView, self).make_row_credits_grid(row)
|
||||||
|
|
||||||
|
if (self.get_use_buefy()
|
||||||
|
and self.has_perm('edit_row')
|
||||||
|
and self.row_editable(row)):
|
||||||
|
|
||||||
|
# add the Un-Declare action
|
||||||
|
g.main_actions.append(self.make_action(
|
||||||
|
'remove', label="Un-Declare",
|
||||||
|
url='#', icon='trash',
|
||||||
|
link_class='has-text-danger',
|
||||||
|
click_handler='removeCreditInit(props.row)'))
|
||||||
|
|
||||||
|
return g
|
||||||
|
|
||||||
def vuejs_convert_quantity(self, cstruct):
|
def vuejs_convert_quantity(self, cstruct):
|
||||||
result = dict(cstruct)
|
result = dict(cstruct)
|
||||||
if result['cases'] is colander.null:
|
if result['cases'] is colander.null:
|
||||||
|
@ -872,6 +991,55 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
self.viewing = True
|
self.viewing = True
|
||||||
use_buefy = self.get_use_buefy()
|
use_buefy = self.get_use_buefy()
|
||||||
row = self.get_row_instance()
|
row = self.get_row_instance()
|
||||||
|
|
||||||
|
# things are a bit different now w/ buefy support..
|
||||||
|
if use_buefy:
|
||||||
|
|
||||||
|
# don't even bother showing this page if that's all the
|
||||||
|
# request was about
|
||||||
|
if self.request.method == 'GET':
|
||||||
|
return self.redirect(self.get_row_action_url('view', row))
|
||||||
|
|
||||||
|
# make sure edit is allowed
|
||||||
|
if not (self.has_perm('edit_row') and self.row_editable(row)):
|
||||||
|
raise self.forbidden()
|
||||||
|
|
||||||
|
# check for JSON POST, which is submitted via AJAX from
|
||||||
|
# the "view row" page
|
||||||
|
if self.request.method == 'POST' and not self.request.POST:
|
||||||
|
data = self.request.json_body
|
||||||
|
kwargs = dict(data)
|
||||||
|
|
||||||
|
# TODO: for some reason quantities can come through as strings?
|
||||||
|
cases = kwargs['quantity']['cases']
|
||||||
|
if cases is not None:
|
||||||
|
if cases == '':
|
||||||
|
cases = None
|
||||||
|
else:
|
||||||
|
cases = decimal.Decimal(cases)
|
||||||
|
kwargs['cases'] = cases
|
||||||
|
units = kwargs['quantity']['units']
|
||||||
|
if units is not None:
|
||||||
|
if units == '':
|
||||||
|
units = None
|
||||||
|
else:
|
||||||
|
units = decimal.Decimal(units)
|
||||||
|
kwargs['units'] = units
|
||||||
|
del kwargs['quantity']
|
||||||
|
|
||||||
|
# handler takes care of the receiving logic for us
|
||||||
|
try:
|
||||||
|
self.batch_handler.receive_row(row, **kwargs)
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
return self.json_response({'error': six.text_type(error)})
|
||||||
|
|
||||||
|
self.Session.flush()
|
||||||
|
self.Session.refresh(row)
|
||||||
|
return self.json_response({
|
||||||
|
'ok': True,
|
||||||
|
'row': self.get_context_row(row)})
|
||||||
|
|
||||||
batch = row.batch
|
batch = row.batch
|
||||||
permission_prefix = self.get_permission_prefix()
|
permission_prefix = self.get_permission_prefix()
|
||||||
possible_modes = [
|
possible_modes = [
|
||||||
|
@ -1024,11 +1192,59 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
"""
|
"""
|
||||||
use_buefy = self.get_use_buefy()
|
use_buefy = self.get_use_buefy()
|
||||||
row = self.get_row_instance()
|
row = self.get_row_instance()
|
||||||
|
|
||||||
|
# things are a bit different now w/ buefy support..
|
||||||
|
if use_buefy:
|
||||||
|
|
||||||
|
# don't even bother showing this page if that's all the
|
||||||
|
# request was about
|
||||||
|
if self.request.method == 'GET':
|
||||||
|
return self.redirect(self.get_row_action_url('view', row))
|
||||||
|
|
||||||
|
# make sure edit is allowed
|
||||||
|
if not (self.has_perm('edit_row') and self.row_editable(row)):
|
||||||
|
raise self.forbidden()
|
||||||
|
|
||||||
|
# check for JSON POST, which is submitted via AJAX from
|
||||||
|
# the "view row" page
|
||||||
|
if self.request.method == 'POST' and not self.request.POST:
|
||||||
|
data = self.request.json_body
|
||||||
|
kwargs = dict(data)
|
||||||
|
|
||||||
|
# TODO: for some reason quantities can come through as strings?
|
||||||
|
if kwargs['cases'] is not None:
|
||||||
|
if kwargs['cases'] == '':
|
||||||
|
kwargs['cases'] = None
|
||||||
|
else:
|
||||||
|
kwargs['cases'] = decimal.Decimal(kwargs['cases'])
|
||||||
|
if kwargs['units'] is not None:
|
||||||
|
if kwargs['units'] == '':
|
||||||
|
kwargs['units'] = None
|
||||||
|
else:
|
||||||
|
kwargs['units'] = decimal.Decimal(kwargs['units'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = self.handler.can_declare_credit(row, **kwargs)
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
return self.json_response({'error': six.text_type(error)})
|
||||||
|
|
||||||
|
else:
|
||||||
|
if result:
|
||||||
|
self.handler.declare_credit(row, **kwargs)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return self.json_response({
|
||||||
|
'error': "Handler says you can't declare that credit; "
|
||||||
|
"not sure why"})
|
||||||
|
|
||||||
|
self.Session.flush()
|
||||||
|
self.Session.refresh(row)
|
||||||
|
return self.json_response({
|
||||||
|
'ok': True,
|
||||||
|
'row': self.get_context_row(row)})
|
||||||
|
|
||||||
batch = row.batch
|
batch = row.batch
|
||||||
possible_credit_types = [
|
|
||||||
'damaged',
|
|
||||||
'expired',
|
|
||||||
]
|
|
||||||
context = {
|
context = {
|
||||||
'row': row,
|
'row': row,
|
||||||
'batch': batch,
|
'batch': batch,
|
||||||
|
@ -1044,9 +1260,10 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
schema = DeclareCreditForm()
|
schema = DeclareCreditForm()
|
||||||
form = forms.Form(schema=schema, request=self.request,
|
form = forms.Form(schema=schema, request=self.request,
|
||||||
use_buefy=use_buefy)
|
use_buefy=use_buefy)
|
||||||
|
form.cancel_url = self.get_row_action_url('view', row)
|
||||||
|
|
||||||
# credit_type
|
# credit_type
|
||||||
values = [(m, m) for m in possible_credit_types]
|
values = [(m, m) for m in POSSIBLE_CREDIT_TYPES]
|
||||||
if use_buefy:
|
if use_buefy:
|
||||||
widget = dfwidget.SelectWidget(values=values)
|
widget = dfwidget.SelectWidget(values=values)
|
||||||
else:
|
else:
|
||||||
|
@ -1085,6 +1302,54 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
context['parent_title'] = self.get_instance_title(batch)
|
context['parent_title'] = self.get_instance_title(batch)
|
||||||
return self.render_to_response('declare_credit', context)
|
return self.render_to_response('declare_credit', context)
|
||||||
|
|
||||||
|
def undeclare_credit(self):
|
||||||
|
"""
|
||||||
|
View for un-declaring a credit, i.e. moving the credit amounts
|
||||||
|
back into the "received" tally.
|
||||||
|
"""
|
||||||
|
model = self.model
|
||||||
|
row = self.get_row_instance()
|
||||||
|
data = self.request.json_body
|
||||||
|
|
||||||
|
# make sure edit is allowed
|
||||||
|
if not (self.has_perm('edit_row') and self.row_editable(row)):
|
||||||
|
raise self.forbidden()
|
||||||
|
|
||||||
|
# figure out which credit to un-declare
|
||||||
|
credit = None
|
||||||
|
uuid = data.get('uuid')
|
||||||
|
if uuid:
|
||||||
|
credit = self.Session.query(model.PurchaseBatchCredit).get(uuid)
|
||||||
|
if not credit:
|
||||||
|
return {'error': "Credit not found"}
|
||||||
|
|
||||||
|
# un-declare it
|
||||||
|
self.batch_handler.undeclare_credit(row, credit)
|
||||||
|
self.Session.flush()
|
||||||
|
self.Session.refresh(row)
|
||||||
|
|
||||||
|
return {'ok': True,
|
||||||
|
'row': self.get_context_row(row)}
|
||||||
|
|
||||||
|
def get_context_row(self, row):
|
||||||
|
app = self.get_rattail_app()
|
||||||
|
return {
|
||||||
|
'sequence': row.sequence,
|
||||||
|
'case_quantity': float(row.case_quantity) if row.case_quantity is not None else None,
|
||||||
|
'ordered': self.render_row_quantity(row, 'ordered'),
|
||||||
|
'shipped': self.render_row_quantity(row, 'shipped'),
|
||||||
|
'received': self.render_row_quantity(row, 'received'),
|
||||||
|
'cases_received': float(row.cases_received) if row.cases_received is not None else None,
|
||||||
|
'units_received': float(row.units_received) if row.units_received is not None else None,
|
||||||
|
'damaged': self.render_row_quantity(row, 'damaged'),
|
||||||
|
'expired': self.render_row_quantity(row, 'expired'),
|
||||||
|
'mispick': self.render_row_quantity(row, 'mispick'),
|
||||||
|
'missing': self.render_row_quantity(row, 'missing'),
|
||||||
|
'credits': self.get_context_credits(row),
|
||||||
|
'invoice_total_calculated': app.render_currency(row.invoice_total_calculated),
|
||||||
|
'status': row.STATUS[row.status_code],
|
||||||
|
}
|
||||||
|
|
||||||
def transform_unit_row(self):
|
def transform_unit_row(self):
|
||||||
"""
|
"""
|
||||||
View which transforms the given row, which is assumed to associate with
|
View which transforms the given row, which is assumed to associate with
|
||||||
|
@ -1593,6 +1858,14 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
config.add_view(cls, attr='declare_credit', route_name='{}.declare_credit'.format(route_prefix),
|
config.add_view(cls, attr='declare_credit', route_name='{}.declare_credit'.format(route_prefix),
|
||||||
permission='{}.edit_row'.format(permission_prefix))
|
permission='{}.edit_row'.format(permission_prefix))
|
||||||
|
|
||||||
|
# un-declare credit
|
||||||
|
config.add_route('{}.undeclare_credit'.format(route_prefix),
|
||||||
|
'{}/rows/{{row_uuid}}/undeclare-credit'.format(instance_url_prefix))
|
||||||
|
config.add_view(cls, attr='undeclare_credit',
|
||||||
|
route_name='{}.undeclare_credit'.format(route_prefix),
|
||||||
|
permission='{}.edit_row'.format(permission_prefix),
|
||||||
|
renderer='json')
|
||||||
|
|
||||||
# update row cost
|
# update row cost
|
||||||
config.add_route('{}.update_row_cost'.format(route_prefix), '{}/update-row-cost'.format(instance_url_prefix))
|
config.add_route('{}.update_row_cost'.format(route_prefix), '{}/update-row-cost'.format(instance_url_prefix))
|
||||||
config.add_view(cls, attr='update_row_cost', route_name='{}.update_row_cost'.format(route_prefix),
|
config.add_view(cls, attr='update_row_cost', route_name='{}.update_row_cost'.format(route_prefix),
|
||||||
|
@ -1649,12 +1922,8 @@ class NewReceivingBatch(colander.Schema):
|
||||||
class ReceiveRowForm(colander.MappingSchema):
|
class ReceiveRowForm(colander.MappingSchema):
|
||||||
|
|
||||||
mode = colander.SchemaNode(colander.String(),
|
mode = colander.SchemaNode(colander.String(),
|
||||||
validator=colander.OneOf([
|
validator=colander.OneOf(
|
||||||
'received',
|
POSSIBLE_RECEIVING_MODES))
|
||||||
'damaged',
|
|
||||||
'expired',
|
|
||||||
# 'mispick',
|
|
||||||
]))
|
|
||||||
|
|
||||||
quantity = forms.types.ProductQuantity()
|
quantity = forms.types.ProductQuantity()
|
||||||
|
|
||||||
|
@ -1677,11 +1946,8 @@ class ReceiveRowForm(colander.MappingSchema):
|
||||||
class DeclareCreditForm(colander.MappingSchema):
|
class DeclareCreditForm(colander.MappingSchema):
|
||||||
|
|
||||||
credit_type = colander.SchemaNode(colander.String(),
|
credit_type = colander.SchemaNode(colander.String(),
|
||||||
validator=colander.OneOf([
|
validator=colander.OneOf(
|
||||||
'damaged',
|
POSSIBLE_CREDIT_TYPES))
|
||||||
'expired',
|
|
||||||
# 'mispick',
|
|
||||||
]))
|
|
||||||
|
|
||||||
quantity = forms.types.ProductQuantity()
|
quantity = forms.types.ProductQuantity()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue