diff --git a/tailbone/forms/renderers/__init__.py b/tailbone/forms/renderers/__init__.py index 984e40ca..e1ad83fe 100644 --- a/tailbone/forms/renderers/__init__.py +++ b/tailbone/forms/renderers/__init__.py @@ -46,4 +46,6 @@ from .products import (GPCFieldRenderer, ScancodeFieldRenderer, BrandFieldRenderer, ProductFieldRenderer, PriceFieldRenderer, PriceWithExpirationFieldRenderer) +from .custorders import CustomerOrderFieldRenderer + from .batch import BatchIDFieldRenderer, HandheldBatchFieldRenderer diff --git a/tailbone/forms/renderers/core.py b/tailbone/forms/renderers/core.py index ac80404a..27e3f4c9 100644 --- a/tailbone/forms/renderers/core.py +++ b/tailbone/forms/renderers/core.py @@ -67,9 +67,16 @@ class DateFieldRenderer(CustomFieldRenderer, fa.DateFieldRenderer): """ change_year = False - def init(self, change_year=False): + def init(self, date_format=None, change_year=False): + self.date_format = date_format self.change_year = change_year + def render_readonly(self, **kwargs): + value = self.raw_value + if value is None: + return '' + return value.strftime(self.date_format) + def render(self, **kwargs): kwargs['name'] = self.name kwargs['value'] = self.value diff --git a/tailbone/forms/renderers/custorders.py b/tailbone/forms/renderers/custorders.py new file mode 100644 index 00000000..758ecd3f --- /dev/null +++ b/tailbone/forms/renderers/custorders.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2017 Lance Edgar +# +# This file is part of Rattail. +# +# Rattail is free software: you can redistribute it and/or modify it under the +# terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +# more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Rattail. If not, see . +# +################################################################################ +""" +Customer order field renderers +""" + +from __future__ import unicode_literals, absolute_import + +import formalchemy as fa +from webhelpers.html import tags + + +class CustomerOrderFieldRenderer(fa.fields.SelectFieldRenderer): + """ + Renders a link to the customer order + """ + + def render_readonly(self, **kwargs): + order = self.raw_value + if not order: + return '' + return tags.link_to(order, self.request.route_url('custorders.view', uuid=order.uuid)) diff --git a/tailbone/views/custorders/__init__.py b/tailbone/views/custorders/__init__.py new file mode 100644 index 00000000..d66ba2ed --- /dev/null +++ b/tailbone/views/custorders/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2017 Lance Edgar +# +# This file is part of Rattail. +# +# Rattail is free software: you can redistribute it and/or modify it under the +# terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +# more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Rattail. If not, see . +# +################################################################################ +""" +Customer Order Views +""" + +from __future__ import unicode_literals, absolute_import + + +def includeme(config): + config.include('tailbone.views.custorders.orders') + config.include('tailbone.views.custorders.items') diff --git a/tailbone/views/custorders/items.py b/tailbone/views/custorders/items.py new file mode 100644 index 00000000..c1d29b91 --- /dev/null +++ b/tailbone/views/custorders/items.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2017 Lance Edgar +# +# This file is part of Rattail. +# +# Rattail is free software: you can redistribute it and/or modify it under the +# terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +# more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Rattail. If not, see . +# +################################################################################ +""" +Customer order item views +""" + +from __future__ import unicode_literals, absolute_import + +import datetime + +from sqlalchemy import orm + +from rattail.db import model +from rattail.time import localtime + +import formalchemy as fa + +from tailbone import forms +from tailbone.views import MasterView + + +class CustomerOrderItemsView(MasterView): + """ + Master view for customer order items + """ + model_class = model.CustomerOrderItem + route_prefix = 'custorders.items' + url_prefix = '/custorders/items' + creatable = False + editable = False + deletable = False + + has_rows = True + model_row_class = model.CustomerOrderItemEvent + rows_title = "Event History" + rows_filterable = False + rows_sortable = False + rows_pageable = False + rows_viewable = False + + def query(self, session): + return session.query(model.CustomerOrderItem)\ + .join(model.CustomerOrder)\ + .options(orm.joinedload(model.CustomerOrderItem.order)\ + .joinedload(model.CustomerOrder.person)) + + def _preconfigure_grid(self, g): + + g.joiners['person'] = lambda q: q.outerjoin(model.Person) + g.filters['person'] = g.make_filter('person', model.Person.display_name, label="Person Name", + default_active=True, default_verb='contains') + g.sorters['person'] = g.make_sorter(model.Person.display_name) + + g.filters['product_brand'].label = "Brand" + g.product_brand.set(label="Brand") + + g.filters['product_description'].label = "Description" + g.product_description.set(label="Description") + + g.filters['product_size'].label = "Size" + g.product_size.set(label="Size") + + g.case_quantity.set(renderer=forms.renderers.QuantityFieldRenderer) + g.cases_ordered.set(renderer=forms.renderers.QuantityFieldRenderer) + g.units_ordered.set(renderer=forms.renderers.QuantityFieldRenderer) + + g.total_price.set(renderer=forms.renderers.CurrencyFieldRenderer) + + g.status_code.set(label="Status") + + g.append(fa.Field('person', value=lambda i: i.order.person)) + + g.sorters['order_created'] = g.make_sorter(model.CustomerOrder.created) + g.append(fa.Field('order_created', + value=lambda i: localtime(self.rattail_config, i.order.created, from_utc=True), + renderer=forms.renderers.DateTimeFieldRenderer)) + + g.default_sortkey = 'order_created' + g.default_sortdir = 'desc' + + def configure_grid(self, g): + g.configure( + include=[ + g.person, + g.product_brand, + g.product_description, + g.product_size, + g.case_quantity, + g.cases_ordered, + g.units_ordered, + g.order_created, + g.status_code, + ], + readonly=True) + + def _preconfigure_fieldset(self, fs): + fs.order.set(renderer=forms.renderers.CustomerOrderFieldRenderer) + fs.product.set(renderer=forms.renderers.ProductFieldRenderer) + fs.product_unit_of_measure.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.UNIT_OF_MEASURE)) + fs.case_quantity.set(renderer=forms.renderers.QuantityFieldRenderer) + fs.cases_ordered.set(renderer=forms.renderers.QuantityFieldRenderer) + fs.units_ordered.set(renderer=forms.renderers.QuantityFieldRenderer) + fs.unit_price.set(renderer=forms.renderers.CurrencyFieldRenderer) + fs.total_price.set(renderer=forms.renderers.CurrencyFieldRenderer) + fs.paid_amount.set(renderer=forms.renderers.CurrencyFieldRenderer) + fs.status_code.set(label="Status") + fs.append(fa.Field('person', value=lambda i: i.order.person, + renderer=forms.renderers.PersonFieldRenderer)) + + def configure_fieldset(self, fs): + fs.configure( + include=[ + fs.person, + fs.product, + fs.product_brand, + fs.product_description, + fs.product_size, + fs.case_quantity, + fs.cases_ordered, + fs.units_ordered, + fs.unit_price, + fs.total_price, + fs.paid_amount, + fs.status_code, + ]) + + def get_row_data(self, item): + return self.Session.query(model.CustomerOrderItemEvent)\ + .filter(model.CustomerOrderItemEvent.item == item)\ + .order_by(model.CustomerOrderItemEvent.occurred, + model.CustomerOrderItemEvent.type_code) + + def _preconfigure_row_grid(self, g): + g.occurred.set(label="When") + g.type_code.set(label="What") # TODO: enum renderer + g.user.set(label="Who") + g.note.set(label="Notes") + + def configure_row_grid(self, g): + g.configure( + include=[ + g.occurred, + g.type_code, + g.user, + g.note, + ], + readonly=True) + + +def includeme(config): + CustomerOrderItemsView.defaults(config) diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py new file mode 100644 index 00000000..6ef4f5f8 --- /dev/null +++ b/tailbone/views/custorders/orders.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2017 Lance Edgar +# +# This file is part of Rattail. +# +# Rattail is free software: you can redistribute it and/or modify it under the +# terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +# more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Rattail. If not, see . +# +################################################################################ +""" +Customer Order Views +""" + +from __future__ import unicode_literals, absolute_import + +from sqlalchemy import orm + +from rattail.db import model + +from tailbone import forms +from tailbone.db import Session +from tailbone.views import MasterView +from tailbone.newgrids.filters import ChoiceValueRenderer + + +class CustomerOrdersView(MasterView): + """ + Master view for customer orders + """ + model_class = model.CustomerOrder + route_prefix = 'custorders' + creatable = False + editable = False + deletable = False + + def query(self, session): + return session.query(model.CustomerOrder)\ + .options(orm.joinedload(model.CustomerOrder.customer)) + + def _preconfigure_grid(self, g): + g.joiners['customer'] = lambda q: q.outerjoin(model.Customer) + g.sorters['customer'] = g.make_sorter(model.Customer.name) + g.filters['customer'] = g.make_filter('customer', model.Customer.name, + label="Customer Name", + default_active=True, + default_verb='contains') + + g.joiners['person'] = lambda q: q.outerjoin(model.Person) + g.sorters['person'] = g.make_sorter(model.Person.display_name) + g.filters['person'] = g.make_filter('person', model.Person.display_name, + label="Person Name", + default_active=True, + default_verb='contains') + + # TODO: enum choices renderer + g.filters['status_code'].label = "Status" + g.status_code.set(label="Status") + + g.id.set(label="ID") + g.default_sortkey = 'created' + g.default_sortdir = 'desc' + + def configure_grid(self, g): + g.configure( + include=[ + g.id, + g.customer, + g.person, + g.created, + g.status_code, + ], + readonly=True) + + def _preconfigure_fieldset(self, fs): + fs.customer.set(options=[]) + fs.id.set(label="ID", readonly=True) + fs.person.set(renderer=forms.renderers.PersonFieldRenderer) + fs.created.set(readonly=True) + fs.status_code.set(label="Status") + + def configure_fieldset(self, fs): + fs.configure( + include=[ + fs.id, + fs.customer, + fs.person, + fs.created, + fs.status_code, + ]) + + +def includeme(config): + CustomerOrdersView.defaults(config)