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)