Add basic/unfinished "new customer order" page/feature
so far creates the order batch, and can set some customer info
This commit is contained in:
parent
c32f47ba95
commit
808e737202
426
tailbone/templates/custorders/create.mako
Normal file
426
tailbone/templates/custorders/create.mako
Normal file
|
@ -0,0 +1,426 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/master/create.mako" />
|
||||||
|
|
||||||
|
<%def name="extra_styles()">
|
||||||
|
${parent.extra_styles()}
|
||||||
|
% if use_buefy:
|
||||||
|
<style type="text/css">
|
||||||
|
.this-page-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="page_content()">
|
||||||
|
<br />
|
||||||
|
% if use_buefy:
|
||||||
|
<customer-order-creator></customer-order-creator>
|
||||||
|
% else:
|
||||||
|
<p>Sorry, but this page is not supported by your current theme configuration.</p>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="order_form_buttons()">
|
||||||
|
<div class="buttons">
|
||||||
|
<b-button type="is-primary"
|
||||||
|
@click="submitOrder()"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="fas fa-upload">
|
||||||
|
Submit this Order
|
||||||
|
</b-button>
|
||||||
|
<b-button @click="startOverEntirely()"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="fas fa-redo">
|
||||||
|
Start Over Entirely
|
||||||
|
</b-button>
|
||||||
|
<b-button @click="cancelOrder()"
|
||||||
|
type="is-danger"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="fas fa-trash">
|
||||||
|
Cancel this Order
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_this_page_template()">
|
||||||
|
${parent.render_this_page_template()}
|
||||||
|
|
||||||
|
<script type="text/x-template" id="customer-order-creator-template">
|
||||||
|
<div>
|
||||||
|
|
||||||
|
${self.order_form_buttons()}
|
||||||
|
|
||||||
|
<b-collapse class="panel" :class="customerPanelType"
|
||||||
|
:open.sync="customerPanelOpen">
|
||||||
|
|
||||||
|
<div slot="trigger"
|
||||||
|
slot-scope="props"
|
||||||
|
class="panel-heading"
|
||||||
|
role="button">
|
||||||
|
<b-icon pack="fas"
|
||||||
|
## TODO: this icon toggling should work, according to
|
||||||
|
## Buefy docs, but i could not ever get it to work.
|
||||||
|
## what am i missing?
|
||||||
|
## https://buefy.org/documentation/collapse/
|
||||||
|
## :icon="props.open ? 'caret-down' : 'caret-right'">
|
||||||
|
## (for now we just always show caret-right instead)
|
||||||
|
icon="caret-right">
|
||||||
|
</b-icon>
|
||||||
|
<strong v-html="customerPanelHeader"></strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-block">
|
||||||
|
<div style="width: 100%;">
|
||||||
|
|
||||||
|
<div style="display: flex; flex-direction: row;">
|
||||||
|
<div style="flex-grow: 1; margin-right: 1rem;">
|
||||||
|
<b-notification :type="customerStatusType"
|
||||||
|
position="is-bottom-right"
|
||||||
|
:closable="false">
|
||||||
|
{{ customerStatusText }}
|
||||||
|
</b-notification>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="buttons"> -->
|
||||||
|
<!-- <b-button @click="startOverCustomer()" -->
|
||||||
|
<!-- icon-pack="fas" -->
|
||||||
|
<!-- icon-left="fas fa-redo"> -->
|
||||||
|
<!-- Start Over -->
|
||||||
|
<!-- </b-button> -->
|
||||||
|
<!-- </div> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<div class="field">
|
||||||
|
<b-radio v-model="customerIsKnown"
|
||||||
|
:native-value="true">
|
||||||
|
Customer is already in the system.
|
||||||
|
</b-radio>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="customerIsKnown">
|
||||||
|
<b-field label="Customer Name" horizontal>
|
||||||
|
<tailbone-autocomplete
|
||||||
|
ref="customerAutocomplete"
|
||||||
|
v-model="customerUUID"
|
||||||
|
:initial-label="customerDisplay"
|
||||||
|
serviceUrl="${url('customers.autocomplete')}"
|
||||||
|
@input="customerChanged">
|
||||||
|
</tailbone-autocomplete>
|
||||||
|
</b-field>
|
||||||
|
<b-field label="Phone Number" horizontal>
|
||||||
|
<b-input v-model="phoneNumberEntry"
|
||||||
|
@input="phoneNumberChanged"
|
||||||
|
@keydown.native="phoneNumberKeyDown">
|
||||||
|
</b-input>
|
||||||
|
<b-button v-if="!phoneNumberSaved"
|
||||||
|
type="is-primary"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="fas fa-save"
|
||||||
|
@click="setCustomerData()">
|
||||||
|
Please save when finished editing
|
||||||
|
</b-button>
|
||||||
|
<!-- <tailbone-autocomplete -->
|
||||||
|
<!-- serviceUrl="${url('customers.autocomplete.phone')}"> -->
|
||||||
|
<!-- </tailbone-autocomplete> -->
|
||||||
|
</b-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<div class="field">
|
||||||
|
<b-radio v-model="customerIsKnown" disabled
|
||||||
|
:native-value="false">
|
||||||
|
Customer is not yet in the system.
|
||||||
|
</b-radio>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!customerIsKnown">
|
||||||
|
<b-field label="Customer Name" horizontal>
|
||||||
|
<b-input v-model="customerName"></b-input>
|
||||||
|
</b-field>
|
||||||
|
<b-field label="Phone Number" horizontal>
|
||||||
|
<b-input v-model="phoneNumber"></b-input>
|
||||||
|
</b-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div> <!-- panel-block -->
|
||||||
|
</b-collapse>
|
||||||
|
|
||||||
|
<b-collapse class="panel"
|
||||||
|
open>
|
||||||
|
|
||||||
|
<div slot="trigger"
|
||||||
|
slot-scope="props"
|
||||||
|
class="panel-heading"
|
||||||
|
role="button">
|
||||||
|
<b-icon pack="fas"
|
||||||
|
## TODO: this icon toggling should work, according to
|
||||||
|
## Buefy docs, but i could not ever get it to work.
|
||||||
|
## what am i missing?
|
||||||
|
## https://buefy.org/documentation/collapse/
|
||||||
|
## :icon="props.open ? 'caret-down' : 'caret-right'">
|
||||||
|
## (for now we just always show caret-right instead)
|
||||||
|
icon="caret-right">
|
||||||
|
</b-icon>
|
||||||
|
<strong>Items</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-block">
|
||||||
|
<div>
|
||||||
|
TODO: items go here
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
|
||||||
|
${self.order_form_buttons()}
|
||||||
|
|
||||||
|
${h.form(request.current_route_url(), ref='batchActionForm')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.hidden('action', **{'v-model': 'batchAction'})}
|
||||||
|
${h.end_form()}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="make_this_page_component()">
|
||||||
|
${parent.make_this_page_component()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
const CustomerOrderCreator = {
|
||||||
|
template: '#customer-order-creator-template',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
batchAction: null,
|
||||||
|
|
||||||
|
customerPanelOpen: true,
|
||||||
|
customerIsKnown: true,
|
||||||
|
customerUUID: ${json.dumps(batch.customer_uuid)|n},
|
||||||
|
customerDisplay: ${json.dumps(six.text_type(batch.customer or ''))|n},
|
||||||
|
customerEntry: null,
|
||||||
|
phoneNumberEntry: ${json.dumps(batch.phone_number)|n},
|
||||||
|
phoneNumberSaved: true,
|
||||||
|
customerName: null,
|
||||||
|
phoneNumber: null,
|
||||||
|
|
||||||
|
## 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},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
customerPanelHeader() {
|
||||||
|
let text = "Customer"
|
||||||
|
|
||||||
|
if (this.customerIsKnown) {
|
||||||
|
if (this.customerUUID) {
|
||||||
|
if (this.$refs.customerAutocomplete) {
|
||||||
|
text = "Customer: " + this.$refs.customerAutocomplete.getDisplayText()
|
||||||
|
} else {
|
||||||
|
text = "Customer: " + this.customerDisplay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.customerName) {
|
||||||
|
text = "Customer: " + this.customerName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.customerPanelOpen) {
|
||||||
|
text += ' <p class="' + this.customerHeaderClass + '" style="display: inline-block; float: right;">' + this.customerStatusText + '</p>'
|
||||||
|
}
|
||||||
|
|
||||||
|
return text
|
||||||
|
},
|
||||||
|
customerHeaderClass() {
|
||||||
|
if (!this.customerPanelOpen) {
|
||||||
|
if (this.customerStatusType == 'is-danger') {
|
||||||
|
return 'has-text-danger'
|
||||||
|
} else if (this.customerStatusType == 'is-warning') {
|
||||||
|
return 'has-text-warning'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customerPanelType() {
|
||||||
|
if (!this.customerPanelOpen) {
|
||||||
|
return this.customerStatusType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customerStatusType() {
|
||||||
|
return this.customerStatusTypeAndText.type
|
||||||
|
},
|
||||||
|
customerStatusText() {
|
||||||
|
return this.customerStatusTypeAndText.text
|
||||||
|
},
|
||||||
|
customerStatusTypeAndText() {
|
||||||
|
let phoneNumber = null
|
||||||
|
if (this.customerIsKnown) {
|
||||||
|
if (!this.customerUUID) {
|
||||||
|
return {
|
||||||
|
type: 'is-danger',
|
||||||
|
text: "Please identify the customer.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.phoneNumberEntry) {
|
||||||
|
return {
|
||||||
|
type: 'is-warning',
|
||||||
|
text: "Please provide a phone number for the customer.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
phoneNumber = this.phoneNumberEntry
|
||||||
|
} else { // customer is not known
|
||||||
|
if (!this.customerName) {
|
||||||
|
return {
|
||||||
|
type: 'is-danger',
|
||||||
|
text: "Please identify the customer.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.phoneNumber) {
|
||||||
|
return {
|
||||||
|
type: 'is-warning',
|
||||||
|
text: "Please provide a phone number for the customer.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
phoneNumber = this.phoneNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
let phoneDigits = phoneNumber.replace(/\D/g, '')
|
||||||
|
if (!phoneDigits.length || (phoneDigits.length != 7 && phoneDigits.length != 10)) {
|
||||||
|
return {
|
||||||
|
type: 'is-warning',
|
||||||
|
text: "The phone number does not appear to be valid.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.customerIsKnown) {
|
||||||
|
return {
|
||||||
|
type: 'is-warning',
|
||||||
|
text: "Will create a new customer record.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: null,
|
||||||
|
text: "Everything seems to be okay here.",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
startOverEntirely() {
|
||||||
|
let msg = "Are you sure you want to start over entirely?\n\n"
|
||||||
|
+ "This will totally delete this order and start a new one."
|
||||||
|
if (!confirm(msg)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.batchAction = 'start_over_entirely'
|
||||||
|
this.$nextTick(function() {
|
||||||
|
this.$refs.batchActionForm.submit()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// startOverCustomer(confirmed) {
|
||||||
|
// if (!confirmed) {
|
||||||
|
// let msg = "Are you sure you want to start over for the customer data?"
|
||||||
|
// if (!confirm(msg)) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// this.customerIsKnown = true
|
||||||
|
// this.customerUUID = null
|
||||||
|
// // this.customerEntry = null
|
||||||
|
// this.phoneNumberEntry = null
|
||||||
|
// this.customerName = null
|
||||||
|
// this.phoneNumber = null
|
||||||
|
// },
|
||||||
|
|
||||||
|
// startOverItem(confirmed) {
|
||||||
|
// if (!confirmed) {
|
||||||
|
// let msg = "Are you sure you want to start over for the item data?"
|
||||||
|
// if (!confirm(msg)) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // TODO: reset things
|
||||||
|
// },
|
||||||
|
|
||||||
|
cancelOrder() {
|
||||||
|
let msg = "Are you sure you want to cancel?\n\n"
|
||||||
|
+ "This will totally delete the current order."
|
||||||
|
if (!confirm(msg)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.batchAction = 'delete_batch'
|
||||||
|
this.$nextTick(function() {
|
||||||
|
this.$refs.batchActionForm.submit()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
submitBatchData(params, callback) {
|
||||||
|
let url = ${json.dumps(request.current_route_url())|n}
|
||||||
|
|
||||||
|
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((response) => {
|
||||||
|
if (callback) {
|
||||||
|
callback(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
setCustomerData() {
|
||||||
|
let params = {
|
||||||
|
action: 'set_customer_data',
|
||||||
|
customer_uuid: this.customerUUID,
|
||||||
|
phone_number: this.phoneNumberEntry,
|
||||||
|
}
|
||||||
|
let that = this
|
||||||
|
this.submitBatchData(params, function(response) {
|
||||||
|
that.phoneNumberSaved = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
submitOrder() {
|
||||||
|
alert("okay then!")
|
||||||
|
},
|
||||||
|
|
||||||
|
customerChanged(uuid) {
|
||||||
|
if (!uuid) {
|
||||||
|
this.phoneNumberEntry = null
|
||||||
|
this.setCustomerData()
|
||||||
|
} else {
|
||||||
|
let params = {
|
||||||
|
action: 'get_customer_info',
|
||||||
|
uuid: this.customerUUID,
|
||||||
|
}
|
||||||
|
let that = this
|
||||||
|
this.submitBatchData(params, function(response) {
|
||||||
|
that.phoneNumberEntry = response.data.phone_number
|
||||||
|
that.setCustomerData()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
phoneNumberChanged(value) {
|
||||||
|
this.phoneNumberSaved = false
|
||||||
|
},
|
||||||
|
|
||||||
|
phoneNumberKeyDown(event) {
|
||||||
|
if (event.which == 13) { // Enter
|
||||||
|
this.setCustomerData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.component('customer-order-creator', CustomerOrderCreator)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -26,8 +26,13 @@ Base class for customer order batch views
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
|
|
||||||
|
import colander
|
||||||
|
|
||||||
|
from tailbone import forms
|
||||||
from tailbone.views.batch import BatchMasterView
|
from tailbone.views.batch import BatchMasterView
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,3 +44,79 @@ class CustomerOrderBatchView(BatchMasterView):
|
||||||
model_class = model.CustomerOrderBatch
|
model_class = model.CustomerOrderBatch
|
||||||
model_row_class = model.CustomerOrderBatchRow
|
model_row_class = model.CustomerOrderBatchRow
|
||||||
default_handler_spec = 'rattail.batch.custorder:CustomerOrderBatchHandler'
|
default_handler_spec = 'rattail.batch.custorder:CustomerOrderBatchHandler'
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
'id',
|
||||||
|
'customer',
|
||||||
|
'rows',
|
||||||
|
'created',
|
||||||
|
'created_by',
|
||||||
|
]
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
'id',
|
||||||
|
'customer',
|
||||||
|
'person',
|
||||||
|
'phone_number',
|
||||||
|
'email_address',
|
||||||
|
'created',
|
||||||
|
'created_by',
|
||||||
|
'rows',
|
||||||
|
'status_code',
|
||||||
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
super(CustomerOrderBatchView, self).configure_grid(g)
|
||||||
|
|
||||||
|
g.set_link('customer')
|
||||||
|
g.set_link('created')
|
||||||
|
g.set_link('created_by')
|
||||||
|
|
||||||
|
def configure_form(self, f):
|
||||||
|
super(CustomerOrderBatchView, self).configure_form(f)
|
||||||
|
order = f.model_instance
|
||||||
|
model = self.rattail_config.get_model()
|
||||||
|
|
||||||
|
# readonly fields
|
||||||
|
f.set_readonly('rows')
|
||||||
|
f.set_readonly('status_code')
|
||||||
|
|
||||||
|
# customer
|
||||||
|
if 'customer' in f.fields and self.editing:
|
||||||
|
f.replace('customer', 'customer_uuid')
|
||||||
|
f.set_node('customer_uuid', colander.String(), missing=colander.null)
|
||||||
|
customer_display = ""
|
||||||
|
if self.request.method == 'POST':
|
||||||
|
if self.request.POST.get('customer_uuid'):
|
||||||
|
customer = self.Session.query(model.Customer)\
|
||||||
|
.get(self.request.POST['customer_uuid'])
|
||||||
|
if customer:
|
||||||
|
customer_display = six.text_type(customer)
|
||||||
|
elif self.editing:
|
||||||
|
customer_display = six.text_type(order.customer or "")
|
||||||
|
customers_url = self.request.route_url('customers.autocomplete')
|
||||||
|
f.set_widget('customer_uuid', forms.widgets.JQueryAutocompleteWidget(
|
||||||
|
field_display=customer_display, service_url=customers_url))
|
||||||
|
f.set_label('customer_uuid', "Customer")
|
||||||
|
else:
|
||||||
|
f.set_renderer('customer', self.render_customer)
|
||||||
|
|
||||||
|
# person
|
||||||
|
if 'person' in f.fields and self.editing:
|
||||||
|
f.replace('person', 'person_uuid')
|
||||||
|
f.set_node('person_uuid', colander.String(), missing=colander.null)
|
||||||
|
person_display = ""
|
||||||
|
if self.request.method == 'POST':
|
||||||
|
if self.request.POST.get('person_uuid'):
|
||||||
|
person = self.Session.query(model.Person)\
|
||||||
|
.get(self.request.POST['person_uuid'])
|
||||||
|
if person:
|
||||||
|
person_display = six.text_type(person)
|
||||||
|
elif self.editing:
|
||||||
|
person_display = six.text_type(order.person or "")
|
||||||
|
people_url = self.request.route_url('people.autocomplete')
|
||||||
|
f.set_widget('person_uuid', forms.widgets.JQueryAutocompleteWidget(
|
||||||
|
field_display=person_display, service_url=people_url))
|
||||||
|
f.set_label('person_uuid', "Person")
|
||||||
|
else:
|
||||||
|
f.set_renderer('person', self.render_person)
|
||||||
|
|
|
@ -22,6 +22,11 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
"""
|
"""
|
||||||
Views for 'creating' customer order batches
|
Views for 'creating' customer order batches
|
||||||
|
|
||||||
|
Note that this provides only the "direct" or "raw" table views for these
|
||||||
|
batches. This does *not* provide a way to create a new batch; you should see
|
||||||
|
:meth:`tailbone.views.custorders.orders.CustomerOrdersView.create()` for that
|
||||||
|
logic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2018 Lance Edgar
|
# Copyright © 2010-2020 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -43,7 +43,6 @@ class CustomerOrdersView(MasterView):
|
||||||
"""
|
"""
|
||||||
model_class = model.CustomerOrder
|
model_class = model.CustomerOrder
|
||||||
route_prefix = 'custorders'
|
route_prefix = 'custorders'
|
||||||
creatable = False
|
|
||||||
editable = False
|
editable = False
|
||||||
deletable = False
|
deletable = False
|
||||||
|
|
||||||
|
@ -59,6 +58,8 @@ class CustomerOrdersView(MasterView):
|
||||||
'id',
|
'id',
|
||||||
'customer',
|
'customer',
|
||||||
'person',
|
'person',
|
||||||
|
'phone_number',
|
||||||
|
'email_address',
|
||||||
'created',
|
'created',
|
||||||
'status_code',
|
'status_code',
|
||||||
]
|
]
|
||||||
|
@ -115,6 +116,130 @@ class CustomerOrdersView(MasterView):
|
||||||
url = self.request.route_url('people.view', uuid=person.uuid)
|
url = self.request.route_url('people.view', uuid=person.uuid)
|
||||||
return tags.link_to(text, url)
|
return tags.link_to(text, url)
|
||||||
|
|
||||||
|
def create(self, form=None, template='create'):
|
||||||
|
"""
|
||||||
|
View for creating a new customer order. Note that it does so by way of
|
||||||
|
maintaining a "new customer order" batch, until the user finally
|
||||||
|
submits the order, at which point the batch is converted to a proper
|
||||||
|
order.
|
||||||
|
"""
|
||||||
|
batch = self.get_current_batch()
|
||||||
|
|
||||||
|
if self.request.method == 'POST':
|
||||||
|
|
||||||
|
# first we check for traditional form post
|
||||||
|
action = self.request.POST.get('action')
|
||||||
|
post_actions = [
|
||||||
|
'start_over_entirely',
|
||||||
|
'delete_batch',
|
||||||
|
]
|
||||||
|
if action in post_actions:
|
||||||
|
return getattr(self, action)(batch)
|
||||||
|
|
||||||
|
# okay then, we'll assume newer JSON-style post params
|
||||||
|
data = dict(self.request.json_body)
|
||||||
|
action = data.get('action')
|
||||||
|
json_actions = [
|
||||||
|
'get_customer_info',
|
||||||
|
'set_customer_data',
|
||||||
|
'submit_new_order',
|
||||||
|
]
|
||||||
|
if action in json_actions:
|
||||||
|
result = getattr(self, action)(batch, data)
|
||||||
|
return self.json_response(result)
|
||||||
|
|
||||||
|
context = {'batch': batch}
|
||||||
|
return self.render_to_response(template, context)
|
||||||
|
|
||||||
|
def get_current_batch(self):
|
||||||
|
user = self.request.user
|
||||||
|
if not user:
|
||||||
|
raise RuntimeError("this feature requires a user to be logged in")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# there should be at most *one* new batch per user
|
||||||
|
batch = self.Session.query(model.CustomerOrderBatch)\
|
||||||
|
.filter(model.CustomerOrderBatch.mode == self.enum.CUSTORDER_BATCH_MODE_CREATING)\
|
||||||
|
.filter(model.CustomerOrderBatch.created_by == user)\
|
||||||
|
.one()
|
||||||
|
|
||||||
|
except orm.exc.NoResultFound:
|
||||||
|
# no batch yet for this user, so make one
|
||||||
|
batch = model.CustomerOrderBatch()
|
||||||
|
batch.mode = self.enum.CUSTORDER_BATCH_MODE_CREATING
|
||||||
|
batch.created_by = user
|
||||||
|
self.Session.add(batch)
|
||||||
|
self.Session.flush()
|
||||||
|
|
||||||
|
return batch
|
||||||
|
|
||||||
|
def start_over_entirely(self, batch):
|
||||||
|
# just delete current batch outright
|
||||||
|
# TODO: should use self.handler.do_delete() instead?
|
||||||
|
self.Session.delete(batch)
|
||||||
|
self.Session.flush()
|
||||||
|
|
||||||
|
# send user back to normal "create" page; a new batch will be generated
|
||||||
|
# for them automatically
|
||||||
|
route_prefix = self.get_route_prefix()
|
||||||
|
url = self.request.route_url('{}.create'.format(route_prefix))
|
||||||
|
return self.redirect(url)
|
||||||
|
|
||||||
|
def delete_batch(self, batch):
|
||||||
|
# just delete current batch outright
|
||||||
|
# TODO: should use self.handler.do_delete() instead?
|
||||||
|
self.Session.delete(batch)
|
||||||
|
self.Session.flush()
|
||||||
|
|
||||||
|
# set flash msg just to be more obvious
|
||||||
|
self.request.session.flash("New customer order has been deleted.")
|
||||||
|
|
||||||
|
# send user back to customer orders page, w/ no new batch generated
|
||||||
|
route_prefix = self.get_route_prefix()
|
||||||
|
url = self.request.route_url(route_prefix)
|
||||||
|
return self.redirect(url)
|
||||||
|
|
||||||
|
def get_customer_info(self, batch, data):
|
||||||
|
uuid = data.get('uuid')
|
||||||
|
if not uuid:
|
||||||
|
return {'error': "Must specify a customer UUID"}
|
||||||
|
|
||||||
|
customer = self.Session.query(model.Customer).get(uuid)
|
||||||
|
if not customer:
|
||||||
|
return {'error': "Customer not found"}
|
||||||
|
|
||||||
|
return self.info_for_customer(batch, data, customer)
|
||||||
|
|
||||||
|
def info_for_customer(self, batch, data, customer):
|
||||||
|
phone = customer.first_phone()
|
||||||
|
email = customer.first_email()
|
||||||
|
return {
|
||||||
|
'uuid': customer.uuid,
|
||||||
|
'phone_number': phone.number if phone else None,
|
||||||
|
'email_address': email.address if email else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_customer_data(self, batch, data):
|
||||||
|
if 'customer_uuid' in data:
|
||||||
|
batch.customer_uuid = data['customer_uuid']
|
||||||
|
if 'person_uuid' in data:
|
||||||
|
batch.person_uuid = data['person_uuid']
|
||||||
|
elif batch.customer_uuid:
|
||||||
|
self.Session.flush()
|
||||||
|
batch.person = batch.customer.first_person()
|
||||||
|
else: # no customer set
|
||||||
|
batch.person_uuid = None
|
||||||
|
if 'phone_number' in data:
|
||||||
|
batch.phone_number = data['phone_number']
|
||||||
|
if 'email_address' in data:
|
||||||
|
batch.email_address = data['email_address']
|
||||||
|
self.Session.flush()
|
||||||
|
return {'success': True}
|
||||||
|
|
||||||
|
def submit_new_order(self, batch, data):
|
||||||
|
# TODO
|
||||||
|
return {'success': True}
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
CustomerOrdersView.defaults(config)
|
CustomerOrdersView.defaults(config)
|
||||||
|
|
Loading…
Reference in a new issue