tailbone/tailbone/templates/ordering/view.mako
2024-08-21 09:44:32 -05:00

419 lines
16 KiB
Mako

## -*- coding: utf-8; -*-
<%inherit file="/batch/view.mako" />
<%def name="extra_styles()">
${parent.extra_styles()}
${h.stylesheet_link(request.static_url('tailbone:static/css/purchases.css'))}
</%def>
<%def name="context_menu_items()">
${parent.context_menu_items()}
% if request.has_perm('{}.download_excel'.format(permission_prefix)):
<li>${h.link_to("Download {} as Excel".format(model_title), url('{}.download_excel'.format(route_prefix), uuid=batch.uuid))}</li>
% endif
</%def>
<%def name="render_row_grid_tools()">
${parent.render_row_grid_tools()}
% if not batch.executed and not batch.complete and master.has_perm('edit_row'):
<ordering-scanner numeric-only>
</ordering-scanner>
% endif
</%def>
<%def name="render_vue_templates()">
${parent.render_vue_templates()}
% if not batch.executed and not batch.complete and master.has_perm('edit_row'):
<script type="text/x-template" id="ordering-scanner-template">
<div>
<b-button type="is-primary"
icon-pack="fas"
icon-left="play"
@click="startScanning()">
Start Scanning
</b-button>
<b-modal :active.sync="showScanningDialog"
:can-cancel="false">
<div class="card">
<div class="card-content">
<section style="min-height: 400px;">
<div class="columns">
<div class="column">
<b-field grouped>
<numeric-input v-if="numericOnly"
v-model="itemEntry"
allow-enter
placeholder="Enter UPC"
icon-pack="fas"
icon="fas fa-search"
ref="itemEntryInput"
:disabled="currentRow"
@keydown.native="itemEntryKeydown">
</numeric-input>
<b-input v-if="!numericOnly"
v-model="itemEntry"
placeholder="Enter UPC"
icon-pack="fas"
icon="fas fa-search"
ref="itemEntryInput"
:disabled="currentRow">
</b-input>
<b-button @click="fetchEntry()"
:disabled="currentRow">
Fetch
</b-button>
</b-field>
<div v-if="currentRow">
<b-field grouped>
<b-field label="${enum.UNIT_OF_MEASURE[enum.UNIT_OF_MEASURE_CASE]}" horizontal>
<numeric-input v-model="currentRow.cases_ordered"
ref="casesInput"
@keydown.native="casesKeydown"
style="width: 60px; margin-right: 1rem;">
</numeric-input>
</b-field>
<b-field :label="currentRow.unit_of_measure_display" horizontal>
<numeric-input v-model="currentRow.units_ordered"
ref="unitsInput"
@keydown.native="unitsKeydown"
style="width: 60px;">
</numeric-input>
</b-field>
</b-field>
<p class="block has-text-weight-bold">
1 ${enum.UNIT_OF_MEASURE[enum.UNIT_OF_MEASURE_CASE]}
= {{ currentRow.case_quantity || '??' }}
{{ currentRow.unit_of_measure_display }}
</p>
<p class="block has-text-weight-bold">
{{ currentRow.po_case_cost_display || '$?.??' }}
per ${enum.UNIT_OF_MEASURE[enum.UNIT_OF_MEASURE_CASE]};
{{ currentRow.po_unit_cost_display || '$?.??' }}
per {{ currentRow.unit_of_measure_display }}
</p>
<p class="block has-text-weight-bold">
Total is
{{ totalCostDisplay }}
</p>
<div class="buttons">
<b-button type="is-primary"
icon-pack="fas"
icon-left="save"
@click="saveCurrentRow()">
Save
</b-button>
<b-button @click="cancelCurrentRow()">
Cancel
</b-button>
</div>
</div>
</div>
<div class="column is-three-fifths">
<div v-if="currentRow">
<b-field label="UPC" horizontal>
{{ currentRow.upc_display }}
</b-field>
<b-field label="Brand" horizontal>
{{ currentRow.brand_name }}
</b-field>
<b-field label="Description" horizontal>
{{ currentRow.description }}
</b-field>
<b-field label="Size" horizontal>
{{ currentRow.size }}
</b-field>
<b-field label="Reg. Price" horizontal>
{{ currentRow.product_price_display }}
</b-field>
<div class="buttons">
<img :src="currentRow.image_url"></img>
<b-button v-if="currentRow.product_url"
type="is-primary"
tag="a" :href="currentRow.product_url"
target="_blank">
View Full Product
</b-button>
</div>
</div>
</div>
</div> <!-- columns -->
</section>
<div class="level">
<div class="level-left">
</div>
<div class="level-right">
<div class="level-item buttons">
<once-button type="is-primary"
@click="stopScanning()"
text="Stop Scanning"
icon-left="stop"
:disabled="currentRow"
:title="currentRow ? 'Please save or cancel first' : null">
</once-button>
</div>
</div>
</div>
</div> <!-- card-content -->
</div>
</b-modal>
</div>
</script>
% endif
</%def>
<%def name="modify_vue_vars()">
${parent.modify_vue_vars()}
% if not batch.executed and not batch.complete and master.has_perm('edit_row'):
<script>
let OrderingScanner = {
template: '#ordering-scanner-template',
props: {
numericOnly: Boolean,
},
data() {
return {
showScanningDialog: false,
itemEntry: null,
fetching: false,
currentRow: null,
saving: false,
## TODO: should find a better way to handle CSRF token
csrftoken: ${json.dumps(h.get_csrf_token(request))|n},
}
},
computed: {
totalUnits() {
let cases = parseFloat(this.currentRow.cases_ordered || 0)
let units = parseFloat(this.currentRow.units_ordered || 0)
if (cases) {
units += cases * (this.currentRow.case_quantity || 1)
}
return units
},
totalUnitsDisplay() {
let cases = parseFloat(this.currentRow.cases_ordered || 0)
let units = parseFloat(this.currentRow.units_ordered || 0)
let casesTotal = ""
if (cases) {
casesTotal = cases.toString() + " ${enum.UNIT_OF_MEASURE[enum.UNIT_OF_MEASURE_CASE]}"
}
let unitsTotal = ""
if (units) {
unitsTotal = units.toString() + " " + this.currentRow.unit_of_measure_display
}
if (casesTotal.length && unitsTotal.length) {
return casesTotal + " + " + unitsTotal
} else if (casesTotal.length) {
return casesTotal
} else if (unitsTotal.length) {
return unitsTotal
}
return "??"
},
totalCost() {
if (this.currentRow.po_case_cost === null
&& this.currentRow.po_unit_cost === null) {
return null
}
let cases = parseFloat(this.currentRow.cases_ordered || 0)
let units = parseFloat(this.currentRow.units_ordered || 0)
let total = cases * this.currentRow.po_case_cost
total += units * this.currentRow.po_unit_cost
return total
},
totalCostDisplay() {
if (this.totalCost === null) {
return '$?.??'
}
return '$' + this.totalCost.toFixed(2)
},
},
methods: {
startScanning() {
this.showScanningDialog = true
this.$nextTick(() => {
this.$refs.itemEntryInput.focus()
})
},
itemEntryKeydown(event) {
if (event.which == 13) {
this.fetchEntry()
}
},
fetchEntry() {
if (this.fetching) {
return
}
if (!this.itemEntry) {
return
}
this.fetching = true
let url = '${url('{}.scanning_entry'.format(route_prefix), uuid=batch.uuid)}'
let params = {
entry: this.itemEntry,
}
let headers = {
## TODO: should find a better way to handle CSRF token
'X-CSRF-TOKEN': this.csrftoken,
}
## TODO: should find a better way to handle CSRF token
this.$http.post(url, params, {headers: headers}).then(({ data }) => {
if (data.error) {
this.$buefy.toast.open({
message: "Fetch failed: " + data.error,
type: 'is-danger',
duration: 4000, // 4 seconds
})
} else {
this.currentRow = data.row
this.$nextTick(() => {
this.$refs.casesInput.focus()
})
}
this.fetching = false
}, response => {
this.$buefy.toast.open({
message: "Fetch failed: (unknown error)",
type: 'is-danger',
duration: 4000, // 4 seconds
})
this.fetching = false
})
},
casesKeydown(event) {
if (event.which == 13) {
this.$refs.unitsInput.focus()
} else if (event.which == 27) {
this.cancelCurrentRow()
}
},
unitsKeydown(event) {
if (event.which == 13) {
this.saveCurrentRow()
} else if (event.which == 27) {
this.cancelCurrentRow()
}
},
saveCurrentRow() {
if (this.saving) {
return
}
this.saving = true
let url = '${url('{}.scanning_update'.format(route_prefix), uuid=batch.uuid)}'
let params = {
row_uuid: this.currentRow.uuid,
cases_ordered: this.currentRow.cases_ordered,
units_ordered: this.currentRow.units_ordered,
}
let headers = {
## TODO: should find a better way to handle CSRF token
'X-CSRF-TOKEN': this.csrftoken,
}
## TODO: should find a better way to handle CSRF token
this.$http.post(url, params, {headers: headers}).then(({ data }) => {
if (data.error) {
this.$buefy.toast.open({
message: "Save failed: " + data.error,
type: 'is-danger',
duration: 4000, // 4 seconds
})
} else {
this.$buefy.toast.open({
message: "Item was saved",
type: 'is-success',
})
this.itemEntry = null
this.currentRow = null
this.$nextTick(() => {
this.$refs.itemEntryInput.focus()
})
}
this.saving = false
}, response => {
this.$buefy.toast.open({
message: "Save failed: (unknown error)",
type: 'is-danger',
duration: 4000, // 4 seconds
})
this.saving = false
})
},
cancelCurrentRow() {
this.itemEntry = null
this.currentRow = null
this.$buefy.toast.open({
message: "Edit was cancelled",
type: 'is-warning',
})
this.$nextTick(() => {
this.$refs.itemEntryInput.focus()
})
},
stopScanning() {
location.reload()
},
}
}
</script>
% endif
</%def>
<%def name="make_vue_components()">
${parent.make_vue_components()}
% if not batch.executed and not batch.complete and master.has_perm('edit_row'):
<script>
Vue.component('ordering-scanner', OrderingScanner)
</script>
% endif
</%def>