diff --git a/rattail/batch/custorder.py b/rattail/batch/custorder.py index 1556c9e16b27c881d2e1b28665ac3d019447a88b..3b921b4b39dbdb659744a8b8294aec0c9454e6e9 100644 --- a/rattail/batch/custorder.py +++ b/rattail/batch/custorder.py @@ -60,6 +60,68 @@ class CustomerOrderBatchHandler(BatchHandler): session = self.app.get_session(batch) batch.store = self.config.get_store(session) + def new_order_requires_customer(self): + """ + Returns a boolean indicating whether a *new* "customer order" + in fact requires a proper customer account, or not. Note that + in all cases a new order requires a *person* to associate + with, but technically the customer is optional, unless this + returns true. + """ + return self.config.getbool('rattail.custorders', + 'new_order_requires_customer', + default=False) + + def assign_contact(self, batch, customer=None, person=None, **kwargs): + """ + Assign the customer and/or person "contact" for the order. + """ + clientele = self.app.get_clientele_handler() + customer_required = self.new_order_requires_customer() + + # nb. person is always required + if customer and not person: + person = clientele.get_person(customer) + if not person: + raise ValueError("Must specify a person") + + # customer may or may not be optional + if person and not customer: + customer = clientele.get_customer(person) + if customer_required and not customer: + raise ValueError("Must specify a customer account") + + # assign contact + batch.customer = customer + batch.person = person + + # update phone/email per new contact + batch.phone_number = None + batch.email_address = None + if customer_required: + batch.phone_number = clientele.get_first_phone_number(customer) + batch.email_address = clientele.get_first_email_address(customer) + else: + batch.phone_number = person.first_phone_number() + batch.email_address = person.first_email_address() + + session = self.app.get_session(batch) + session.flush() + session.refresh(batch) + + def unassign_contact(self, batch, **kwargs): + """ + Unassign the customer and/or person "contact" for the order. + """ + batch.customer = None + batch.person = None + batch.phone_number = None + batch.email_address = None + + session = self.app.get_session(batch) + session.flush() + session.refresh(batch) + def get_case_size_for_product(self, product): if product.case_size: return product.case_size @@ -79,8 +141,8 @@ class CustomerOrderBatchHandler(BatchHandler): def customer_autocomplete(self, session, term, **kwargs): """ - Override the customer autocomplete, to search by phone number - as well as customer name. + Override the Customer autocomplete, to search by phone number + as well as name. """ model = self.model @@ -101,7 +163,7 @@ class CustomerOrderBatchHandler(BatchHandler): else: # term does not look like a phone number - # so just search by customer name + # so just search by name criteria = [model.Customer.name.ilike('%{}%'.format(word)) for word in term.split()] query = query.filter(sa.and_(*criteria)) @@ -123,6 +185,85 @@ class CustomerOrderBatchHandler(BatchHandler): return results + def person_autocomplete(self, session, term, **kwargs): + """ + Override the Person autocomplete, to search by phone number as + well as name. + """ + model = self.model + + # define the base query + query = session.query(model.Person)\ + .options(orm.joinedload(model.Person.phones)) + + # does search term look like a phone number? + phone_term = self.get_phone_search_term(term) + if phone_term: + + # yep, so just search for the phone number + query = query.join(model.PersonPhoneNumber, + model.PersonPhoneNumber.parent_uuid == model.Person.uuid) + query = query.filter(sa.func.regexp_replace(model.PersonPhoneNumber.number, + r'\D', '', 'g')\ + .like('%{}%'.format(phone_term))) + + else: # term does not look like a phone number + + # so just search by name + criteria = [model.Person.display_name.ilike('%{}%'.format(word)) + for word in term.split()] + query = query.filter(sa.and_(*criteria)) + + # oh, and sort by something useful + query = query.order_by(model.Person.display_name) + + # generate result list from query + results = [] + for person in query: + phone = person.first_phone() + if phone: + label = "{} {}".format(person.display_name, phone.number) + else: + label = person.display_name + results.append({'value': person.uuid, + 'label': label, + 'display': person.display_name}) + + return results + + def get_customer_info(self, batch, **kwargs): + """ + Return a data dict containing misc. info pertaining to the + customer/person for the order batch. + """ + info = { + 'customer_uuid': None, + 'person_uuid': None, + 'phone_number': None, + 'email_address': None, + } + + if batch.customer: + info['customer_uuid'] = batch.customer.uuid + phone = batch.customer.first_phone() + if phone: + info['phone_number'] = phone.number + email = batch.customer.first_email() + if email: + info['email_address'] = email.address + + if batch.person: + info['person_uuid'] = batch.person.uuid + if not info['phone_number']: + phone = batch.person.first_phone() + if phone: + info['phone_number'] = phone.number + email = batch.person.first_email() + if email: + info['email_address'] = email.address + + return info + def custom_product_autocomplete(self, session, term, **kwargs): """ For the given term, this should return a (possibly empty) list diff --git a/rattail/clientele.py b/rattail/clientele.py index 372178cf91668772f6387a652a85f440c4fdbb7a..196861d5c330a319af30dd28736f471275c359b0 100644 --- a/rattail/clientele.py +++ b/rattail/clientele.py @@ -72,6 +72,50 @@ class ClienteleHandler(GenericHandler): customer.people.append(person) return customer + def get_first_phone(self, customer, **kwargs): + """ + Return the first available phone record found, either for the + customer, or its first person. + """ + phone = customer.first_phone() + if phone: + return phone + + person = self.get_person(customer) + if person: + return person.first_phone() + + def get_first_phone_number(self, customer, **kwargs): + """ + Return the first available phone number found, either for the + customer, or its first person. + """ + phone = self.first_phone(customer) + if phone: + return phone.number + + def get_first_email(self, customer, **kwargs): + """ + Return the first available email record found, either for the + customer, or its first person. + """ + email = customer.first_email() + if email: + return email + + person = self.get_person(customer) + if person: + return person.first_email() + + def get_first_email_address(self, customer, **kwargs): + """ + Return the first available email address found, either for the + customer, or its first person. + """ + email = self.first_email(customer) + if email: + return email.address + def get_clientele_handler(config, **kwargs): """ diff --git a/rattail/db/model/contact.py b/rattail/db/model/contact.py index 03fd432f92844010069f84937bff58de35c994c2..3fca38ab4333e93e62e23497aecce823f00984d1 100644 --- a/rattail/db/model/contact.py +++ b/rattail/db/model/contact.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2020 Lance Edgar +# Copyright © 2010-2021 Lance Edgar # # This file is part of Rattail. # @@ -155,6 +155,14 @@ class ContactMixin(object): if self.emails: return self.emails[0] + def first_email_address(self, **kwargs): + """ + Return the first available email address for the contact. + """ + email = self.first_email() + if email: + return email.address + def make_email(self, **kwargs): """ Make a new "email" record for the contact. @@ -212,6 +220,14 @@ class ContactMixin(object): if self.phones: return self.phones[0] + def first_phone_number(self, **kwargs): + """ + Return the first available phone number for the contact. + """ + phone = self.first_phone() + if phone: + return phone.number + def make_phone(self, **kwargs): """ Make a new "phone" record for the contact. diff --git a/rattail/settings.py b/rattail/settings.py index 309c75dcf7a05356c91e175d599d574c242c955c..0280a8ccd531296bd958a5e7952c8a126a804f28 100644 --- a/rattail/settings.py +++ b/rattail/settings.py @@ -113,6 +113,21 @@ class rattail_workdir(Setting): name = 'workdir' +############################## +# Customer Orders +############################## + +class rattail_custorders_new_order_requires_customer(Setting): + """ + If set, then all new orders require a proper customer account. If + *not* set then just a "person" will suffice. + """ + group = "Customer Orders" + namespace = 'rattail.custorders' + name = 'new_order_requires_customer' + data_type = bool + + ############################## # DataSync ##############################