Add support for "new customer" when creating new custorder

This commit is contained in:
Lance Edgar 2021-10-06 18:22:29 -04:00
parent 25a019cc12
commit d933dd2723
4 changed files with 283 additions and 35 deletions

View file

@ -333,19 +333,84 @@
<br /> <br />
<div class="field"> <div class="field">
<b-radio v-model="contactIsKnown" disabled <b-radio v-model="contactIsKnown"
:native-value="false"> :native-value="false">
Customer is not yet in the system. Customer is not yet in the system.
</b-radio> </b-radio>
</div> </div>
<div v-if="!contactIsKnown"> <div v-if="!contactIsKnown"
<b-field label="Customer Name" horizontal> style="padding-left: 10rem; display: flex;">
<b-input v-model="customerName"></b-input> <div>
<b-field label="Customer Name">
<span>{{ newCustomerName }}</span>
</b-field> </b-field>
<b-field label="Phone Number" horizontal> <b-field grouped>
<b-input v-model="phoneNumber"></b-input> <b-field label="Phone Number">
<span>{{ newCustomerPhone }}</span>
</b-field> </b-field>
<b-field label="Email Address">
<span>{{ newCustomerEmail }}</span>
</b-field>
</b-field>
</div>
<div>
<b-button type="is-primary"
@click="editNewCustomerInit()"
icon-pack="fas"
icon-left="edit">
Edit New Customer
</b-button>
</div>
<div style="margin-left: 1rem;">
<b-notification type="is-warning"
:closable="false">
<p>Duplicate records can be difficult to clean up!</p>
<p>Please be sure the customer is not already in the system.</p>
</b-notification>
</div>
<b-modal has-modal-card
:active.sync="editNewCustomerShowDialog">
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Edit New Customer</p>
</header>
<section class="modal-card-body">
<b-field label="Customer Name">
<b-input v-model="editNewCustomerName"
ref="editNewCustomerInput">
</b-input>
</b-field>
<b-field grouped>
<b-field label="Phone Number">
<b-input v-model="editNewCustomerPhone"></b-input>
</b-field>
<b-field label="Email Address">
<b-input v-model="editNewCustomerEmail"></b-input>
</b-field>
</b-field>
</section>
<footer class="modal-card-foot">
<b-button type="is-primary"
icon-pack="fas"
icon-left="save"
:disabled="editNewCustomerSaveDisabled"
@click="editNewCustomerSave()">
{{ editNewCustomerSaveText }}
</b-button>
<b-button @click="editNewCustomerShowDialog = false">
Cancel
</b-button>
</footer>
</div>
</b-modal>
</div> </div>
</div> </div>
@ -577,7 +642,7 @@
batchTotalPriceDisplay: ${json.dumps(normalized_batch['total_price_display'])|n}, batchTotalPriceDisplay: ${json.dumps(normalized_batch['total_price_display'])|n},
customerPanelOpen: false, customerPanelOpen: false,
contactIsKnown: true, contactIsKnown: ${json.dumps(contact_is_known)|n},
% if new_order_requires_customer: % if new_order_requires_customer:
contactUUID: ${json.dumps(batch.customer_uuid)|n}, contactUUID: ${json.dumps(batch.customer_uuid)|n},
% else: % else:
@ -609,10 +674,17 @@
% endif % endif
customerName: null, newCustomerName: ${json.dumps(new_customer_name)|n},
phoneNumber: null, newCustomerPhone: ${json.dumps(new_customer_phone)|n},
newCustomerEmail: ${json.dumps(new_customer_email)|n},
contactNotes: ${json.dumps(contact_notes)|n}, contactNotes: ${json.dumps(contact_notes)|n},
editNewCustomerShowDialog: false,
editNewCustomerName: null,
editNewCustomerPhone: null,
editNewCustomerEmail: null,
editNewCustomerSaving: false,
items: ${json.dumps(order_items)|n}, items: ${json.dumps(order_items)|n},
editingItem: null, editingItem: null,
showingItemDialog: false, showingItemDialog: false,
@ -646,8 +718,8 @@
} }
} }
} else { } else {
if (this.customerName) { if (this.newCustomerName) {
text = "Customer: " + this.customerName text = "Customer: " + this.newCustomerName
} }
} }
@ -700,19 +772,19 @@
} }
phoneNumber = this.orderPhoneNumber phoneNumber = this.orderPhoneNumber
} else { // customer is not known } else { // customer is not known
if (!this.customerName) { if (!this.newCustomerName) {
return { return {
type: 'is-danger', type: 'is-danger',
text: "Please identify the customer.", text: "Please identify the customer.",
} }
} }
if (!this.phoneNumber) { if (!this.newCustomerPhone) {
return { return {
type: 'is-warning', type: 'is-warning',
text: "Please provide a phone number for the customer.", text: "Please provide a phone number for the customer.",
} }
} }
phoneNumber = this.phoneNumber phoneNumber = this.newCustomerPhone
} }
let phoneDigits = phoneNumber.replace(/\D/g, '') let phoneDigits = phoneNumber.replace(/\D/g, '')
@ -774,6 +846,26 @@
% endif % endif
editNewCustomerSaveDisabled() {
if (this.editNewCustomerSaving) {
return true
}
if (!this.editNewCustomerName) {
return true
}
if (!(this.editNewCustomerPhone || this.editNewCustomerEmail)) {
return true
}
return false
},
editNewCustomerSaveText() {
if (this.editNewCustomerSaving) {
return "Working, please wait..."
}
return "Save"
},
itemsPanelHeader() { itemsPanelHeader() {
let text = "Items" let text = "Items"
@ -1044,6 +1136,46 @@
% endif % endif
editNewCustomerInit() {
this.editNewCustomerName = this.newCustomerName
this.editNewCustomerPhone = this.newCustomerPhone
this.editNewCustomerEmail = this.newCustomerEmail
this.editNewCustomerShowDialog = true
this.$nextTick(() => {
this.$refs.editNewCustomerInput.focus()
})
},
editNewCustomerSave() {
this.editNewCustomerSaving = true
let params = {
action: 'update_pending_customer',
display_name: this.editNewCustomerName,
phone_number: this.editNewCustomerPhone,
email_address: this.editNewCustomerEmail,
}
this.submitBatchData(params, response => {
if (response.data.success) {
this.newCustomerName = response.data.new_customer_name
this.newCustomerPhone = response.data.phone_number
this.orderPhoneNumber = response.data.phone_number
this.newCustomerEmail = response.data.email_address
this.orderEmailAddress = response.data.email_address
this.editNewCustomerShowDialog = false
} else {
this.$buefy.toast.open({
message: "Save failed: " + (response.data.error || "(unknown error)"),
type: 'is-danger',
duration: 2000, // 2 seconds
})
}
this.editNewCustomerSaving = false
})
},
showAddItemDialog() { showAddItemDialog() {
this.customerPanelOpen = false this.customerPanelOpen = false
this.editingItem = null this.editingItem = null

View file

@ -450,6 +450,63 @@ class CustomerView(MasterView):
permission='{}.detach_person'.format(permission_prefix)) permission='{}.detach_person'.format(permission_prefix))
class PendingCustomerView(MasterView):
"""
Master view for the Pending Customer class.
"""
model_class = model.PendingCustomer
route_prefix = 'pending_customers'
url_prefix = '/customers/pending'
labels = {
'id': "ID",
'status_code': "Status",
}
grid_columns = [
'id',
'display_name',
'first_name',
'last_name',
'phone_number',
'email_address',
'status_code',
]
form_fields = [
'id',
'display_name',
'first_name',
'middle_name',
'last_name',
'phone_number',
'phone_type',
'email_address',
'email_type',
'address_street',
'address_street2',
'address_city',
'address_state',
'address_zipcode',
'address_type',
'status_code',
]
def configure_grid(self, g):
super(PendingCustomerView, self).configure_grid(g)
g.set_enum('status_code', self.enum.PENDING_CUSTOMER_STATUS)
g.set_sort_defaults('display_name')
g.set_link('id')
g.set_link('display_name')
def configure_form(self, f):
super(PendingCustomerView, self).configure_form(f)
f.set_enum('status_code', self.enum.PENDING_CUSTOMER_STATUS)
# # TODO: this is referenced by some custom apps, but should be moved?? # # TODO: this is referenced by some custom apps, but should be moved??
# def unique_id(value, field): # def unique_id(value, field):
# customer = field.parent.model # customer = field.parent.model
@ -491,3 +548,4 @@ def includeme(config):
renderer='json', permission='customers.view') renderer='json', permission='customers.view')
CustomerView.defaults(config) CustomerView.defaults(config)
PendingCustomerView.defaults(config)

View file

@ -31,6 +31,7 @@ import six
from rattail.db import model from rattail.db import model
import colander import colander
from webhelpers2.html import tags
from tailbone import forms from tailbone import forms
from tailbone.views.batch import BatchMasterView from tailbone.views.batch import BatchMasterView
@ -61,6 +62,7 @@ class CustomerOrderBatchView(BatchMasterView):
'store', 'store',
'customer', 'customer',
'person', 'person',
'pending_customer',
'phone_number', 'phone_number',
'email_address', 'email_address',
'params', 'params',
@ -151,8 +153,19 @@ class CustomerOrderBatchView(BatchMasterView):
else: else:
f.set_renderer('person', self.render_person) f.set_renderer('person', self.render_person)
# pending_customer
f.set_renderer('pending_customer', self.render_pending_customer)
f.set_type('total_price', 'currency') f.set_type('total_price', 'currency')
def render_pending_customer(self, batch, field):
pending = batch.pending_customer
if not pending:
return
text = six.text_type(pending)
url = self.request.route_url('pending_customers.view', uuid=pending.uuid)
return tags.link_to(text, url)
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'

View file

@ -230,6 +230,7 @@ class CustomerOrderView(MasterView):
'unassign_contact', 'unassign_contact',
'update_phone_number', 'update_phone_number',
'update_email_address', 'update_email_address',
'update_pending_customer',
'get_customer_info', 'get_customer_info',
# 'set_customer_data', # 'set_customer_data',
'find_product_by_upc', 'find_product_by_upc',
@ -252,26 +253,17 @@ class CustomerOrderView(MasterView):
else: else:
product_autocomplete = 'products.autocomplete' product_autocomplete = 'products.autocomplete'
context = {'batch': batch, context = self.get_context_contact(batch)
context.update({
'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.handler.new_order_requires_customer(),
'allow_contact_info_choice': self.handler.allow_contact_info_choice(), 'allow_contact_info_choice': self.handler.allow_contact_info_choice(),
'restrict_contact_info': self.handler.should_restrict_contact_info(), 'restrict_contact_info': self.handler.should_restrict_contact_info(),
'contact_display': self.handler.get_contact_display(batch),
'contact_phones': self.handler.get_contact_phones(batch),
'contact_emails': self.handler.get_contact_emails(batch),
'add_phone_number': bool(batch.get_param('add_phone_number')),
'add_email_address': bool(batch.get_param('add_email_address')),
'contact_profile_url': None,
'contact_notes': self.handler.get_contact_notes(batch),
'order_items': items, 'order_items': items,
'product_autocomplete_url': self.request.route_url(product_autocomplete)} 'product_autocomplete_url': self.request.route_url(product_autocomplete),
})
# maybe add profile URL
if batch.person_uuid:
if self.request.has_perm('people.view_profile'):
context['contact_profile_url'] = self.request.route_url(
'people.view_profile', uuid=batch.person_uuid)
return self.render_to_response(template, context) return self.render_to_response(template, context)
@ -403,14 +395,38 @@ class CustomerOrderView(MasterView):
'customer_uuid': batch.customer_uuid, 'customer_uuid': batch.customer_uuid,
'person_uuid': batch.person_uuid, 'person_uuid': batch.person_uuid,
'phone_number': batch.phone_number, 'phone_number': batch.phone_number,
'contact_display': self.handler.get_contact_display(batch),
'email_address': batch.email_address, 'email_address': batch.email_address,
'contact_phones': self.handler.get_contact_phones(batch), 'contact_phones': self.handler.get_contact_phones(batch),
'contact_emails': self.handler.get_contact_emails(batch), 'contact_emails': self.handler.get_contact_emails(batch),
'contact_notes': self.handler.get_contact_notes(batch), 'contact_notes': self.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,
'new_customer_name': None,
'new_customer_phone': None,
'new_customer_email': None,
} }
pending = batch.pending_customer
if pending:
context.update({
'new_customer_name': pending.display_name,
'new_customer_phone': pending.phone_number,
'new_customer_email': pending.email_address,
})
# figure out if "contact is known" from user's perspective.
# if we have a uuid then it's definitely known, otherwise if
# we have a pending customer then it's definitely *not* known,
# but if no pending customer yet then we can still "assume" it
# is known, by default, until user specifies otherwise.
contact = self.handler.get_contact(batch)
if contact:
context['contact_is_known'] = True
else:
context['contact_is_known'] = not bool(pending)
# maybe add profile URL # maybe add profile URL
if batch.person_uuid: if batch.person_uuid:
if self.request.has_perm('people.view_profile'): if self.request.has_perm('people.view_profile'):
@ -457,6 +473,35 @@ class CustomerOrderView(MasterView):
'email_address': batch.email_address, 'email_address': batch.email_address,
} }
def update_pending_customer(self, batch, data):
model = self.model
app = self.get_rattail_app()
# clear out any contact it may have
self.handler.unassign_contact(batch)
# create pending customer if needed
pending = batch.pending_customer
if not pending:
pending = model.PendingCustomer()
pending.user = self.request.user
pending.status_code = self.enum.PENDING_CUSTOMER_STATUS_PENDING
batch.pending_customer = pending
# update pending customer info
pending.display_name = data['display_name']
pending.phone_number = app.format_phone_number(data['phone_number'])
pending.email_address = data['email_address']
# also update the batch w/ contact info
batch.phone_number = pending.phone_number
batch.email_address = pending.email_address
self.Session.flush()
context = self.get_context_contact(batch)
context['success'] = True
return context
def product_autocomplete(self): def product_autocomplete(self):
""" """
Custom product autocomplete logic, which invokes the handler. Custom product autocomplete logic, which invokes the handler.