diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako new file mode 100644 index 00000000..24245b7a --- /dev/null +++ b/tailbone/templates/custorders/create.mako @@ -0,0 +1,426 @@ +## -*- coding: utf-8; -*- +<%inherit file="/master/create.mako" /> + +<%def name="extra_styles()"> + ${parent.extra_styles()} + % if use_buefy: + + % endif + + +<%def name="page_content()"> +
+ % if use_buefy: + + % else: +

Sorry, but this page is not supported by your current theme configuration.

+ % endif + + +<%def name="order_form_buttons()"> +
+ + Submit this Order + + + Start Over Entirely + + + Cancel this Order + +
+ + +<%def name="render_this_page_template()"> + ${parent.render_this_page_template()} + + + + +<%def name="make_this_page_component()"> + ${parent.make_this_page_component()} + + + + +${parent.body()} diff --git a/tailbone/views/custorders/batch.py b/tailbone/views/custorders/batch.py index a49be977..c8b6280f 100644 --- a/tailbone/views/custorders/batch.py +++ b/tailbone/views/custorders/batch.py @@ -26,8 +26,13 @@ Base class for customer order batch views from __future__ import unicode_literals, absolute_import +import six + from rattail.db import model +import colander + +from tailbone import forms from tailbone.views.batch import BatchMasterView @@ -39,3 +44,79 @@ class CustomerOrderBatchView(BatchMasterView): model_class = model.CustomerOrderBatch model_row_class = model.CustomerOrderBatchRow 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) diff --git a/tailbone/views/custorders/creating.py b/tailbone/views/custorders/creating.py index 29dc5b35..c14448eb 100644 --- a/tailbone/views/custorders/creating.py +++ b/tailbone/views/custorders/creating.py @@ -22,6 +22,11 @@ ################################################################################ """ 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 diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index dee21f15..9ffc06c8 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2018 Lance Edgar +# Copyright © 2010-2020 Lance Edgar # # This file is part of Rattail. # @@ -43,7 +43,6 @@ class CustomerOrdersView(MasterView): """ model_class = model.CustomerOrder route_prefix = 'custorders' - creatable = False editable = False deletable = False @@ -59,6 +58,8 @@ class CustomerOrdersView(MasterView): 'id', 'customer', 'person', + 'phone_number', + 'email_address', 'created', 'status_code', ] @@ -115,6 +116,130 @@ class CustomerOrdersView(MasterView): url = self.request.route_url('people.view', uuid=person.uuid) 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): CustomerOrdersView.defaults(config)