diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index 4716404d..d921779e 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -333,19 +333,84 @@
- Customer is not yet in the system.
-
- - - - - - +
+
+ + {{ newCustomerName }} + + + + {{ newCustomerPhone }} + + + {{ newCustomerEmail }} + + +
+ +
+ + Edit New Customer + +
+ +
+ +

Duplicate records can be difficult to clean up!

+

Please be sure the customer is not already in the system.

+
+
+ + + + +
@@ -577,7 +642,7 @@ batchTotalPriceDisplay: ${json.dumps(normalized_batch['total_price_display'])|n}, customerPanelOpen: false, - contactIsKnown: true, + contactIsKnown: ${json.dumps(contact_is_known)|n}, % if new_order_requires_customer: contactUUID: ${json.dumps(batch.customer_uuid)|n}, % else: @@ -609,10 +674,17 @@ % endif - customerName: null, - phoneNumber: null, + newCustomerName: ${json.dumps(new_customer_name)|n}, + newCustomerPhone: ${json.dumps(new_customer_phone)|n}, + newCustomerEmail: ${json.dumps(new_customer_email)|n}, contactNotes: ${json.dumps(contact_notes)|n}, + editNewCustomerShowDialog: false, + editNewCustomerName: null, + editNewCustomerPhone: null, + editNewCustomerEmail: null, + editNewCustomerSaving: false, + items: ${json.dumps(order_items)|n}, editingItem: null, showingItemDialog: false, @@ -646,8 +718,8 @@ } } } else { - if (this.customerName) { - text = "Customer: " + this.customerName + if (this.newCustomerName) { + text = "Customer: " + this.newCustomerName } } @@ -700,19 +772,19 @@ } phoneNumber = this.orderPhoneNumber } else { // customer is not known - if (!this.customerName) { + if (!this.newCustomerName) { return { type: 'is-danger', text: "Please identify the customer.", } } - if (!this.phoneNumber) { + if (!this.newCustomerPhone) { return { type: 'is-warning', text: "Please provide a phone number for the customer.", } } - phoneNumber = this.phoneNumber + phoneNumber = this.newCustomerPhone } let phoneDigits = phoneNumber.replace(/\D/g, '') @@ -774,6 +846,26 @@ % 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() { let text = "Items" @@ -1044,6 +1136,46 @@ % 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() { this.customerPanelOpen = false this.editingItem = null diff --git a/tailbone/views/customers.py b/tailbone/views/customers.py index 27b19e94..65618c1a 100644 --- a/tailbone/views/customers.py +++ b/tailbone/views/customers.py @@ -450,6 +450,63 @@ class CustomerView(MasterView): 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?? # def unique_id(value, field): # customer = field.parent.model @@ -491,3 +548,4 @@ def includeme(config): renderer='json', permission='customers.view') CustomerView.defaults(config) + PendingCustomerView.defaults(config) diff --git a/tailbone/views/custorders/batch.py b/tailbone/views/custorders/batch.py index 1cced9de..fb47f247 100644 --- a/tailbone/views/custorders/batch.py +++ b/tailbone/views/custorders/batch.py @@ -31,6 +31,7 @@ import six from rattail.db import model import colander +from webhelpers2.html import tags from tailbone import forms from tailbone.views.batch import BatchMasterView @@ -61,6 +62,7 @@ class CustomerOrderBatchView(BatchMasterView): 'store', 'customer', 'person', + 'pending_customer', 'phone_number', 'email_address', 'params', @@ -151,8 +153,19 @@ class CustomerOrderBatchView(BatchMasterView): else: f.set_renderer('person', self.render_person) + # pending_customer + f.set_renderer('pending_customer', self.render_pending_customer) + 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): if row.status_code == row.STATUS_PRODUCT_NOT_FOUND: return 'warning' diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index aa005dd1..1c6aed08 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -230,6 +230,7 @@ class CustomerOrderView(MasterView): 'unassign_contact', 'update_phone_number', 'update_email_address', + 'update_pending_customer', 'get_customer_info', # 'set_customer_data', 'find_product_by_upc', @@ -252,26 +253,17 @@ class CustomerOrderView(MasterView): else: product_autocomplete = 'products.autocomplete' - context = {'batch': batch, - 'normalized_batch': self.normalize_batch(batch), - 'new_order_requires_customer': self.handler.new_order_requires_customer(), - 'allow_contact_info_choice': self.handler.allow_contact_info_choice(), - '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, - 'product_autocomplete_url': self.request.route_url(product_autocomplete)} + context = self.get_context_contact(batch) - # 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) + context.update({ + 'batch': batch, + 'normalized_batch': self.normalize_batch(batch), + 'new_order_requires_customer': self.handler.new_order_requires_customer(), + 'allow_contact_info_choice': self.handler.allow_contact_info_choice(), + 'restrict_contact_info': self.handler.should_restrict_contact_info(), + 'order_items': items, + 'product_autocomplete_url': self.request.route_url(product_autocomplete), + }) return self.render_to_response(template, context) @@ -403,14 +395,38 @@ class CustomerOrderView(MasterView): 'customer_uuid': batch.customer_uuid, 'person_uuid': batch.person_uuid, 'phone_number': batch.phone_number, + 'contact_display': self.handler.get_contact_display(batch), 'email_address': batch.email_address, 'contact_phones': self.handler.get_contact_phones(batch), 'contact_emails': self.handler.get_contact_emails(batch), 'contact_notes': self.handler.get_contact_notes(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, + '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 if batch.person_uuid: if self.request.has_perm('people.view_profile'): @@ -457,6 +473,35 @@ class CustomerOrderView(MasterView): '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): """ Custom product autocomplete logic, which invokes the handler.