Add basic "pending product" support for new custorder batch
This commit is contained in:
parent
408bffb775
commit
c0db03bc28
72
tailbone/templates/custorders/configure.mako
Normal file
72
tailbone/templates/custorders/configure.mako
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/configure.mako" />
|
||||||
|
|
||||||
|
<%def name="form_content()">
|
||||||
|
|
||||||
|
<h3 class="block is-size-3">Customer Handling</h3>
|
||||||
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
|
<b-field message="If not set, only a Person is required.">
|
||||||
|
<b-checkbox name="rattail.custorders.new_order_requires_customer"
|
||||||
|
v-model="simpleSettings['rattail.custorders.new_order_requires_customer']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
Require a Customer account
|
||||||
|
</b-checkbox>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field message="If not set, default contact info is always assumed.">
|
||||||
|
<b-checkbox name="rattail.custorders.new_orders.allow_contact_info_choice"
|
||||||
|
v-model="simpleSettings['rattail.custorders.new_orders.allow_contact_info_choice']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
Allow user to choose contact info
|
||||||
|
</b-checkbox>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field message="Only applies if user is allowed to choose contact info.">
|
||||||
|
<b-checkbox name="rattail.custorders.new_orders.allow_contact_info_create"
|
||||||
|
v-model="simpleSettings['rattail.custorders.new_orders.allow_contact_info_create']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
Allow user to enter new contact info
|
||||||
|
</b-checkbox>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<p class="block">
|
||||||
|
If you allow users to enter new contact info, the default action
|
||||||
|
when the order is submitted, is to send email with details of
|
||||||
|
the new contact info. Settings for these are at:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul class="list">
|
||||||
|
<li class="list-item">
|
||||||
|
${h.link_to("New Phone Request", url('emailprofiles.view', key='new_phone_requested'))}
|
||||||
|
</li>
|
||||||
|
<li class="list-item">
|
||||||
|
${h.link_to("New Email Request", url('emailprofiles.view', key='new_email_requested'))}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="block is-size-3">Product Handling</h3>
|
||||||
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
|
<b-field message="If set, user can enter details of an arbitrary new "pending" product.">
|
||||||
|
<b-checkbox name="rattail.custorders.allow_unknown_product"
|
||||||
|
v-model="simpleSettings['rattail.custorders.allow_unknown_product']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
Allow creating orders for "unknown" products
|
||||||
|
</b-checkbox>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field>
|
||||||
|
<b-checkbox name="rattail.custorders.product_price_may_be_questionable"
|
||||||
|
v-model="simpleSettings['rattail.custorders.product_price_may_be_questionable']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
Allow prices to be flagged as "questionable"
|
||||||
|
</b-checkbox>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -12,6 +12,18 @@
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_instance_header_buttons()">
|
||||||
|
${parent.render_instance_header_buttons()}
|
||||||
|
% if use_buefy and master.configurable and master.has_perm('configure'):
|
||||||
|
<div class="level-item">
|
||||||
|
<once-button tag="a" href="${url('{}.configure'.format(route_prefix))}"
|
||||||
|
icon-left="cog"
|
||||||
|
text="Configure">
|
||||||
|
</once-button>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
<%def name="page_content()">
|
<%def name="page_content()">
|
||||||
<br />
|
<br />
|
||||||
% if use_buefy:
|
% if use_buefy:
|
||||||
|
@ -155,11 +167,11 @@
|
||||||
<div class="level-left">
|
<div class="level-left">
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<div v-if="orderPhoneNumber">
|
<div v-if="orderPhoneNumber">
|
||||||
<p>
|
<p :class="addOtherPhoneNumber ? 'has-text-success': null">
|
||||||
{{ orderPhoneNumber }}
|
{{ orderPhoneNumber }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="addOtherPhoneNumber"
|
<p v-if="addOtherPhoneNumber"
|
||||||
class="is-size-7 is-italic">
|
class="is-size-7 is-italic has-text-success">
|
||||||
will be added to customer record
|
will be added to customer record
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -170,7 +182,7 @@
|
||||||
</div>
|
</div>
|
||||||
% if allow_contact_info_choice:
|
% if allow_contact_info_choice:
|
||||||
<div class="level-item"
|
<div class="level-item"
|
||||||
% if restrict_contact_info:
|
% if not allow_contact_info_create:
|
||||||
v-if="contactPhones.length > 1"
|
v-if="contactPhones.length > 1"
|
||||||
% endif
|
% endif
|
||||||
>
|
>
|
||||||
|
@ -203,7 +215,7 @@
|
||||||
</b-radio>
|
</b-radio>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
% if not restrict_contact_info:
|
% if allow_contact_info_create:
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-radio v-model="existingPhoneUUID"
|
<b-radio v-model="existingPhoneUUID"
|
||||||
:native-value="null">
|
:native-value="null">
|
||||||
|
@ -249,11 +261,11 @@
|
||||||
<div class="level-left">
|
<div class="level-left">
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<div v-if="orderEmailAddress">
|
<div v-if="orderEmailAddress">
|
||||||
<p>
|
<p :class="addOtherEmailAddress ? 'has-text-success' : null">
|
||||||
{{ orderEmailAddress }}
|
{{ orderEmailAddress }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="addOtherEmailAddress"
|
<p v-if="addOtherEmailAddress"
|
||||||
class="is-size-7 is-italic">
|
class="is-size-7 is-italic has-text-success">
|
||||||
will be added to customer record
|
will be added to customer record
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -264,7 +276,7 @@
|
||||||
</div>
|
</div>
|
||||||
% if allow_contact_info_choice:
|
% if allow_contact_info_choice:
|
||||||
<div class="level-item"
|
<div class="level-item"
|
||||||
% if restrict_contact_info:
|
% if not allow_contact_info_create:
|
||||||
v-if="contactEmails.length > 1"
|
v-if="contactEmails.length > 1"
|
||||||
% endif
|
% endif
|
||||||
>
|
>
|
||||||
|
@ -296,7 +308,7 @@
|
||||||
</b-radio>
|
</b-radio>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
% if not restrict_contact_info:
|
% if allow_contact_info_create:
|
||||||
<b-field>
|
<b-field>
|
||||||
<b-radio v-model="existingEmailUUID"
|
<b-radio v-model="existingEmailUUID"
|
||||||
:native-value="null">
|
:native-value="null">
|
||||||
|
@ -556,7 +568,13 @@
|
||||||
<span>{{ productCaseQuantity }}</span>
|
<span>{{ productCaseQuantity }}</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Unit Price">
|
<b-field label="Reg. Price"
|
||||||
|
v-if="productSalePriceDisplay">
|
||||||
|
<span>{{ productUnitRegularPriceDisplay }}</span>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Unit Price"
|
||||||
|
v-if="!productSalePriceDisplay">
|
||||||
<span
|
<span
|
||||||
% if product_price_may_be_questionable:
|
% if product_price_may_be_questionable:
|
||||||
:class="productPriceNeedsConfirmation ? 'has-background-warning' : ''"
|
:class="productPriceNeedsConfirmation ? 'has-background-warning' : ''"
|
||||||
|
@ -599,70 +617,172 @@
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<b-radio v-model="productIsKnown" disabled
|
<b-radio v-model="productIsKnown"
|
||||||
|
% if not allow_unknown_product:
|
||||||
|
disabled
|
||||||
|
% endif
|
||||||
:native-value="false">
|
:native-value="false">
|
||||||
Product is not yet in the system.
|
Product is not yet in the system.
|
||||||
</b-radio>
|
</b-radio>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-show="!productIsKnown"
|
||||||
|
style="padding-left: 5rem;">
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field label="Brand">
|
||||||
|
<b-input v-model="pendingProduct.brand_name">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Description"
|
||||||
|
:type="pendingProduct.description ? null : 'is-danger'">
|
||||||
|
<b-input v-model="pendingProduct.description">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Unit Size">
|
||||||
|
<b-input v-model="pendingProduct.size">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field :label="productKeyLabel">
|
||||||
|
<b-input v-model="pendingProduct[productKeyField]">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Department">
|
||||||
|
<b-select v-model="pendingProduct.department_uuid">
|
||||||
|
<option :value="null">(not known)</option>
|
||||||
|
<option v-for="option in departmentOptions"
|
||||||
|
:key="option.value"
|
||||||
|
:value="option.value">
|
||||||
|
{{ option.label }}
|
||||||
|
</option>
|
||||||
|
</b-select>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Unit Reg. Price">
|
||||||
|
<b-input v-model="pendingProduct.regular_price_amount"
|
||||||
|
type="number" step="0.01">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field label="Vendor">
|
||||||
|
<b-input v-model="pendingProduct.vendor_name">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Vendor Item Code">
|
||||||
|
<b-input v-model="pendingProduct.vendor_item_code">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Unit Cost">
|
||||||
|
<b-input v-model="pendingProduct.unit_cost"
|
||||||
|
type="number" step="0.01"
|
||||||
|
style="width: 10rem;">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Case Size">
|
||||||
|
<b-input v-model="pendingProduct.case_size"
|
||||||
|
type="number" step="0.01"
|
||||||
|
style="width: 7rem;">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Notes">
|
||||||
|
<b-input v-model="pendingProduct.notes"
|
||||||
|
type="textarea">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</div>
|
||||||
</b-tab-item>
|
</b-tab-item>
|
||||||
<b-tab-item label="Quantity">
|
<b-tab-item label="Quantity">
|
||||||
|
|
||||||
<div class="is-pulled-right has-text-centered">
|
<div class="is-pulled-right has-text-centered">
|
||||||
<img :src="productImageURL"
|
<img :src="productImageURL"
|
||||||
style="height: 150px; width: 150px; "/>
|
style="height: 150px; width: 150px; "/>
|
||||||
## <p>{{ productKey }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<b-field grouped>
|
<b-field grouped>
|
||||||
<b-field label="Product" horizontal>
|
<b-field label="Product" horizontal>
|
||||||
<span>{{ productDisplay }}</span>
|
<span :class="productIsKnown ? null : 'has-text-success'">
|
||||||
|
{{ productIsKnown ? productDisplay : pendingProduct.brand_name + ' ' + pendingProduct.description + ' ' + pendingProduct.size }}
|
||||||
|
</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field grouped>
|
<b-field grouped>
|
||||||
|
|
||||||
<b-field label="Unit Size">
|
<b-field label="Unit Size">
|
||||||
<span>{{ productSize }}</span>
|
<span :class="productIsKnown ? null : 'has-text-success'">
|
||||||
|
{{ productIsKnown ? productSize : pendingProduct.size }}
|
||||||
|
</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Unit Price">
|
<b-field label="Reg. Price"
|
||||||
<span
|
v-if="productSalePriceDisplay">
|
||||||
% if product_price_may_be_questionable:
|
<span>
|
||||||
:class="productPriceNeedsConfirmation ? 'has-background-warning' : ''"
|
{{ productUnitRegularPriceDisplay }}
|
||||||
% endif
|
</span>
|
||||||
>
|
</b-field>
|
||||||
{{ productUnitPriceDisplay }}
|
|
||||||
|
<b-field label="Unit Price"
|
||||||
|
v-if="!productSalePriceDisplay">
|
||||||
|
<span :class="productIsKnown ? null : 'has-text-success'"
|
||||||
|
% if product_price_may_be_questionable:
|
||||||
|
:class="productPriceNeedsConfirmation ? 'has-background-warning' : ''"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
{{ productIsKnown ? productUnitPriceDisplay : '$' + pendingProduct.regular_price_amount }}
|
||||||
</span>
|
</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Sale Price"
|
<b-field label="Sale Price"
|
||||||
v-if="productSalePriceDisplay">
|
v-if="productSalePriceDisplay">
|
||||||
<span class="has-background-warning">
|
<span class="has-background-warning"
|
||||||
|
:class="productIsKnown ? null : 'has-text-success'">
|
||||||
{{ productSalePriceDisplay }}
|
{{ productSalePriceDisplay }}
|
||||||
</span>
|
</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Sale Ends"
|
<b-field label="Sale Ends"
|
||||||
v-if="productSaleEndsDisplay">
|
v-if="productSaleEndsDisplay">
|
||||||
<span class="has-background-warning">
|
<span class="has-background-warning"
|
||||||
|
:class="productIsKnown ? null : 'has-text-success'">
|
||||||
{{ productSaleEndsDisplay }}
|
{{ productSaleEndsDisplay }}
|
||||||
</span>
|
</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Case Size">
|
<b-field label="Case Size">
|
||||||
<span>{{ productCaseQuantity }}</span>
|
<span :class="productIsKnown ? null : 'has-text-success'">
|
||||||
|
{{ productIsKnown ? productCaseQuantity : pendingProduct.case_size }}
|
||||||
|
</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Case Price">
|
<b-field label="Case Price">
|
||||||
<span
|
<span
|
||||||
% if product_price_may_be_questionable:
|
% if product_price_may_be_questionable:
|
||||||
:class="(productPriceNeedsConfirmation || productSalePriceDisplay) ? 'has-background-warning' : ''"
|
:class="{'has-text-success': !productIsKnown, 'has-background-warning': productPriceNeedsConfirmation || productSalePriceDisplay}"
|
||||||
% else:
|
% else:
|
||||||
:class="productSalePriceDisplay ? 'has-background-warning' : ''"
|
:class="{'has-text-success': !productIsKnown, 'has-background-warning': !!productSalePriceDisplay}"
|
||||||
% endif
|
% endif
|
||||||
>
|
>
|
||||||
{{ productCasePriceDisplay }}
|
{{ getCasePriceDisplay() }}
|
||||||
</span>
|
</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
|
@ -671,7 +791,9 @@
|
||||||
<b-field grouped>
|
<b-field grouped>
|
||||||
|
|
||||||
<b-field label="Quantity" horizontal>
|
<b-field label="Quantity" horizontal>
|
||||||
<b-input v-model="productQuantity"></b-input>
|
<b-input v-model="productQuantity"
|
||||||
|
type="number" step="0.01">
|
||||||
|
</b-input>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-select v-model="productUOM">
|
<b-select v-model="productUOM">
|
||||||
|
@ -684,6 +806,14 @@
|
||||||
|
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
<b-field label="Total Price">
|
||||||
|
<span :class="productSalePriceDisplay ? 'has-background-warning': null">
|
||||||
|
{{ getItemTotalPriceDisplay() }}
|
||||||
|
</span>
|
||||||
|
</b-field>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
</b-tab-item>
|
</b-tab-item>
|
||||||
</b-tabs>
|
</b-tabs>
|
||||||
|
|
||||||
|
@ -692,9 +822,10 @@
|
||||||
Cancel
|
Cancel
|
||||||
</b-button>
|
</b-button>
|
||||||
<b-button type="is-primary"
|
<b-button type="is-primary"
|
||||||
|
@click="itemDialogSave()"
|
||||||
|
:disabled="itemDialogSaveDisabled"
|
||||||
icon-pack="fas"
|
icon-pack="fas"
|
||||||
icon-left="fas fa-save"
|
icon-left="save">
|
||||||
@click="itemDialogSave()">
|
|
||||||
{{ itemDialogSaveButtonText }}
|
{{ itemDialogSaveButtonText }}
|
||||||
</b-button>
|
</b-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -807,60 +938,65 @@
|
||||||
</b-modal>
|
</b-modal>
|
||||||
|
|
||||||
<b-table v-if="items.length"
|
<b-table v-if="items.length"
|
||||||
:data="items">
|
:data="items"
|
||||||
|
:row-class="(row, i) => row.product_uuid ? null : 'has-text-success'">
|
||||||
<template slot-scope="props">
|
<template slot-scope="props">
|
||||||
|
|
||||||
<b-table-column field="product_upc_pretty" label="UPC">
|
<b-table-column :label="productKeyLabel">
|
||||||
{{ props.row.product_upc_pretty }}
|
{{ props.row.product_key }}
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="product_brand" label="Brand">
|
<b-table-column label="Brand">
|
||||||
{{ props.row.product_brand }}
|
{{ props.row.product_brand }}
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="product_description" label="Description">
|
<b-table-column label="Description">
|
||||||
{{ props.row.product_description }}
|
{{ props.row.product_description }}
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="product_size" label="Size">
|
<b-table-column label="Size">
|
||||||
{{ props.row.product_size }}
|
{{ props.row.product_size }}
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="department_display" label="Department">
|
<b-table-column label="Department">
|
||||||
{{ props.row.department_display }}
|
{{ props.row.department_display }}
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="order_quantity_display" label="Quantity">
|
<b-table-column label="Quantity">
|
||||||
<span v-html="props.row.order_quantity_display"></span>
|
<span v-html="props.row.order_quantity_display"></span>
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="unit_price_display" label="Unit Price">
|
<b-table-column label="Unit Price">
|
||||||
<span
|
<span
|
||||||
% if product_price_may_be_questionable:
|
% if product_price_may_be_questionable:
|
||||||
:class="props.row.price_needs_confirmation ? 'has-background-warning' : ''"
|
:class="props.row.price_needs_confirmation ? 'has-background-warning' : ''"
|
||||||
|
% else:
|
||||||
|
:class="props.row.pricing_reflects_sale ? 'has-background-warning' : null"
|
||||||
% endif
|
% endif
|
||||||
>
|
>
|
||||||
{{ props.row.unit_price_display }}
|
{{ props.row.unit_price_display }}
|
||||||
</span>
|
</span>
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="total_price_display" label="Total">
|
<b-table-column label="Total">
|
||||||
<span
|
<span
|
||||||
% if product_price_may_be_questionable:
|
% if product_price_may_be_questionable:
|
||||||
:class="props.row.price_needs_confirmation ? 'has-background-warning' : ''"
|
:class="props.row.price_needs_confirmation ? 'has-background-warning' : ''"
|
||||||
|
% else:
|
||||||
|
:class="props.row.pricing_reflects_sale ? 'has-background-warning' : null"
|
||||||
% endif
|
% endif
|
||||||
>
|
>
|
||||||
{{ props.row.total_price_display }}
|
{{ props.row.total_price_display }}
|
||||||
</span>
|
</span>
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="vendor_display" label="Vendor">
|
<b-table-column label="Vendor">
|
||||||
{{ props.row.vendor_display }}
|
{{ props.row.vendor_display }}
|
||||||
</b-table-column>
|
</b-table-column>
|
||||||
|
|
||||||
<b-table-column field="actions" label="Actions">
|
<b-table-column field="actions" label="Actions">
|
||||||
<a href="#" class="grid-action"
|
<a href="#" class="grid-action"
|
||||||
@click.prevent="showEditItemDialog(props.index)">
|
@click.prevent="showEditItemDialog(props.row)">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
Edit
|
Edit
|
||||||
</a>
|
</a>
|
||||||
|
@ -974,11 +1110,16 @@
|
||||||
productDisplay: null,
|
productDisplay: null,
|
||||||
productUPC: null,
|
productUPC: null,
|
||||||
productKey: null,
|
productKey: null,
|
||||||
|
productKeyField: ${json.dumps(product_key_field)|n},
|
||||||
productKeyLabel: ${json.dumps(product_key_label)|n},
|
productKeyLabel: ${json.dumps(product_key_label)|n},
|
||||||
productSize: null,
|
productSize: null,
|
||||||
productCaseQuantity: null,
|
productCaseQuantity: null,
|
||||||
|
productUnitPrice: null,
|
||||||
productUnitPriceDisplay: null,
|
productUnitPriceDisplay: null,
|
||||||
|
productUnitRegularPriceDisplay: null,
|
||||||
|
productCasePrice: null,
|
||||||
productCasePriceDisplay: null,
|
productCasePriceDisplay: null,
|
||||||
|
productSalePrice: null,
|
||||||
productSalePriceDisplay: null,
|
productSalePriceDisplay: null,
|
||||||
productSaleEndsDisplay: null,
|
productSaleEndsDisplay: null,
|
||||||
productURL: null,
|
productURL: null,
|
||||||
|
@ -994,6 +1135,9 @@
|
||||||
productPriceNeedsConfirmation: false,
|
productPriceNeedsConfirmation: false,
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
|
pendingProduct: {},
|
||||||
|
departmentOptions: ${json.dumps(department_options)|n},
|
||||||
|
|
||||||
## TODO: should find a better way to handle CSRF token
|
## TODO: should find a better way to handle CSRF token
|
||||||
csrftoken: ${json.dumps(request.session.get_csrf_token() or request.session.new_csrf_token())|n},
|
csrftoken: ${json.dumps(request.session.get_csrf_token() or request.session.new_csrf_token())|n},
|
||||||
|
|
||||||
|
@ -1171,6 +1315,19 @@
|
||||||
return text
|
return text
|
||||||
},
|
},
|
||||||
|
|
||||||
|
itemDialogSaveDisabled() {
|
||||||
|
if (this.productIsKnown) {
|
||||||
|
if (!this.productUUID) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!this.pendingProduct.description) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
itemDialogSaveButtonText() {
|
itemDialogSaveButtonText() {
|
||||||
return this.editingItem ? "Update Item" : "Add Item"
|
return this.editingItem ? "Update Item" : "Add Item"
|
||||||
},
|
},
|
||||||
|
@ -1522,6 +1679,71 @@
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getCasePriceDisplay() {
|
||||||
|
if (this.productIsKnown) {
|
||||||
|
return this.productCasePriceDisplay
|
||||||
|
}
|
||||||
|
|
||||||
|
let casePrice = this.getItemCasePrice()
|
||||||
|
if (casePrice) {
|
||||||
|
return "$" + casePrice
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getItemUnitPrice() {
|
||||||
|
if (this.productIsKnown) {
|
||||||
|
return this.productSalePrice || this.productUnitPrice
|
||||||
|
}
|
||||||
|
return this.pendingProduct.regular_price_amount
|
||||||
|
},
|
||||||
|
|
||||||
|
getItemCasePrice() {
|
||||||
|
if (this.productIsKnown) {
|
||||||
|
return this.productCasePrice
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pendingProduct.regular_price_amount) {
|
||||||
|
if (this.pendingProduct.case_size) {
|
||||||
|
let casePrice = this.pendingProduct.regular_price_amount * this.pendingProduct.case_size
|
||||||
|
casePrice = casePrice.toFixed(2)
|
||||||
|
return casePrice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getItemTotalPriceDisplay() {
|
||||||
|
let basePrice = null
|
||||||
|
if (this.productUOM == '${enum.UNIT_OF_MEASURE_CASE}') {
|
||||||
|
basePrice = this.getItemCasePrice()
|
||||||
|
} else {
|
||||||
|
basePrice = this.getItemUnitPrice()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (basePrice) {
|
||||||
|
let totalPrice = basePrice * this.productQuantity
|
||||||
|
if (totalPrice) {
|
||||||
|
totalPrice = totalPrice.toFixed(2)
|
||||||
|
return "$" + totalPrice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
copyPendingProductAttrs(from, to) {
|
||||||
|
to.upc = from.upc
|
||||||
|
to.item_id = from.item_id
|
||||||
|
to.scancode = from.scancode
|
||||||
|
to.brand_name = from.brand_name
|
||||||
|
to.description = from.description
|
||||||
|
to.size = from.size
|
||||||
|
to.department_uuid = from.department_uuid
|
||||||
|
to.regular_price_amount = from.regular_price_amount
|
||||||
|
to.vendor_name = from.vendor_name
|
||||||
|
to.vendor_item_code = from.vendor_item_code
|
||||||
|
to.unit_cost = from.unit_cost
|
||||||
|
to.case_size = from.case_size
|
||||||
|
to.notes = from.notes
|
||||||
|
},
|
||||||
|
|
||||||
showAddItemDialog() {
|
showAddItemDialog() {
|
||||||
this.customerPanelOpen = false
|
this.customerPanelOpen = false
|
||||||
this.editingItem = null
|
this.editingItem = null
|
||||||
|
@ -1532,11 +1754,18 @@
|
||||||
this.productKey = null
|
this.productKey = null
|
||||||
this.productSize = null
|
this.productSize = null
|
||||||
this.productCaseQuantity = null
|
this.productCaseQuantity = null
|
||||||
|
this.productUnitPrice = null
|
||||||
this.productUnitPriceDisplay = null
|
this.productUnitPriceDisplay = null
|
||||||
|
this.productUnitRegularPriceDisplay = null
|
||||||
|
this.productCasePrice = null
|
||||||
this.productCasePriceDisplay = null
|
this.productCasePriceDisplay = null
|
||||||
|
this.productSalePrice = null
|
||||||
this.productSalePriceDisplay = null
|
this.productSalePriceDisplay = null
|
||||||
this.productSaleEndsDisplay = null
|
this.productSaleEndsDisplay = null
|
||||||
this.productImageURL = '${request.static_url('tailbone:static/img/product.png')}'
|
this.productImageURL = '${request.static_url('tailbone:static/img/product.png')}'
|
||||||
|
|
||||||
|
this.pendingProduct = {}
|
||||||
|
|
||||||
this.productQuantity = 1
|
this.productQuantity = 1
|
||||||
this.productUnitChoices = this.defaultUnitChoices
|
this.productUnitChoices = this.defaultUnitChoices
|
||||||
this.productUOM = this.defaultUOM
|
this.productUOM = this.defaultUOM
|
||||||
|
@ -1581,8 +1810,12 @@
|
||||||
this.productKey = selected.key
|
this.productKey = selected.key
|
||||||
this.productSize = selected.size
|
this.productSize = selected.size
|
||||||
this.productCaseQuantity = selected.case_quantity
|
this.productCaseQuantity = selected.case_quantity
|
||||||
|
this.productUnitPrice = selected.unit_price
|
||||||
this.productUnitPriceDisplay = selected.unit_price_display
|
this.productUnitPriceDisplay = selected.unit_price_display
|
||||||
|
this.productUnitRegularPriceDisplay = selected.unit_price_display
|
||||||
|
this.productCasePrice = selected.case_price
|
||||||
this.productCasePriceDisplay = selected.case_price_display
|
this.productCasePriceDisplay = selected.case_price_display
|
||||||
|
this.productSalePrice = selected.sale_price
|
||||||
this.productSalePriceDisplay = selected.sale_price_display
|
this.productSalePriceDisplay = selected.sale_price_display
|
||||||
this.productSaleEndsDisplay = selected.sale_ends_display
|
this.productSaleEndsDisplay = selected.sale_ends_display
|
||||||
this.productImageURL = selected.image_url
|
this.productImageURL = selected.image_url
|
||||||
|
@ -1600,30 +1833,41 @@
|
||||||
this.showingItemDialog = true
|
this.showingItemDialog = true
|
||||||
},
|
},
|
||||||
|
|
||||||
showEditItemDialog(index) {
|
showEditItemDialog(row) {
|
||||||
row = this.items[index]
|
|
||||||
this.editingItem = row
|
this.editingItem = row
|
||||||
this.productIsKnown = true // TODO
|
|
||||||
|
this.productIsKnown = !!row.product_uuid
|
||||||
this.productUUID = row.product_uuid
|
this.productUUID = row.product_uuid
|
||||||
|
this.pendingProduct = {}
|
||||||
|
if (row.pending_product) {
|
||||||
|
this.copyPendingProductAttrs(row.pending_product,
|
||||||
|
this.pendingProduct)
|
||||||
|
}
|
||||||
|
|
||||||
this.productDisplay = row.product_full_description
|
this.productDisplay = row.product_full_description
|
||||||
this.productUPC = row.product_upc_pretty || row.product_upc
|
this.productUPC = row.product_upc_pretty || row.product_upc
|
||||||
this.productKey = row.product_key
|
this.productKey = row.product_key
|
||||||
this.productSize = row.product_size
|
this.productSize = row.product_size
|
||||||
this.productCaseQuantity = row.case_quantity
|
this.productCaseQuantity = row.case_quantity
|
||||||
this.productURL = row.product_url
|
this.productURL = row.product_url
|
||||||
|
this.productUnitPrice = row.unit_price
|
||||||
this.productUnitPriceDisplay = row.unit_price_display
|
this.productUnitPriceDisplay = row.unit_price_display
|
||||||
|
this.productUnitRegularPriceDisplay = row.unit_regular_price_display
|
||||||
|
this.productCasePrice = row.case_price
|
||||||
this.productCasePriceDisplay = row.case_price_display
|
this.productCasePriceDisplay = row.case_price_display
|
||||||
this.productSalePriceDisplay = row.sale_price_display
|
this.productSalePrice = row.sale_price
|
||||||
|
this.productSalePriceDisplay = row.unit_sale_price_display
|
||||||
this.productSaleEndsDisplay = row.sale_ends_display
|
this.productSaleEndsDisplay = row.sale_ends_display
|
||||||
this.productImageURL = row.product_image_url
|
this.productImageURL = row.product_image_url || '${request.static_url('tailbone:static/img/product.png')}'
|
||||||
this.productQuantity = row.order_quantity
|
|
||||||
this.productUnitChoices = row.order_uom_choices
|
|
||||||
this.productUOM = row.order_uom
|
|
||||||
|
|
||||||
% if product_price_may_be_questionable:
|
% if product_price_may_be_questionable:
|
||||||
this.productPriceNeedsConfirmation = row.price_needs_confirmation
|
this.productPriceNeedsConfirmation = row.price_needs_confirmation
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
|
this.productQuantity = row.order_quantity
|
||||||
|
this.productUnitChoices = row.order_uom_choices
|
||||||
|
this.productUOM = row.order_uom
|
||||||
|
|
||||||
this.itemDialogTabIndex = 1
|
this.itemDialogTabIndex = 1
|
||||||
this.showingItemDialog = true
|
this.showingItemDialog = true
|
||||||
},
|
},
|
||||||
|
@ -1658,8 +1902,12 @@
|
||||||
this.productKey = null
|
this.productKey = null
|
||||||
this.productSize = null
|
this.productSize = null
|
||||||
this.productCaseQuantity = null
|
this.productCaseQuantity = null
|
||||||
|
this.productUnitPrice = null
|
||||||
this.productUnitPriceDisplay = null
|
this.productUnitPriceDisplay = null
|
||||||
|
this.productUnitRegularPriceDisplay = null
|
||||||
|
this.productCasePrice = null
|
||||||
this.productCasePriceDisplay = null
|
this.productCasePriceDisplay = null
|
||||||
|
this.productSalePrice = null
|
||||||
this.productSalePriceDisplay = null
|
this.productSalePriceDisplay = null
|
||||||
this.productSaleEndsDisplay = null
|
this.productSaleEndsDisplay = null
|
||||||
this.productURL = null
|
this.productURL = null
|
||||||
|
@ -1705,8 +1953,12 @@
|
||||||
this.productDisplay = response.data.full_description
|
this.productDisplay = response.data.full_description
|
||||||
this.productSize = response.data.size
|
this.productSize = response.data.size
|
||||||
this.productCaseQuantity = response.data.case_quantity
|
this.productCaseQuantity = response.data.case_quantity
|
||||||
|
this.productUnitPrice = response.data.unit_price
|
||||||
this.productUnitPriceDisplay = response.data.unit_price_display
|
this.productUnitPriceDisplay = response.data.unit_price_display
|
||||||
|
this.productUnitRegularPriceDisplay = response.data.unit_price_display
|
||||||
|
this.productCasePrice = response.data.case_price
|
||||||
this.productCasePriceDisplay = response.data.case_price_display
|
this.productCasePriceDisplay = response.data.case_price_display
|
||||||
|
this.productSalePrice = response.data.sale_price
|
||||||
this.productSalePriceDisplay = response.data.sale_price_display
|
this.productSalePriceDisplay = response.data.sale_price_display
|
||||||
this.productSaleEndsDisplay = response.data.sale_ends_display
|
this.productSaleEndsDisplay = response.data.sale_ends_display
|
||||||
this.productURL = response.data.url
|
this.productURL = response.data.url
|
||||||
|
@ -1728,13 +1980,17 @@
|
||||||
|
|
||||||
let params = {
|
let params = {
|
||||||
product_is_known: this.productIsKnown,
|
product_is_known: this.productIsKnown,
|
||||||
product_uuid: this.productUUID,
|
% if product_price_may_be_questionable:
|
||||||
|
price_needs_confirmation: this.productPriceNeedsConfirmation,
|
||||||
|
% endif
|
||||||
order_quantity: this.productQuantity,
|
order_quantity: this.productQuantity,
|
||||||
order_uom: this.productUOM,
|
order_uom: this.productUOM,
|
||||||
|
}
|
||||||
|
|
||||||
% if product_price_may_be_questionable:
|
if (this.productIsKnown) {
|
||||||
price_needs_confirmation: this.productPriceNeedsConfirmation,
|
params.product_uuid = this.productUUID
|
||||||
% endif
|
} else {
|
||||||
|
params.pending_product = this.pendingProduct
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.editingItem) {
|
if (this.editingItem) {
|
||||||
|
|
|
@ -4,11 +4,10 @@
|
||||||
<%def name="title()">Delete ${model_title}: ${instance_title}</%def>
|
<%def name="title()">Delete ${model_title}: ${instance_title}</%def>
|
||||||
|
|
||||||
<%def name="context_menu_items()">
|
<%def name="context_menu_items()">
|
||||||
<li>${h.link_to("Back to {}".format(model_title_plural), url(route_prefix))}</li>
|
% if not use_buefy and master.viewable and master.has_perm('view'):
|
||||||
% if master.viewable and request.has_perm('{}.view'.format(permission_prefix)):
|
|
||||||
<li>${h.link_to("View this {}".format(model_title), action_url('view', instance))}</li>
|
<li>${h.link_to("View this {}".format(model_title), action_url('view', instance))}</li>
|
||||||
% endif
|
% endif
|
||||||
% if master.editable and request.has_perm('{}.edit'.format(permission_prefix)):
|
% if not use_buefy and master.editable and master.has_perm('edit'):
|
||||||
<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 not use_buefy and master.creatable and master.show_create_link and master.has_perm('create'):
|
% if not use_buefy and master.creatable and master.show_create_link and master.has_perm('create'):
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="context_menu_items()">
|
<%def name="context_menu_items()">
|
||||||
% if master.viewable and request.has_perm('{}.view'.format(permission_prefix)):
|
% if not use_buefy and master.viewable and master.has_perm('view'):
|
||||||
<li>${h.link_to("View this {}".format(model_title), action_url('view', instance))}</li>
|
<li>${h.link_to("View this {}".format(model_title), action_url('view', instance))}</li>
|
||||||
% endif
|
% endif
|
||||||
${self.context_menu_item_delete()}
|
${self.context_menu_item_delete()}
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="render_main_fields(form)">
|
<%def name="render_main_fields(form)">
|
||||||
${form.render_field_readonly('upc')}
|
${form.render_field_readonly(product_key_field)}
|
||||||
${form.render_field_readonly('brand')}
|
${form.render_field_readonly('brand')}
|
||||||
${form.render_field_readonly('description')}
|
${form.render_field_readonly('description')}
|
||||||
${form.render_field_readonly('size')}
|
${form.render_field_readonly('size')}
|
||||||
|
|
|
@ -277,7 +277,7 @@
|
||||||
<span class="header-text">
|
<span class="header-text">
|
||||||
${index_title}
|
${index_title}
|
||||||
</span>
|
</span>
|
||||||
% if use_buefy and master.creatable and master.show_create_link and master.has_perm('create'):
|
% if master.creatable and master.show_create_link and master.has_perm('create'):
|
||||||
<once-button type="is-primary"
|
<once-button type="is-primary"
|
||||||
tag="a" href="${url('{}.create'.format(route_prefix))}"
|
tag="a" href="${url('{}.create'.format(route_prefix))}"
|
||||||
icon-left="plus"
|
icon-left="plus"
|
||||||
|
@ -409,67 +409,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="level-right">
|
<div class="level-right">
|
||||||
% if use_buefy and master and master.viewing:
|
${self.render_instance_header_buttons()}
|
||||||
## TODO: is there a better way to check if viewing parent?
|
|
||||||
% if parent_instance is Undefined:
|
|
||||||
% if master.editable and instance_editable and master.has_perm('edit'):
|
|
||||||
<div class="level-item">
|
|
||||||
<once-button tag="a" href="${action_url('edit', instance)}"
|
|
||||||
icon-left="edit"
|
|
||||||
text="Edit This">
|
|
||||||
</once-button>
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
% if master.cloneable and master.has_perm('clone'):
|
|
||||||
<div class="level-item">
|
|
||||||
<once-button tag="a" href="${action_url('clone', instance)}"
|
|
||||||
icon-left="object-ungroup"
|
|
||||||
text="Clone This">
|
|
||||||
</once-button>
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
% if master.deletable and instance_deletable and master.has_perm('delete'):
|
|
||||||
<div class="level-item">
|
|
||||||
<once-button tag="a" href="${action_url('delete', instance)}"
|
|
||||||
type="is-danger"
|
|
||||||
icon-left="trash"
|
|
||||||
text="Delete This">
|
|
||||||
</once-button>
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
% else:
|
|
||||||
## viewing row
|
|
||||||
% if instance_deletable and master.has_perm('delete_row'):
|
|
||||||
<div class="level-item">
|
|
||||||
<once-button tag="a" href="${action_url('delete', instance)}"
|
|
||||||
type="is-danger"
|
|
||||||
icon-left="trash"
|
|
||||||
text="Delete This">
|
|
||||||
</once-button>
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
% endif
|
|
||||||
% endif
|
|
||||||
% if show_prev_next is not Undefined and show_prev_next:
|
|
||||||
% if prev_url:
|
|
||||||
<div class="level-item">
|
|
||||||
${h.link_to(u"« Older", prev_url, class_='button autodisable')}
|
|
||||||
</div>
|
|
||||||
% else:
|
|
||||||
<div class="level-item">
|
|
||||||
${h.link_to(u"« Older", '#', class_='button', disabled='disabled')}
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
% if next_url:
|
|
||||||
<div class="level-item">
|
|
||||||
${h.link_to(u"Newer »", next_url, class_='button autodisable')}
|
|
||||||
</div>
|
|
||||||
% else:
|
|
||||||
<div class="level-item">
|
|
||||||
${h.link_to(u"Newer »", '#', class_='button', disabled='disabled')}
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
% endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -583,6 +523,113 @@
|
||||||
${tailbone_autocomplete_template()}
|
${tailbone_autocomplete_template()}
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_instance_header_buttons()">
|
||||||
|
${self.render_crud_header_buttons()}
|
||||||
|
${self.render_prevnext_header_buttons()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_crud_header_buttons()">
|
||||||
|
% if master and master.viewing:
|
||||||
|
## TODO: is there a better way to check if viewing parent?
|
||||||
|
% if parent_instance is Undefined:
|
||||||
|
% if master.editable and instance_editable and master.has_perm('edit'):
|
||||||
|
<div class="level-item">
|
||||||
|
<once-button tag="a" href="${action_url('edit', instance)}"
|
||||||
|
icon-left="edit"
|
||||||
|
text="Edit This">
|
||||||
|
</once-button>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% if master.cloneable and master.has_perm('clone'):
|
||||||
|
<div class="level-item">
|
||||||
|
<once-button tag="a" href="${action_url('clone', instance)}"
|
||||||
|
icon-left="object-ungroup"
|
||||||
|
text="Clone This">
|
||||||
|
</once-button>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% if master.deletable and instance_deletable and master.has_perm('delete'):
|
||||||
|
<div class="level-item">
|
||||||
|
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||||
|
type="is-danger"
|
||||||
|
icon-left="trash"
|
||||||
|
text="Delete This">
|
||||||
|
</once-button>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% else:
|
||||||
|
## viewing row
|
||||||
|
% if instance_deletable and master.has_perm('delete_row'):
|
||||||
|
<div class="level-item">
|
||||||
|
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||||
|
type="is-danger"
|
||||||
|
icon-left="trash"
|
||||||
|
text="Delete This">
|
||||||
|
</once-button>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% endif
|
||||||
|
% elif master and master.editing:
|
||||||
|
% if master.viewable and master.has_perm('view'):
|
||||||
|
<div class="level-item">
|
||||||
|
<once-button tag="a" href="${action_url('view', instance)}"
|
||||||
|
icon-left="eye"
|
||||||
|
text="View This">
|
||||||
|
</once-button>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% if master.deletable and instance_deletable and master.has_perm('delete'):
|
||||||
|
<div class="level-item">
|
||||||
|
<once-button tag="a" href="${action_url('delete', instance)}"
|
||||||
|
type="is-danger"
|
||||||
|
icon-left="trash"
|
||||||
|
text="Delete This">
|
||||||
|
</once-button>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% elif master and master.deleting:
|
||||||
|
% if master.viewable and master.has_perm('view'):
|
||||||
|
<div class="level-item">
|
||||||
|
<once-button tag="a" href="${action_url('view', instance)}"
|
||||||
|
icon-left="eye"
|
||||||
|
text="View This">
|
||||||
|
</once-button>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% if master.editable and instance_editable and master.has_perm('edit'):
|
||||||
|
<div class="level-item">
|
||||||
|
<once-button tag="a" href="${action_url('edit', instance)}"
|
||||||
|
icon-left="edit"
|
||||||
|
text="Edit This">
|
||||||
|
</once-button>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_prevnext_header_buttons()">
|
||||||
|
% if show_prev_next is not Undefined and show_prev_next:
|
||||||
|
% if prev_url:
|
||||||
|
<div class="level-item">
|
||||||
|
${h.link_to(u"« Older", prev_url, class_='button autodisable')}
|
||||||
|
</div>
|
||||||
|
% else:
|
||||||
|
<div class="level-item">
|
||||||
|
${h.link_to(u"« Older", '#', class_='button', disabled='disabled')}
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% if next_url:
|
||||||
|
<div class="level-item">
|
||||||
|
${h.link_to(u"Newer »", next_url, class_='button autodisable')}
|
||||||
|
</div>
|
||||||
|
% else:
|
||||||
|
<div class="level-item">
|
||||||
|
${h.link_to(u"Newer »", '#', class_='button', disabled='disabled')}
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
<%def name="declare_whole_page_vars()">
|
<%def name="declare_whole_page_vars()">
|
||||||
${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">
|
||||||
|
|
|
@ -623,16 +623,6 @@ class BatchMasterView(MasterView):
|
||||||
def get_row_status_enum(self):
|
def get_row_status_enum(self):
|
||||||
return self.model_row_class.STATUS
|
return self.model_row_class.STATUS
|
||||||
|
|
||||||
def render_upc(self, row, field):
|
|
||||||
upc = row.upc
|
|
||||||
if not upc:
|
|
||||||
return ""
|
|
||||||
text = upc.pretty()
|
|
||||||
if row.product_uuid:
|
|
||||||
url = self.request.route_url('products.view', uuid=row.product_uuid)
|
|
||||||
return tags.link_to(text, url)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def render_upc_pretty(self, row, field):
|
def render_upc_pretty(self, row, field):
|
||||||
upc = getattr(row, field)
|
upc = getattr(row, field)
|
||||||
if upc:
|
if upc:
|
||||||
|
|
|
@ -510,7 +510,18 @@ class PendingCustomerView(MasterView):
|
||||||
|
|
||||||
f.set_enum('status_code', self.enum.PENDING_CUSTOMER_STATUS)
|
f.set_enum('status_code', self.enum.PENDING_CUSTOMER_STATUS)
|
||||||
|
|
||||||
f.set_renderer('user', self.render_user)
|
# created
|
||||||
|
if self.creating:
|
||||||
|
f.remove('created')
|
||||||
|
else:
|
||||||
|
f.set_readonly('created')
|
||||||
|
|
||||||
|
# user
|
||||||
|
if self.creating:
|
||||||
|
f.remove('user')
|
||||||
|
else:
|
||||||
|
f.set_readonly('user')
|
||||||
|
f.set_renderer('user', self.render_user)
|
||||||
|
|
||||||
|
|
||||||
# # TODO: this is referenced by some custom apps, but should be moved??
|
# # TODO: this is referenced by some custom apps, but should be moved??
|
||||||
|
|
|
@ -74,7 +74,6 @@ class CustomerOrderBatchView(BatchMasterView):
|
||||||
]
|
]
|
||||||
|
|
||||||
row_labels = {
|
row_labels = {
|
||||||
'product_upc': "UPC",
|
|
||||||
'product_brand': "Brand",
|
'product_brand': "Brand",
|
||||||
'product_description': "Description",
|
'product_description': "Description",
|
||||||
'product_size': "Size",
|
'product_size': "Size",
|
||||||
|
@ -83,7 +82,7 @@ class CustomerOrderBatchView(BatchMasterView):
|
||||||
|
|
||||||
row_grid_columns = [
|
row_grid_columns = [
|
||||||
'sequence',
|
'sequence',
|
||||||
'product_upc',
|
'_product_key_',
|
||||||
'product_brand',
|
'product_brand',
|
||||||
'product_description',
|
'product_description',
|
||||||
'product_size',
|
'product_size',
|
||||||
|
@ -94,6 +93,38 @@ class CustomerOrderBatchView(BatchMasterView):
|
||||||
'status_code',
|
'status_code',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
product_key_fields = {
|
||||||
|
'upc': 'product_upc',
|
||||||
|
'item_id': 'product_item_id',
|
||||||
|
'scancode': 'product_scancode',
|
||||||
|
}
|
||||||
|
|
||||||
|
row_form_fields = [
|
||||||
|
'sequence',
|
||||||
|
'item_entry',
|
||||||
|
'product',
|
||||||
|
'pending_product',
|
||||||
|
'_product_key_',
|
||||||
|
'product_brand',
|
||||||
|
'product_description',
|
||||||
|
'product_size',
|
||||||
|
'product_weighed',
|
||||||
|
'product_unit_of_measure',
|
||||||
|
'department_number',
|
||||||
|
'department_name',
|
||||||
|
'product_unit_cost',
|
||||||
|
'case_quantity',
|
||||||
|
'unit_price',
|
||||||
|
'price_needs_confirmation',
|
||||||
|
'order_quantity',
|
||||||
|
'order_uom',
|
||||||
|
'discount_percent',
|
||||||
|
'total_price',
|
||||||
|
'paid_amount',
|
||||||
|
# 'payment_transaction_number',
|
||||||
|
'status_code',
|
||||||
|
]
|
||||||
|
|
||||||
def configure_grid(self, g):
|
def configure_grid(self, g):
|
||||||
super(CustomerOrderBatchView, self).configure_grid(g)
|
super(CustomerOrderBatchView, self).configure_grid(g)
|
||||||
|
|
||||||
|
@ -170,6 +201,8 @@ class CustomerOrderBatchView(BatchMasterView):
|
||||||
def row_grid_extra_class(self, row, i):
|
def row_grid_extra_class(self, row, i):
|
||||||
if row.status_code == row.STATUS_PRODUCT_NOT_FOUND:
|
if row.status_code == row.STATUS_PRODUCT_NOT_FOUND:
|
||||||
return 'warning'
|
return 'warning'
|
||||||
|
if row.status_code == row.STATUS_PENDING_PRODUCT:
|
||||||
|
return 'notice'
|
||||||
|
|
||||||
def configure_row_grid(self, g):
|
def configure_row_grid(self, g):
|
||||||
super(CustomerOrderBatchView, self).configure_row_grid(g)
|
super(CustomerOrderBatchView, self).configure_row_grid(g)
|
||||||
|
@ -189,6 +222,9 @@ class CustomerOrderBatchView(BatchMasterView):
|
||||||
super(CustomerOrderBatchView, self).configure_row_form(f)
|
super(CustomerOrderBatchView, self).configure_row_form(f)
|
||||||
|
|
||||||
f.set_renderer('product', self.render_product)
|
f.set_renderer('product', self.render_product)
|
||||||
|
f.set_renderer('pending_product', self.render_pending_product)
|
||||||
|
|
||||||
|
f.set_renderer('product_upc', self.render_upc)
|
||||||
|
|
||||||
f.set_type('case_quantity', 'quantity')
|
f.set_type('case_quantity', 'quantity')
|
||||||
f.set_type('cases_ordered', 'quantity')
|
f.set_type('cases_ordered', 'quantity')
|
||||||
|
@ -197,3 +233,4 @@ class CustomerOrderBatchView(BatchMasterView):
|
||||||
f.set_enum('order_uom', self.enum.UNIT_OF_MEASURE)
|
f.set_enum('order_uom', self.enum.UNIT_OF_MEASURE)
|
||||||
f.set_type('unit_price', 'currency')
|
f.set_type('unit_price', 'currency')
|
||||||
f.set_type('total_price', 'currency')
|
f.set_type('total_price', 'currency')
|
||||||
|
f.set_type('paid_amount', 'currency')
|
||||||
|
|
|
@ -90,6 +90,7 @@ class CustomerOrderItemView(MasterView):
|
||||||
'sequence',
|
'sequence',
|
||||||
'person',
|
'person',
|
||||||
'product',
|
'product',
|
||||||
|
'pending_product',
|
||||||
'product_brand',
|
'product_brand',
|
||||||
'product_description',
|
'product_description',
|
||||||
'product_size',
|
'product_size',
|
||||||
|
@ -178,6 +179,9 @@ class CustomerOrderItemView(MasterView):
|
||||||
# product
|
# product
|
||||||
f.set_renderer('product', self.render_product)
|
f.set_renderer('product', self.render_product)
|
||||||
|
|
||||||
|
# pending_product
|
||||||
|
f.set_renderer('pending_product', self.render_pending_product)
|
||||||
|
|
||||||
# product uom
|
# product uom
|
||||||
f.set_enum('product_unit_of_measure', self.enum.UNIT_OF_MEASURE)
|
f.set_enum('product_unit_of_measure', self.enum.UNIT_OF_MEASURE)
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ class CustomerOrderView(MasterView):
|
||||||
model_class = model.CustomerOrder
|
model_class = model.CustomerOrder
|
||||||
route_prefix = 'custorders'
|
route_prefix = 'custorders'
|
||||||
editable = False
|
editable = False
|
||||||
|
configurable = True
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
'id': "ID",
|
'id': "ID",
|
||||||
|
@ -96,6 +97,10 @@ class CustomerOrderView(MasterView):
|
||||||
'status_code',
|
'status_code',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __init__(self, request):
|
||||||
|
super(CustomerOrderView, self).__init__(request)
|
||||||
|
self.batch_handler = self.get_batch_handler()
|
||||||
|
|
||||||
def query(self, session):
|
def query(self, session):
|
||||||
return session.query(model.CustomerOrder)\
|
return session.query(model.CustomerOrder)\
|
||||||
.options(orm.joinedload(model.CustomerOrder.customer))
|
.options(orm.joinedload(model.CustomerOrder.customer))
|
||||||
|
@ -241,7 +246,8 @@ class CustomerOrderView(MasterView):
|
||||||
submits the order, at which point the batch is converted to a proper
|
submits the order, at which point the batch is converted to a proper
|
||||||
order.
|
order.
|
||||||
"""
|
"""
|
||||||
self.handler = self.get_batch_handler()
|
# TODO: deprecate / remove this
|
||||||
|
self.handler = self.batch_handler
|
||||||
batch = self.get_current_batch()
|
batch = self.get_current_batch()
|
||||||
|
|
||||||
if self.request.method == 'POST':
|
if self.request.method == 'POST':
|
||||||
|
@ -285,16 +291,30 @@ class CustomerOrderView(MasterView):
|
||||||
context.update({
|
context.update({
|
||||||
'batch': batch,
|
'batch': batch,
|
||||||
'normalized_batch': self.normalize_batch(batch),
|
'normalized_batch': self.normalize_batch(batch),
|
||||||
'new_order_requires_customer': self.handler.new_order_requires_customer(),
|
'new_order_requires_customer': self.batch_handler.new_order_requires_customer(),
|
||||||
'product_price_may_be_questionable': self.handler.product_price_may_be_questionable(),
|
'product_key_field': self.rattail_config.product_key(),
|
||||||
'allow_contact_info_choice': self.handler.allow_contact_info_choice(),
|
'product_price_may_be_questionable': self.batch_handler.product_price_may_be_questionable(),
|
||||||
'restrict_contact_info': self.handler.should_restrict_contact_info(),
|
'allow_contact_info_choice': self.batch_handler.allow_contact_info_choice(),
|
||||||
|
'allow_contact_info_create': not self.batch_handler.allow_contact_info_creation(),
|
||||||
'order_items': items,
|
'order_items': items,
|
||||||
'product_key_label': self.rattail_config.product_key_title(),
|
'product_key_label': self.rattail_config.product_key_title(),
|
||||||
|
'allow_unknown_product': self.batch_handler.allow_unknown_product(),
|
||||||
|
'department_options': self.get_department_options(),
|
||||||
})
|
})
|
||||||
|
|
||||||
return self.render_to_response(template, context)
|
return self.render_to_response(template, context)
|
||||||
|
|
||||||
|
def get_department_options(self):
|
||||||
|
model = self.model
|
||||||
|
departments = self.Session.query(model.Department)\
|
||||||
|
.order_by(model.Department.name)\
|
||||||
|
.all()
|
||||||
|
options = []
|
||||||
|
for department in departments:
|
||||||
|
options.append({'label': department.name,
|
||||||
|
'value': department.uuid})
|
||||||
|
return options
|
||||||
|
|
||||||
def get_current_batch(self):
|
def get_current_batch(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
if not user:
|
if not user:
|
||||||
|
@ -311,7 +331,7 @@ class CustomerOrderView(MasterView):
|
||||||
except orm.exc.NoResultFound:
|
except orm.exc.NoResultFound:
|
||||||
# no batch yet for this user, so make one
|
# no batch yet for this user, so make one
|
||||||
|
|
||||||
batch = self.handler.make_batch(
|
batch = self.batch_handler.make_batch(
|
||||||
self.Session(), created_by=user,
|
self.Session(), created_by=user,
|
||||||
mode=self.enum.CUSTORDER_BATCH_MODE_CREATING)
|
mode=self.enum.CUSTORDER_BATCH_MODE_CREATING)
|
||||||
self.Session.add(batch)
|
self.Session.add(batch)
|
||||||
|
@ -320,16 +340,7 @@ class CustomerOrderView(MasterView):
|
||||||
return batch
|
return batch
|
||||||
|
|
||||||
def start_over_entirely(self, batch):
|
def start_over_entirely(self, batch):
|
||||||
|
self.batch_handler.do_delete(batch)
|
||||||
# delete pending customer if present
|
|
||||||
pending = batch.pending_customer
|
|
||||||
if pending:
|
|
||||||
batch.pending_customer = None
|
|
||||||
self.Session.delete(pending)
|
|
||||||
|
|
||||||
# just delete current batch outright
|
|
||||||
# TODO: should use self.handler.do_delete() instead?
|
|
||||||
self.Session.delete(batch)
|
|
||||||
self.Session.flush()
|
self.Session.flush()
|
||||||
|
|
||||||
# send user back to normal "create" page; a new batch will be generated
|
# send user back to normal "create" page; a new batch will be generated
|
||||||
|
@ -339,16 +350,7 @@ class CustomerOrderView(MasterView):
|
||||||
return self.redirect(url)
|
return self.redirect(url)
|
||||||
|
|
||||||
def delete_batch(self, batch):
|
def delete_batch(self, batch):
|
||||||
|
self.batch_handler.do_delete(batch)
|
||||||
# delete pending customer if present
|
|
||||||
pending = batch.pending_customer
|
|
||||||
if pending:
|
|
||||||
batch.pending_customer = None
|
|
||||||
self.Session.delete(pending)
|
|
||||||
|
|
||||||
# just delete current batch outright
|
|
||||||
# TODO: should use self.handler.do_delete() instead?
|
|
||||||
self.Session.delete(batch)
|
|
||||||
self.Session.flush()
|
self.Session.flush()
|
||||||
|
|
||||||
# set flash msg just to be more obvious
|
# set flash msg just to be more obvious
|
||||||
|
@ -363,19 +365,21 @@ class CustomerOrderView(MasterView):
|
||||||
"""
|
"""
|
||||||
Customer autocomplete logic, which invokes the handler.
|
Customer autocomplete logic, which invokes the handler.
|
||||||
"""
|
"""
|
||||||
self.handler = self.get_batch_handler()
|
# TODO: deprecate / remove this
|
||||||
|
self.handler = self.batch_handler
|
||||||
term = self.request.GET['term']
|
term = self.request.GET['term']
|
||||||
return self.handler.customer_autocomplete(self.Session(), term,
|
return self.batch_handler.customer_autocomplete(self.Session(), term,
|
||||||
user=self.request.user)
|
user=self.request.user)
|
||||||
|
|
||||||
def person_autocomplete(self):
|
def person_autocomplete(self):
|
||||||
"""
|
"""
|
||||||
Person autocomplete logic, which invokes the handler.
|
Person autocomplete logic, which invokes the handler.
|
||||||
"""
|
"""
|
||||||
self.handler = self.get_batch_handler()
|
# TODO: deprecate / remove this
|
||||||
|
self.handler = self.batch_handler
|
||||||
term = self.request.GET['term']
|
term = self.request.GET['term']
|
||||||
return self.handler.person_autocomplete(self.Session(), term,
|
return self.batch_handler.person_autocomplete(self.Session(), term,
|
||||||
user=self.request.user)
|
user=self.request.user)
|
||||||
|
|
||||||
def get_customer_info(self, batch, data):
|
def get_customer_info(self, batch, data):
|
||||||
uuid = data.get('uuid')
|
uuid = data.get('uuid')
|
||||||
|
@ -391,7 +395,7 @@ class CustomerOrderView(MasterView):
|
||||||
def info_for_customer(self, batch, data, customer):
|
def info_for_customer(self, batch, data, customer):
|
||||||
|
|
||||||
# most info comes from handler
|
# most info comes from handler
|
||||||
info = self.handler.get_customer_info(batch)
|
info = self.batch_handler.get_customer_info(batch)
|
||||||
|
|
||||||
# maybe add profile URL
|
# maybe add profile URL
|
||||||
if info['person_uuid']:
|
if info['person_uuid']:
|
||||||
|
@ -407,7 +411,7 @@ class CustomerOrderView(MasterView):
|
||||||
# this will either be a Person or Customer UUID
|
# this will either be a Person or Customer UUID
|
||||||
uuid = data['uuid']
|
uuid = data['uuid']
|
||||||
|
|
||||||
if self.handler.new_order_requires_customer():
|
if self.batch_handler.new_order_requires_customer():
|
||||||
|
|
||||||
customer = self.Session.query(model.Customer).get(uuid)
|
customer = self.Session.query(model.Customer).get(uuid)
|
||||||
if not customer:
|
if not customer:
|
||||||
|
@ -423,7 +427,7 @@ class CustomerOrderView(MasterView):
|
||||||
|
|
||||||
# invoke handler to assign contact
|
# invoke handler to assign contact
|
||||||
try:
|
try:
|
||||||
self.handler.assign_contact(batch, **kwargs)
|
self.batch_handler.assign_contact(batch, **kwargs)
|
||||||
except ValueError as error:
|
except ValueError as error:
|
||||||
return {'error': six.text_type(error)}
|
return {'error': six.text_type(error)}
|
||||||
|
|
||||||
|
@ -439,9 +443,9 @@ class CustomerOrderView(MasterView):
|
||||||
'phone_number': batch.phone_number,
|
'phone_number': batch.phone_number,
|
||||||
'contact_display': batch.contact_name,
|
'contact_display': batch.contact_name,
|
||||||
'email_address': batch.email_address,
|
'email_address': batch.email_address,
|
||||||
'contact_phones': self.handler.get_contact_phones(batch),
|
'contact_phones': self.batch_handler.get_contact_phones(batch),
|
||||||
'contact_emails': self.handler.get_contact_emails(batch),
|
'contact_emails': self.batch_handler.get_contact_emails(batch),
|
||||||
'contact_notes': self.handler.get_contact_notes(batch),
|
'contact_notes': self.batch_handler.get_contact_notes(batch),
|
||||||
'add_phone_number': bool(batch.get_param('add_phone_number')),
|
'add_phone_number': bool(batch.get_param('add_phone_number')),
|
||||||
'add_email_address': bool(batch.get_param('add_email_address')),
|
'add_email_address': bool(batch.get_param('add_email_address')),
|
||||||
'contact_profile_url': None,
|
'contact_profile_url': None,
|
||||||
|
@ -467,7 +471,7 @@ class CustomerOrderView(MasterView):
|
||||||
# we have a pending customer then it's definitely *not* known,
|
# we have a pending customer then it's definitely *not* known,
|
||||||
# but if no pending customer yet then we can still "assume" it
|
# but if no pending customer yet then we can still "assume" it
|
||||||
# is known, by default, until user specifies otherwise.
|
# is known, by default, until user specifies otherwise.
|
||||||
contact = self.handler.get_contact(batch)
|
contact = self.batch_handler.get_contact(batch)
|
||||||
if contact:
|
if contact:
|
||||||
context['contact_is_known'] = True
|
context['contact_is_known'] = True
|
||||||
else:
|
else:
|
||||||
|
@ -482,7 +486,7 @@ class CustomerOrderView(MasterView):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def unassign_contact(self, batch, data):
|
def unassign_contact(self, batch, data):
|
||||||
self.handler.unassign_contact(batch)
|
self.batch_handler.unassign_contact(batch)
|
||||||
self.Session.flush()
|
self.Session.flush()
|
||||||
context = self.get_context_contact(batch)
|
context = self.get_context_contact(batch)
|
||||||
context['success'] = True
|
context['success'] = True
|
||||||
|
@ -524,7 +528,8 @@ class CustomerOrderView(MasterView):
|
||||||
def update_pending_customer(self, batch, data):
|
def update_pending_customer(self, batch, data):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.handler.update_pending_customer(batch, self.request.user, data)
|
self.batch_handler.update_pending_customer(batch, self.request.user,
|
||||||
|
data)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
return {'error': six.text_type(error)}
|
return {'error': six.text_type(error)}
|
||||||
|
|
||||||
|
@ -562,11 +567,14 @@ class CustomerOrderView(MasterView):
|
||||||
return self.info_for_product(batch, data, product)
|
return self.info_for_product(batch, data, product)
|
||||||
|
|
||||||
def uom_choices_for_product(self, product):
|
def uom_choices_for_product(self, product):
|
||||||
return self.handler.uom_choices_for_product(product)
|
return self.batch_handler.uom_choices_for_product(product)
|
||||||
|
|
||||||
|
def uom_choices_for_row(self, row):
|
||||||
|
return self.batch_handler.uom_choices_for_row(row)
|
||||||
|
|
||||||
def info_for_product(self, batch, data, product):
|
def info_for_product(self, batch, data, product):
|
||||||
try:
|
try:
|
||||||
info = self.handler.get_product_info(batch, product)
|
info = self.batch_handler.get_product_info(batch, product)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
return {'error': six.text_type(error)}
|
return {'error': six.text_type(error)}
|
||||||
else:
|
else:
|
||||||
|
@ -574,12 +582,12 @@ class CustomerOrderView(MasterView):
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def get_past_items(self, batch, data):
|
def get_past_items(self, batch, data):
|
||||||
past_products = self.handler.get_past_products(batch)
|
past_products = self.batch_handler.get_past_products(batch)
|
||||||
past_items = []
|
past_items = []
|
||||||
|
|
||||||
for product in past_products:
|
for product in past_products:
|
||||||
try:
|
try:
|
||||||
item = self.handler.get_product_info(batch, product)
|
item = self.batch_handler.get_product_info(batch, product)
|
||||||
except:
|
except:
|
||||||
# nb. handler may raise error if product is "unsupported"
|
# nb. handler may raise error if product is "unsupported"
|
||||||
pass
|
pass
|
||||||
|
@ -613,22 +621,20 @@ class CustomerOrderView(MasterView):
|
||||||
|
|
||||||
def normalize_row(self, row):
|
def normalize_row(self, row):
|
||||||
app = self.get_rattail_app()
|
app = self.get_rattail_app()
|
||||||
products = app.get_products_handler()
|
products_handler = app.get_products_handler()
|
||||||
|
|
||||||
product = row.product
|
|
||||||
department = product.department if product else None
|
|
||||||
cost = product.cost if product else None
|
|
||||||
data = {
|
data = {
|
||||||
'uuid': row.uuid,
|
'uuid': row.uuid,
|
||||||
'sequence': row.sequence,
|
'sequence': row.sequence,
|
||||||
'item_entry': row.item_entry,
|
'item_entry': row.item_entry,
|
||||||
'product_uuid': row.product_uuid,
|
'product_uuid': row.product_uuid,
|
||||||
'product_upc': six.text_type(row.product_upc or ''),
|
'product_upc': six.text_type(row.product_upc or ''),
|
||||||
|
'product_item_id': row.product_item_id,
|
||||||
|
'product_scancode': row.product_scancode,
|
||||||
'product_upc_pretty': row.product_upc.pretty() if row.product_upc else None,
|
'product_upc_pretty': row.product_upc.pretty() if row.product_upc else None,
|
||||||
'product_brand': row.product_brand,
|
'product_brand': row.product_brand,
|
||||||
'product_description': row.product_description,
|
'product_description': row.product_description,
|
||||||
'product_size': row.product_size,
|
'product_size': row.product_size,
|
||||||
'product_full_description': product.full_description if product else row.product_description,
|
|
||||||
'product_weighed': row.product_weighed,
|
'product_weighed': row.product_weighed,
|
||||||
|
|
||||||
'case_quantity': pretty_quantity(row.case_quantity),
|
'case_quantity': pretty_quantity(row.case_quantity),
|
||||||
|
@ -636,38 +642,88 @@ class CustomerOrderView(MasterView):
|
||||||
'units_ordered': pretty_quantity(row.units_ordered),
|
'units_ordered': pretty_quantity(row.units_ordered),
|
||||||
'order_quantity': pretty_quantity(row.order_quantity),
|
'order_quantity': pretty_quantity(row.order_quantity),
|
||||||
'order_uom': row.order_uom,
|
'order_uom': row.order_uom,
|
||||||
'order_uom_choices': self.uom_choices_for_product(product),
|
'order_uom_choices': self.uom_choices_for_row(row),
|
||||||
|
|
||||||
'department_display': department.name if department else None,
|
'department_display': row.department_name,
|
||||||
'vendor_display': cost.vendor.name if cost else None,
|
|
||||||
|
|
||||||
'unit_price': six.text_type(row.unit_price) if row.unit_price is not None else None,
|
'unit_price': float(row.unit_price) if row.unit_price is not None else None,
|
||||||
'unit_price_display': self.get_unit_price_display(row),
|
'unit_price_display': self.get_unit_price_display(row),
|
||||||
'total_price': six.text_type(row.total_price) if row.total_price is not None else None,
|
'total_price': float(row.total_price) if row.total_price is not None else None,
|
||||||
'total_price_display': "${:0.2f}".format(row.total_price) if row.total_price is not None else None,
|
'total_price_display': app.render_currency(row.total_price),
|
||||||
|
|
||||||
'status_code': row.status_code,
|
'status_code': row.status_code,
|
||||||
'status_text': row.status_text,
|
'status_text': row.status_text,
|
||||||
}
|
}
|
||||||
|
|
||||||
case_price = self.handler.get_case_price_for_row(row)
|
if row.unit_regular_price:
|
||||||
data['case_price'] = six.text_type(case_price) if case_price is not None else None
|
data['unit_regular_price'] = float(row.unit_regular_price)
|
||||||
|
data['unit_regular_price_display'] = app.render_currency(row.unit_regular_price)
|
||||||
|
|
||||||
|
if row.unit_sale_price:
|
||||||
|
data['unit_sale_price'] = float(row.unit_sale_price)
|
||||||
|
data['unit_sale_price_display'] = app.render_currency(row.unit_sale_price)
|
||||||
|
if row.sale_ends:
|
||||||
|
sale_ends = app.localtime(row.sale_ends, from_utc=True).date()
|
||||||
|
data['sale_ends'] = six.text_type(sale_ends)
|
||||||
|
data['sale_ends_display'] = app.render_date(sale_ends)
|
||||||
|
|
||||||
|
if row.unit_sale_price and row.unit_price == row.unit_sale_price:
|
||||||
|
data['pricing_reflects_sale'] = True
|
||||||
|
|
||||||
|
if row.product or row.pending_product:
|
||||||
|
data['product_full_description'] = products_handler.make_full_description(
|
||||||
|
row.product or row.pending_product)
|
||||||
|
|
||||||
|
if row.product:
|
||||||
|
cost = row.product.cost
|
||||||
|
if cost:
|
||||||
|
data['vendor_display'] = cost.vendor.name
|
||||||
|
elif row.pending_product:
|
||||||
|
data['vendor_display'] = row.pending_product.vendor_name
|
||||||
|
|
||||||
|
if row.pending_product:
|
||||||
|
pending = row.pending_product
|
||||||
|
data['pending_product'] = {
|
||||||
|
'uuid': pending.uuid,
|
||||||
|
'upc': six.text_type(pending.upc) if pending.upc is not None else None,
|
||||||
|
'item_id': pending.item_id,
|
||||||
|
'scancode': pending.scancode,
|
||||||
|
'brand_name': pending.brand_name,
|
||||||
|
'description': pending.description,
|
||||||
|
'size': pending.size,
|
||||||
|
'department_uuid': pending.department_uuid,
|
||||||
|
'regular_price_amount': float(pending.regular_price_amount) if pending.regular_price_amount is not None else None,
|
||||||
|
'vendor_name': pending.vendor_name,
|
||||||
|
'vendor_item_code': pending.vendor_item_code,
|
||||||
|
'unit_cost': float(pending.unit_cost) if pending.unit_cost is not None else None,
|
||||||
|
'case_size': float(pending.case_size) if pending.case_size is not None else None,
|
||||||
|
'notes': pending.notes,
|
||||||
|
}
|
||||||
|
|
||||||
|
case_price = self.batch_handler.get_case_price_for_row(row)
|
||||||
|
data['case_price'] = float(case_price) if case_price is not None else None
|
||||||
data['case_price_display'] = app.render_currency(case_price)
|
data['case_price_display'] = app.render_currency(case_price)
|
||||||
|
|
||||||
if self.handler.product_price_may_be_questionable():
|
if self.batch_handler.product_price_may_be_questionable():
|
||||||
data['price_needs_confirmation'] = row.price_needs_confirmation
|
data['price_needs_confirmation'] = row.price_needs_confirmation
|
||||||
|
|
||||||
key = self.rattail_config.product_key()
|
key = self.rattail_config.product_key()
|
||||||
if key == 'upc':
|
if key == 'upc':
|
||||||
data['product_key'] = data['product_upc_pretty']
|
data['product_key'] = data['product_upc_pretty']
|
||||||
else:
|
elif key == 'item_id':
|
||||||
data['product_key'] = getattr(product, key, data['product_upc_pretty'])
|
data['product_key'] = row.product_item_id
|
||||||
|
elif key == 'scancode':
|
||||||
|
data['product_key'] = row.product_scancode
|
||||||
|
else: # TODO: this seems not useful
|
||||||
|
data['product_key'] = getattr(row.product, key, data['product_upc_pretty'])
|
||||||
|
|
||||||
if row.product:
|
if row.product:
|
||||||
data.update({
|
data.update({
|
||||||
'product_url': self.request.route_url('products.view', uuid=row.product.uuid),
|
'product_url': self.request.route_url('products.view', uuid=row.product.uuid),
|
||||||
'product_image_url': products.get_image_url(row.product),
|
'product_image_url': products_handler.get_image_url(row.product),
|
||||||
})
|
})
|
||||||
|
elif row.product_upc:
|
||||||
|
data['product_image_url'] = products_handler.get_image_url(upc=row.product_upc)
|
||||||
|
|
||||||
unit_uom = self.enum.UNIT_OF_MEASURE_POUND if data['product_weighed'] else self.enum.UNIT_OF_MEASURE_EACH
|
unit_uom = self.enum.UNIT_OF_MEASURE_POUND if data['product_weighed'] else self.enum.UNIT_OF_MEASURE_EACH
|
||||||
if row.order_uom == self.enum.UNIT_OF_MEASURE_CASE:
|
if row.order_uom == self.enum.UNIT_OF_MEASURE_CASE:
|
||||||
|
@ -695,6 +751,11 @@ class CustomerOrderView(MasterView):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def add_item(self, batch, data):
|
def add_item(self, batch, data):
|
||||||
|
app = self.get_rattail_app()
|
||||||
|
|
||||||
|
order_quantity = decimal.Decimal(data.get('order_quantity') or '0')
|
||||||
|
order_uom = data.get('order_uom')
|
||||||
|
|
||||||
if data.get('product_is_known'):
|
if data.get('product_is_known'):
|
||||||
|
|
||||||
uuid = data.get('product_uuid')
|
uuid = data.get('product_uuid')
|
||||||
|
@ -706,18 +767,30 @@ class CustomerOrderView(MasterView):
|
||||||
return {'error': "Product not found"}
|
return {'error': "Product not found"}
|
||||||
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if self.handler.product_price_may_be_questionable():
|
if self.batch_handler.product_price_may_be_questionable():
|
||||||
kwargs['price_needs_confirmation'] = data.get('price_needs_confirmation')
|
kwargs['price_needs_confirmation'] = data.get('price_needs_confirmation')
|
||||||
|
|
||||||
row = self.handler.add_product(batch, product,
|
row = self.batch_handler.add_product(batch, product,
|
||||||
decimal.Decimal(data.get('order_quantity') or '0'),
|
order_quantity, order_uom,
|
||||||
data.get('order_uom'),
|
**kwargs)
|
||||||
**kwargs)
|
|
||||||
self.Session.flush()
|
|
||||||
|
|
||||||
else: # product is not known
|
else: # unknown product; add pending
|
||||||
raise NotImplementedError # TODO
|
pending_info = dict(data['pending_product'])
|
||||||
|
|
||||||
|
if 'upc' in pending_info:
|
||||||
|
pending_info['upc'] = app.make_gpc(pending_info['upc'])
|
||||||
|
|
||||||
|
for field in ('unit_cost', 'regular_price_amount', 'case_size'):
|
||||||
|
if field in pending_info:
|
||||||
|
pending_info[field] = decimal.Decimal(pending_info[field])
|
||||||
|
|
||||||
|
pending_info['user'] = self.request.user
|
||||||
|
|
||||||
|
row = self.batch_handler.add_pending_product(batch,
|
||||||
|
pending_info,
|
||||||
|
order_quantity, order_uom)
|
||||||
|
|
||||||
|
self.Session.flush()
|
||||||
return {'batch': self.normalize_batch(batch),
|
return {'batch': self.normalize_batch(batch),
|
||||||
'row': self.normalize_row(row)}
|
'row': self.normalize_row(row)}
|
||||||
|
|
||||||
|
@ -733,6 +806,9 @@ class CustomerOrderView(MasterView):
|
||||||
if row not in batch.active_rows():
|
if row not in batch.active_rows():
|
||||||
return {'error': "Row is not active for the batch"}
|
return {'error': "Row is not active for the batch"}
|
||||||
|
|
||||||
|
order_quantity = decimal.Decimal(data.get('order_quantity') or '0')
|
||||||
|
order_uom = data.get('order_uom')
|
||||||
|
|
||||||
if data.get('product_is_known'):
|
if data.get('product_is_known'):
|
||||||
|
|
||||||
uuid = data.get('product_uuid')
|
uuid = data.get('product_uuid')
|
||||||
|
@ -745,19 +821,26 @@ class CustomerOrderView(MasterView):
|
||||||
|
|
||||||
row.item_entry = product.uuid
|
row.item_entry = product.uuid
|
||||||
row.product = product
|
row.product = product
|
||||||
row.order_quantity = decimal.Decimal(data.get('order_quantity') or '0')
|
row.order_quantity = order_quantity
|
||||||
row.order_uom = data.get('order_uom')
|
row.order_uom = order_uom
|
||||||
|
|
||||||
if self.handler.product_price_may_be_questionable():
|
if self.batch_handler.product_price_may_be_questionable():
|
||||||
row.price_needs_confirmation = data.get('price_needs_confirmation')
|
row.price_needs_confirmation = data.get('price_needs_confirmation')
|
||||||
|
|
||||||
self.handler.refresh_row(row)
|
self.batch_handler.refresh_row(row)
|
||||||
self.Session.flush()
|
|
||||||
self.Session.refresh(row)
|
|
||||||
|
|
||||||
else: # product is not known
|
else: # product is not known
|
||||||
raise NotImplementedError # TODO
|
|
||||||
|
|
||||||
|
# set these first, since row will be refreshed below
|
||||||
|
row.order_quantity = order_quantity
|
||||||
|
row.order_uom = order_uom
|
||||||
|
|
||||||
|
# nb. this will refresh the row
|
||||||
|
pending_info = dict(data['pending_product'])
|
||||||
|
self.batch_handler.update_pending_product(row, pending_info)
|
||||||
|
|
||||||
|
self.Session.flush()
|
||||||
|
self.Session.refresh(row)
|
||||||
return {'batch': self.normalize_batch(batch),
|
return {'batch': self.normalize_batch(batch),
|
||||||
'row': self.normalize_row(row)}
|
'row': self.normalize_row(row)}
|
||||||
|
|
||||||
|
@ -774,7 +857,7 @@ class CustomerOrderView(MasterView):
|
||||||
if row not in batch.active_rows():
|
if row not in batch.active_rows():
|
||||||
return {'error': "Row is not active for this batch"}
|
return {'error': "Row is not active for this batch"}
|
||||||
|
|
||||||
self.handler.do_remove_row(row)
|
self.batch_handler.do_remove_row(row)
|
||||||
return {'ok': True,
|
return {'ok': True,
|
||||||
'batch': self.normalize_batch(batch)}
|
'batch': self.normalize_batch(batch)}
|
||||||
|
|
||||||
|
@ -794,7 +877,30 @@ class CustomerOrderView(MasterView):
|
||||||
return {'ok': True, 'next_url': next_url}
|
return {'ok': True, 'next_url': next_url}
|
||||||
|
|
||||||
def execute_new_order_batch(self, batch, data):
|
def execute_new_order_batch(self, batch, data):
|
||||||
return self.handler.do_execute(batch, self.request.user)
|
return self.batch_handler.do_execute(batch, self.request.user)
|
||||||
|
|
||||||
|
def configure_get_simple_settings(self):
|
||||||
|
return [
|
||||||
|
|
||||||
|
# customer handling
|
||||||
|
{'section': 'rattail.custorders',
|
||||||
|
'option': 'new_order_requires_customer',
|
||||||
|
'type': bool},
|
||||||
|
{'section': 'rattail.custorders',
|
||||||
|
'option': 'new_orders.allow_contact_info_choice',
|
||||||
|
'type': bool},
|
||||||
|
{'section': 'rattail.custorders',
|
||||||
|
'option': 'new_orders.allow_contact_info_create',
|
||||||
|
'type': bool},
|
||||||
|
|
||||||
|
# product handling
|
||||||
|
{'section': 'rattail.custorders',
|
||||||
|
'option': 'allow_unknown_product',
|
||||||
|
'type': bool},
|
||||||
|
{'section': 'rattail.custorders',
|
||||||
|
'option': 'product_price_may_be_questionable',
|
||||||
|
'type': bool},
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def defaults(cls, config):
|
def defaults(cls, config):
|
||||||
|
|
|
@ -157,6 +157,8 @@ class MasterView(View):
|
||||||
|
|
||||||
labels = {'uuid': "UUID"}
|
labels = {'uuid': "UUID"}
|
||||||
|
|
||||||
|
product_key_fields = {}
|
||||||
|
|
||||||
# ROW-RELATED ATTRS FOLLOW:
|
# ROW-RELATED ATTRS FOLLOW:
|
||||||
|
|
||||||
has_rows = False
|
has_rows = False
|
||||||
|
@ -449,6 +451,8 @@ class MasterView(View):
|
||||||
grid.hide_column('local_only')
|
grid.hide_column('local_only')
|
||||||
grid.remove_filter('local_only')
|
grid.remove_filter('local_only')
|
||||||
|
|
||||||
|
self.configure_column_product_key(grid)
|
||||||
|
|
||||||
def grid_extra_class(self, obj, i):
|
def grid_extra_class(self, obj, i):
|
||||||
"""
|
"""
|
||||||
Returns string of extra class(es) for the table row corresponding to
|
Returns string of extra class(es) for the table row corresponding to
|
||||||
|
@ -541,6 +545,8 @@ class MasterView(View):
|
||||||
# super(MasterView, self).configure_row_grid(grid)
|
# super(MasterView, self).configure_row_grid(grid)
|
||||||
self.set_row_labels(grid)
|
self.set_row_labels(grid)
|
||||||
|
|
||||||
|
self.configure_column_product_key(grid)
|
||||||
|
|
||||||
def row_grid_extra_class(self, obj, i):
|
def row_grid_extra_class(self, obj, i):
|
||||||
"""
|
"""
|
||||||
Returns string of extra class(es) for the table row corresponding to
|
Returns string of extra class(es) for the table row corresponding to
|
||||||
|
@ -753,6 +759,7 @@ class MasterView(View):
|
||||||
if obj.emails:
|
if obj.emails:
|
||||||
return obj.emails[0].address
|
return obj.emails[0].address
|
||||||
|
|
||||||
|
# TODO: deprecate / remove this
|
||||||
def render_product_key_value(self, obj, field=None):
|
def render_product_key_value(self, obj, field=None):
|
||||||
"""
|
"""
|
||||||
Render the "canonical" product key value for the given object.
|
Render the "canonical" product key value for the given object.
|
||||||
|
@ -764,6 +771,15 @@ class MasterView(View):
|
||||||
return obj.upc.pretty() if obj.upc else ''
|
return obj.upc.pretty() if obj.upc else ''
|
||||||
return getattr(obj, product_key)
|
return getattr(obj, product_key)
|
||||||
|
|
||||||
|
def render_upc(self, obj, field):
|
||||||
|
"""
|
||||||
|
Render a :class:`~rattail:rattail.gpc.GPC` field.
|
||||||
|
"""
|
||||||
|
value = getattr(obj, field)
|
||||||
|
if value:
|
||||||
|
app = self.rattail_config.get_app()
|
||||||
|
return app.render_gpc(value)
|
||||||
|
|
||||||
def render_store(self, obj, field):
|
def render_store(self, obj, field):
|
||||||
store = getattr(obj, field)
|
store = getattr(obj, field)
|
||||||
if store:
|
if store:
|
||||||
|
@ -779,6 +795,14 @@ class MasterView(View):
|
||||||
url = self.request.route_url('products.view', uuid=product.uuid)
|
url = self.request.route_url('products.view', uuid=product.uuid)
|
||||||
return tags.link_to(text, url)
|
return tags.link_to(text, url)
|
||||||
|
|
||||||
|
def render_pending_product(self, obj, field):
|
||||||
|
pending = getattr(obj, field)
|
||||||
|
if not pending:
|
||||||
|
return
|
||||||
|
text = six.text_type(pending)
|
||||||
|
url = self.request.route_url('pending_products.view', uuid=pending.uuid)
|
||||||
|
return tags.link_to(text, url)
|
||||||
|
|
||||||
def render_vendor(self, obj, field):
|
def render_vendor(self, obj, field):
|
||||||
vendor = getattr(obj, field)
|
vendor = getattr(obj, field)
|
||||||
if not vendor:
|
if not vendor:
|
||||||
|
@ -1567,6 +1591,8 @@ class MasterView(View):
|
||||||
return self.render_to_response('delete', {
|
return self.render_to_response('delete', {
|
||||||
'instance': instance,
|
'instance': instance,
|
||||||
'instance_title': instance_title,
|
'instance_title': instance_title,
|
||||||
|
'instance_editable': self.editable_instance(instance),
|
||||||
|
'instance_deletable': self.deletable_instance(instance),
|
||||||
'form': form})
|
'form': form})
|
||||||
|
|
||||||
def bulk_delete(self):
|
def bulk_delete(self):
|
||||||
|
@ -3676,6 +3702,8 @@ class MasterView(View):
|
||||||
"""
|
"""
|
||||||
self.configure_common_form(form)
|
self.configure_common_form(form)
|
||||||
|
|
||||||
|
self.configure_field_product_key(form)
|
||||||
|
|
||||||
def validate_form(self, form):
|
def validate_form(self, form):
|
||||||
if form.validate(newstyle=True):
|
if form.validate(newstyle=True):
|
||||||
self.form_deserialized = form.validated
|
self.form_deserialized = form.validated
|
||||||
|
@ -4107,12 +4135,33 @@ class MasterView(View):
|
||||||
|
|
||||||
self.set_row_labels(form)
|
self.set_row_labels(form)
|
||||||
|
|
||||||
|
self.configure_field_product_key(form)
|
||||||
|
|
||||||
def validate_row_form(self, form):
|
def validate_row_form(self, form):
|
||||||
if form.validate(newstyle=True):
|
if form.validate(newstyle=True):
|
||||||
self.form_deserialized = form.validated
|
self.form_deserialized = form.validated
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def configure_column_product_key(self, g):
|
||||||
|
if '_product_key_' in g.columns:
|
||||||
|
key = self.rattail_config.product_key()
|
||||||
|
field = self.product_key_fields.get(key, key)
|
||||||
|
g.replace('_product_key_', field)
|
||||||
|
g.set_label(field, self.rattail_config.product_key_title(key))
|
||||||
|
g.set_link(field)
|
||||||
|
if key == 'upc':
|
||||||
|
g.set_renderer(field, self.render_upc)
|
||||||
|
|
||||||
|
def configure_field_product_key(self, f):
|
||||||
|
if '_product_key_' in f:
|
||||||
|
key = self.rattail_config.product_key()
|
||||||
|
field = self.product_key_fields.get(key, key)
|
||||||
|
f.replace('_product_key_', field)
|
||||||
|
f.set_label(field, self.rattail_config.product_key_title(key))
|
||||||
|
if key == 'upc':
|
||||||
|
f.set_renderer(field, self.render_upc)
|
||||||
|
|
||||||
def get_row_action_url(self, action, row, **kwargs):
|
def get_row_action_url(self, action, row, **kwargs):
|
||||||
"""
|
"""
|
||||||
Generate a URL for the given action on the given row.
|
Generate a URL for the given action on the given row.
|
||||||
|
|
|
@ -1153,6 +1153,7 @@ class ProductView(MasterView):
|
||||||
use_buefy = self.get_use_buefy()
|
use_buefy = self.get_use_buefy()
|
||||||
|
|
||||||
kwargs['image_url'] = self.handler.get_image_url(product)
|
kwargs['image_url'] = self.handler.get_image_url(product)
|
||||||
|
kwargs['product_key_field'] = self.rattail_config.product_key()
|
||||||
|
|
||||||
# add price history, if user has access
|
# add price history, if user has access
|
||||||
if self.rattail_config.versioning_enabled() and self.has_perm('versions'):
|
if self.rattail_config.versioning_enabled() and self.has_perm('versions'):
|
||||||
|
@ -1910,7 +1911,6 @@ class ProductView(MasterView):
|
||||||
return self.request.route_url('batch.delproduct.view', uuid=batch.uuid)
|
return self.request.route_url('batch.delproduct.view', uuid=batch.uuid)
|
||||||
|
|
||||||
def configure_get_simple_settings(self):
|
def configure_get_simple_settings(self):
|
||||||
config = self.rattail_config
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
# key field
|
# key field
|
||||||
|
@ -1987,6 +1987,7 @@ class PendingProductView(MasterView):
|
||||||
model_class = model.PendingProduct
|
model_class = model.PendingProduct
|
||||||
route_prefix = 'pending_products'
|
route_prefix = 'pending_products'
|
||||||
url_prefix = '/products/pending'
|
url_prefix = '/products/pending'
|
||||||
|
bulk_deletable = True
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
'regular_price_amount': "Regular Price",
|
'regular_price_amount': "Regular Price",
|
||||||
|
@ -1996,10 +1997,10 @@ class PendingProductView(MasterView):
|
||||||
|
|
||||||
grid_columns = [
|
grid_columns = [
|
||||||
'_product_key_',
|
'_product_key_',
|
||||||
'department_name',
|
|
||||||
'brand_name',
|
'brand_name',
|
||||||
'description',
|
'description',
|
||||||
'size',
|
'size',
|
||||||
|
'department_name',
|
||||||
'created',
|
'created',
|
||||||
'user',
|
'user',
|
||||||
'status_code',
|
'status_code',
|
||||||
|
@ -2007,12 +2008,15 @@ class PendingProductView(MasterView):
|
||||||
|
|
||||||
form_fields = [
|
form_fields = [
|
||||||
'_product_key_',
|
'_product_key_',
|
||||||
'department_name',
|
|
||||||
'department',
|
|
||||||
'brand_name',
|
'brand_name',
|
||||||
'brand',
|
'brand',
|
||||||
'description',
|
'description',
|
||||||
'size',
|
'size',
|
||||||
|
'department_name',
|
||||||
|
'department',
|
||||||
|
'vendor_name',
|
||||||
|
'vendor',
|
||||||
|
'unit_cost',
|
||||||
'case_size',
|
'case_size',
|
||||||
'regular_price_amount',
|
'regular_price_amount',
|
||||||
'special_order',
|
'special_order',
|
||||||
|
@ -2025,13 +2029,6 @@ class PendingProductView(MasterView):
|
||||||
def configure_grid(self, g):
|
def configure_grid(self, g):
|
||||||
super(PendingProductView, self).configure_grid(g)
|
super(PendingProductView, self).configure_grid(g)
|
||||||
|
|
||||||
# product key
|
|
||||||
if '_product_key_' in g.columns:
|
|
||||||
key = self.rattail_config.product_key()
|
|
||||||
g.replace('_product_key_', key)
|
|
||||||
g.set_label(key, self.rattail_config.product_key_title(key))
|
|
||||||
g.set_link(key)
|
|
||||||
|
|
||||||
g.set_enum('status_code', self.enum.PENDING_PRODUCT_STATUS)
|
g.set_enum('status_code', self.enum.PENDING_PRODUCT_STATUS)
|
||||||
|
|
||||||
g.set_sort_defaults('created', 'desc')
|
g.set_sort_defaults('created', 'desc')
|
||||||
|
@ -2043,13 +2040,6 @@ class PendingProductView(MasterView):
|
||||||
model = self.model
|
model = self.model
|
||||||
pending = f.model_instance
|
pending = f.model_instance
|
||||||
|
|
||||||
# product key
|
|
||||||
if '_product_key_' in f:
|
|
||||||
key = self.rattail_config.product_key()
|
|
||||||
f.replace('_product_key_', key)
|
|
||||||
f.set_label(key, self.rattail_config.product_key_title(key))
|
|
||||||
f.set_renderer(key, self.render_product_key_value)
|
|
||||||
|
|
||||||
# department
|
# department
|
||||||
if self.creating or self.editing:
|
if self.creating or self.editing:
|
||||||
if 'department' in f:
|
if 'department' in f:
|
||||||
|
@ -2084,10 +2074,35 @@ class PendingProductView(MasterView):
|
||||||
f.set_renderer('brand', self.render_brand)
|
f.set_renderer('brand', self.render_brand)
|
||||||
if pending.brand:
|
if pending.brand:
|
||||||
f.remove('brand_name')
|
f.remove('brand_name')
|
||||||
|
elif pending.brand_name:
|
||||||
|
f.remove('brand')
|
||||||
|
|
||||||
# description
|
# description
|
||||||
f.set_required('description')
|
f.set_required('description')
|
||||||
|
|
||||||
|
# vendor
|
||||||
|
if self.creating or self.editing:
|
||||||
|
if 'vendor' in f:
|
||||||
|
f.remove('vendor_name')
|
||||||
|
f.replace('vendor', 'vendor_uuid')
|
||||||
|
f.set_node('vendor_uuid', colander.String())
|
||||||
|
vendor_display = ""
|
||||||
|
if self.request.method == 'POST':
|
||||||
|
if self.request.POST.get('vendor_uuid'):
|
||||||
|
vendor = self.Session.query(model.Vendor).get(self.request.POST['vendor_uuid'])
|
||||||
|
if vendor:
|
||||||
|
vendor_display = six.text_type(vendor)
|
||||||
|
f.set_widget('vendor_uuid', forms.widgets.JQueryAutocompleteWidget(
|
||||||
|
field_display=vendor_display,
|
||||||
|
service_url=self.request.route_url('vendors.autocomplete')))
|
||||||
|
f.set_label('vendor_uuid', "Vendor")
|
||||||
|
else:
|
||||||
|
f.set_renderer('vendor', self.render_vendor)
|
||||||
|
if pending.vendor:
|
||||||
|
f.remove('vendor_name')
|
||||||
|
elif pending.vendor_name:
|
||||||
|
f.remove('vendor')
|
||||||
|
|
||||||
# case_size
|
# case_size
|
||||||
f.set_type('case_size', 'quantity')
|
f.set_type('case_size', 'quantity')
|
||||||
|
|
||||||
|
@ -2138,6 +2153,30 @@ class PendingProductView(MasterView):
|
||||||
|
|
||||||
return pending
|
return pending
|
||||||
|
|
||||||
|
def before_delete(self, pending):
|
||||||
|
"""
|
||||||
|
Event hook, called just before deletion is attempted.
|
||||||
|
"""
|
||||||
|
model = self.model
|
||||||
|
model_title = self.get_model_title()
|
||||||
|
count = self.Session.query(model.CustomerOrderItem)\
|
||||||
|
.filter(model.CustomerOrderItem.pending_product == pending)\
|
||||||
|
.count()
|
||||||
|
if count:
|
||||||
|
self.request.session.flash("Cannot delete this {} because it is still "
|
||||||
|
"referenced by {} Customer Orders.".format(model_title, count),
|
||||||
|
'error')
|
||||||
|
return self.redirect(self.get_action_url('view', pending))
|
||||||
|
|
||||||
|
count = self.Session.query(model.CustomerOrderBatchRow)\
|
||||||
|
.filter(model.CustomerOrderBatchRow.pending_product == pending)\
|
||||||
|
.count()
|
||||||
|
if count:
|
||||||
|
self.request.session.flash("Cannot delete this {} because it is still "
|
||||||
|
"referenced by {} \"new\" Customer Order Batches.".format(model_title, count),
|
||||||
|
'error')
|
||||||
|
return self.redirect(self.get_action_url('view', pending))
|
||||||
|
|
||||||
|
|
||||||
def print_labels(request):
|
def print_labels(request):
|
||||||
profile = request.params.get('profile')
|
profile = request.params.get('profile')
|
||||||
|
|
Loading…
Reference in a new issue