diff --git a/tailbone/static/css/newgrids.css b/tailbone/static/css/newgrids.css
index 66351494..4b19509a 100644
--- a/tailbone/static/css/newgrids.css
+++ b/tailbone/static/css/newgrids.css
@@ -28,6 +28,12 @@
white-space: nowrap;
}
+.newgrid-wrapper .grid-header td.tools p {
+ line-height: 2em;
+ margin: 0;
+ padding: 0 0.5em 0 0;
+}
+
/******************************
* filters
******************************/
diff --git a/tailbone/templates/master/create_row.mako b/tailbone/templates/master/create_row.mako
new file mode 100644
index 00000000..1ad0ab1f
--- /dev/null
+++ b/tailbone/templates/master/create_row.mako
@@ -0,0 +1,10 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/master/create.mako" />
+
+<%def name="title()">New ${row_model_title}%def>
+
+<%def name="context_menu_items()">
+
${h.link_to("Back to {}".format(model_title), index_url)}
+%def>
+
+${parent.body()}
diff --git a/tailbone/templates/master/edit_row.mako b/tailbone/templates/master/edit_row.mako
index c3cfc0af..dab77592 100644
--- a/tailbone/templates/master/edit_row.mako
+++ b/tailbone/templates/master/edit_row.mako
@@ -10,7 +10,7 @@
${h.link_to("Delete this {}".format(row_model_title), row_action_url('delete', instance))}
% endif
% if master.rows_creatable and request.has_perm('{}.create'.format(row_permission_prefix)):
- ${h.link_to("Create a new {}".format(row_model_title), url('{}.create'.format(row_route_prefix)))}
+ ${h.link_to("Create a new {}".format(row_model_title), url('{}.create_row'.format(route_prefix), uuid=row_parent.uuid))}
% endif
%def>
diff --git a/tailbone/templates/purchases/batches/index.mako b/tailbone/templates/purchases/batches/index.mako
new file mode 100644
index 00000000..346a409c
--- /dev/null
+++ b/tailbone/templates/purchases/batches/index.mako
@@ -0,0 +1,11 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/master/index.mako" />
+
+<%def name="context_menu_items()">
+ ${parent.context_menu_items()}
+ % if request.has_perm('purchases.batch'):
+ ${h.link_to("Go to Purchases", url('purchases'))}
+ % endif
+%def>
+
+${parent.body()}
diff --git a/tailbone/templates/purchases/index.mako b/tailbone/templates/purchases/index.mako
new file mode 100644
index 00000000..d9d44c2b
--- /dev/null
+++ b/tailbone/templates/purchases/index.mako
@@ -0,0 +1,11 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/master/index.mako" />
+
+<%def name="context_menu_items()">
+ ${parent.context_menu_items()}
+ % if request.has_perm('purchases.batch.list'):
+ ${h.link_to("Go to Purchase Batches", url('purchases.batch'))}
+ % endif
+%def>
+
+${parent.body()}
diff --git a/tailbone/views/__init__.py b/tailbone/views/__init__.py
index a1f6502f..a716abca 100644
--- a/tailbone/views/__init__.py
+++ b/tailbone/views/__init__.py
@@ -76,6 +76,7 @@ def includeme(config):
config.include('tailbone.views.people')
config.include('tailbone.views.products')
config.include('tailbone.views.progress')
+ config.include('tailbone.views.purchases')
config.include('tailbone.views.reportcodes')
config.include('tailbone.views.reports')
config.include('tailbone.views.roles')
diff --git a/tailbone/views/batch.py b/tailbone/views/batch.py
index 4e726a00..05f66955 100644
--- a/tailbone/views/batch.py
+++ b/tailbone/views/batch.py
@@ -182,10 +182,7 @@ class BatchMasterView(MasterView):
views are encouraged to override this method.
"""
if self.creating:
- fs.configure(
- include=[
- fs.created_by.hidden(),
- ])
+ fs.configure()
else:
batch = fs.model
@@ -208,21 +205,31 @@ class BatchMasterView(MasterView):
def _postconfigure_fieldset(self, fs):
if self.creating:
- if 'created' in fs.render_fields:
- del fs.created
- if 'created_by' in fs.render_fields:
- del fs.created_by
- if 'executed' in fs.render_fields:
- del fs.executed
- if 'executed_by' in fs.render_fields:
- del fs.executed_by
+ unwanted = [
+ 'id',
+ 'rowcount',
+ 'created',
+ 'created_by',
+ 'cognized',
+ 'cognized_by',
+ 'executed',
+ 'executed_by',
+ 'purge',
+ 'data_rows',
+ ]
+ for field in unwanted:
+ if field in fs.render_fields:
+ delattr(fs, field)
else:
batch = fs.model
if not batch.executed:
- if 'executed' in fs.render_fields:
- del fs.executed
- if 'executed_by' in fs.render_fields:
- del fs.executed_by
+ unwanted = [
+ 'executed',
+ 'executed_by',
+ ]
+ for field in unwanted:
+ if field in fs.render_fields:
+ delattr(fs, field)
def save_create_form(self, form):
"""
@@ -325,11 +332,14 @@ class BatchMasterView(MasterView):
return self.render_to_response('edit', context)
- def make_row_grid_tools(self, batch):
+ def make_batch_row_grid_tools(self, batch):
if not batch.executed:
url = self.request.route_url('{}.delete_rows'.format(self.get_route_prefix()), uuid=batch.uuid)
return HTML.tag('p', c=tags.link_to("Delete all rows matching current search", url))
+ def make_row_grid_tools(self, batch):
+ return self.make_batch_row_grid_tools(batch)
+
def redirect_after_edit(self, batch):
"""
Redirect back to edit batch page after editing a batch, unless the
@@ -464,7 +474,7 @@ class BatchMasterView(MasterView):
if progress:
progress.session.load()
progress.session['error'] = True
- progress.session['error_msg'] = "Data refresh failed: {}".format(error)
+ progress.session['error_msg'] = "Data refresh failed: {} {}".format(error.__class__.__name__, error)
progress.session.save()
return
@@ -619,7 +629,7 @@ class BatchMasterView(MasterView):
if progress:
progress.session.load()
progress.session['error'] = True
- progress.session['error_msg'] = "Batch execution failed: {}".format(error)
+ progress.session['error_msg'] = "Batch execution failed: {}: {}".format(type(error).__name__, error)
progress.session.save()
# If no error, check result flag (false means user canceled).
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index e9be3536..ecbc357e 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -36,6 +36,7 @@ from rattail.util import prettify
import formalchemy
from pyramid import httpexceptions
from pyramid.renderers import get_renderer, render_to_response, render
+from webhelpers.html import HTML, tags
from tailbone import forms, newgrids as grids
from tailbone.views import View
@@ -179,8 +180,14 @@ class MasterView(View):
tools=self.make_row_grid_tools(instance))
return self.render_to_response('view', context)
- def make_row_grid_tools(self, instance):
- pass
+ def make_default_row_grid_tools(self, obj):
+ if self.rows_creatable:
+ link = tags.link_to("Create a new {}".format(self.get_row_model_title()),
+ self.get_action_url('create_row', obj))
+ return HTML.tag('p', c=link)
+
+ def make_row_grid_tools(self, obj):
+ return self.make_default_row_grid_tools(obj)
def make_row_grid(self, **kwargs):
"""
@@ -296,6 +303,8 @@ class MasterView(View):
@classmethod
def get_row_model_title(cls):
+ if hasattr(cls, 'row_model_title'):
+ return cls.row_model_title
return "{} Row".format(cls.get_model_title())
@classmethod
@@ -1022,6 +1031,40 @@ class MasterView(View):
# Associated Rows Stuff
##############################
+ def create_row(self):
+ """
+ View for creating a new row record.
+ """
+ self.creating = True
+ parent = self.get_instance()
+ index_url = self.get_action_url('view', parent)
+ form = self.make_row_form(self.model_row_class, cancel_url=index_url)
+ if self.request.method == 'POST':
+ if form.validate():
+ self.before_create_row(form)
+ self.save_create_row_form(form)
+ obj = form.fieldset.model
+ self.after_create_row(obj)
+ return self.redirect_after_create_row(obj)
+ return self.render_to_response('create_row', {
+ 'index_url': index_url,
+ 'index_title': '{} {}'.format(
+ self.get_model_title(),
+ self.get_instance_title(parent)),
+ 'form': form})
+
+ def save_create_row_form(self, form):
+ self.save_row_form(form)
+
+ def before_create_row(self, form):
+ pass
+
+ def after_create_row(self, row_object):
+ pass
+
+ def redirect_after_create_row(self, row):
+ return self.redirect(self.get_action_url('view', self.get_parent(row)))
+
def view_row(self):
"""
View for viewing details of a single data row.
@@ -1061,6 +1104,7 @@ class MasterView(View):
parent = self.get_parent(row)
return self.render_to_response('edit_row', {
'instance': row,
+ 'row_parent': parent,
'instance_title': self.get_row_instance_title(row),
'instance_deletable': self.row_deletable(row),
'index_url': self.get_action_url('view', parent),
@@ -1138,10 +1182,11 @@ class MasterView(View):
self.configure_row_fieldset(fieldset)
kwargs.setdefault('action_url', self.request.current_route_url(_query=None))
- if self.creating:
- kwargs.setdefault('cancel_url', self.get_action_url('view', self.get_parent(instance)))
- else:
- kwargs.setdefault('cancel_url', self.get_row_action_url('view', instance))
+ if 'cancel_url' not in kwargs:
+ if self.creating:
+ kwargs['cancel_url'] = self.get_action_url('view', self.get_parent(instance))
+ else:
+ kwargs['cancel_url'] = self.get_row_action_url('view', instance)
form = forms.AlchemyForm(self.request, fieldset, **kwargs)
form.readonly = self.viewing
@@ -1253,6 +1298,14 @@ class MasterView(View):
### sub-rows stuff follows
+ # create row
+ if cls.has_rows and cls.rows_creatable:
+ config.add_route('{}.create_row'.format(route_prefix), '{}/{{{}}}/new-row'.format(url_prefix, model_key))
+ config.add_view(cls, attr='create_row', route_name='{}.create_row'.format(route_prefix),
+ permission='{}.create_row'.format(permission_prefix))
+ config.add_tailbone_permission(permission_prefix, '{}.create_row'.format(permission_prefix),
+ "Create new {} rows".format(model_title))
+
# view row
if cls.has_rows and cls.rows_viewable:
config.add_route('{}.view'.format(row_route_prefix), '{}/{{uuid}}'.format(row_url_prefix))
diff --git a/tailbone/views/purchases/__init__.py b/tailbone/views/purchases/__init__.py
new file mode 100644
index 00000000..f7c8bd1a
--- /dev/null
+++ b/tailbone/views/purchases/__init__.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+################################################################################
+#
+# Rattail -- Retail Software Framework
+# Copyright © 2010-2016 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 .
+#
+################################################################################
+"""
+Views for purchase orders
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+
+def includeme(config):
+ config.include('tailbone.views.purchases.core')
+ config.include('tailbone.views.purchases.batch')
diff --git a/tailbone/views/purchases/batch.py b/tailbone/views/purchases/batch.py
new file mode 100644
index 00000000..19572ca3
--- /dev/null
+++ b/tailbone/views/purchases/batch.py
@@ -0,0 +1,232 @@
+# -*- coding: utf-8 -*-
+################################################################################
+#
+# Rattail -- Retail Software Framework
+# Copyright © 2010-2016 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 .
+#
+################################################################################
+"""
+Views for purchase order batches
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+from rattail.db import model, api
+from rattail.gpc import GPC
+from rattail.db.batch.purchase.handler import PurchaseBatchHandler
+from rattail.time import localtime
+
+import formalchemy as fa
+
+from tailbone import forms
+from tailbone.db import Session
+from tailbone.views.batch import BatchMasterView
+
+
+class PurchaseBatchView(BatchMasterView):
+ """
+ Master view for purchase order batches.
+ """
+ model_class = model.PurchaseBatch
+ model_title_plural = "Purchase Batches"
+ model_row_class = model.PurchaseBatchRow
+ batch_handler_class = PurchaseBatchHandler
+ route_prefix = 'purchases.batch'
+ url_prefix = '/purchases/batches'
+ rows_creatable = True
+ rows_editable = True
+
+ def _preconfigure_grid(self, g):
+ super(PurchaseBatchView, self)._preconfigure_grid(g)
+
+ g.joiners['vendor'] = lambda q: q.join(model.Vendor)
+ g.filters['vendor'] = g.make_filter('vendor', model.Vendor.name,
+ default_active=True, default_verb='contains')
+ g.sorters['vendor'] = g.make_sorter(model.Vendor.name)
+
+ g.joiners['buyer'] = lambda q: q.join(model.Employee).join(model.Person)
+ g.filters['buyer'] = g.make_filter('buyer', model.Person.display_name,
+ default_active=True, default_verb='contains')
+ g.sorters['buyer'] = g.make_sorter(model.Person.display_name)
+
+ g.date_ordered.set(label="Ordered")
+
+ def configure_grid(self, g):
+ g.configure(
+ include=[
+ g.id,
+ g.vendor,
+ g.buyer,
+ g.date_ordered,
+ g.created,
+ g.created_by,
+ g.executed,
+ ],
+ readonly=True)
+
+ def _preconfigure_fieldset(self, fs):
+ super(PurchaseBatchView, self)._preconfigure_fieldset(fs)
+ fs.po_number.set(label="PO Number")
+ fs.po_total.set(label="PO Total")
+
+ def configure_fieldset(self, fs):
+ fs.configure(
+ include=[
+ fs.store,
+ fs.vendor.with_renderer(forms.renderers.VendorFieldRenderer),
+ fs.buyer.with_renderer(forms.renderers.EmployeeFieldRenderer),
+ fs.date_ordered,
+ fs.po_number,
+ fs.po_total,
+ fs.created,
+ fs.created_by,
+ fs.executed,
+ fs.executed_by,
+ ])
+
+ if self.creating:
+ del fs.po_total
+
+ # default store may be configured
+ store = self.rattail_config.get('rattail', 'store')
+ if store:
+ store = api.get_store(Session(), store)
+ if store:
+ fs.model.store = store
+
+ # default buyer is current user
+ if self.request.method != 'POST':
+ buyer = self.request.user.employee
+ if buyer:
+ fs.model.buyer = buyer
+
+ # default order date is today
+ fs.model.date_ordered = localtime(self.rattail_config).date()
+
+ def _preconfigure_row_grid(self, g):
+ super(PurchaseBatchView, self)._preconfigure_row_grid(g)
+
+ g.filters['upc'].label = "UPC"
+ g.filters['brand_name'].label = "Brand"
+
+ g.upc.set(label="UPC")
+ g.brand_name.set(label="Brand")
+ g.cases_ordered.set(label="Cases")
+ g.units_ordered.set(label="Units")
+ g.po_total.set(label="Total")
+
+ def configure_row_grid(self, g):
+ g.configure(
+ include=[
+ g.sequence,
+ g.upc,
+ g.brand_name,
+ g.description,
+ g.size,
+ g.cases_ordered,
+ g.units_ordered,
+ g.po_total,
+ g.status_code,
+ ],
+ readonly=True)
+
+ def make_row_grid_tools(self, batch):
+ return self.make_default_row_grid_tools(batch)
+
+# def row_grid_row_attrs(self, row, i):
+# attrs = {}
+# if row.status_code in (row.STATUS_NOT_IN_PURCHASE,
+# row.STATUS_NOT_IN_INVOICE,
+# row.STATUS_DIFFERS_FROM_PURCHASE):
+# attrs['class_'] = 'notice'
+# if row.status_code in (row.STATUS_NOT_IN_DB,
+# row.STATUS_COST_NOT_IN_DB,
+# row.STATUS_NO_CASE_QUANTITY):
+# attrs['class_'] = 'warning'
+# return attrs
+
+ def _preconfigure_row_fieldset(self, fs):
+ super(PurchaseBatchView, self)._preconfigure_row_fieldset(fs)
+ fs.upc.set(label="UPC")
+ fs.brand_name.set(label="Brand")
+ fs.po_unit_cost.set(label="PO Unit Cost")
+ fs.po_total.set(label="PO Total")
+ fs.append(fa.Field('item_lookup', label="Item Lookup Code", required=True,
+ validate=self.item_lookup))
+
+ def item_lookup(self, value, field=None):
+ """
+ Try to locate a single product using ``value`` as a lookup code.
+ """
+ batch = self.get_instance()
+ product = api.get_product_by_vendor_code(Session(), value, vendor=batch.vendor)
+ if product:
+ return product.uuid
+ if value.isdigit():
+ product = api.get_product_by_upc(Session(), GPC(value))
+ if not product:
+ product = api.get_product_by_upc(Session(), GPC(value, calc_check_digit='upc'))
+ if product:
+ if not product.cost_for_vendor(batch.vendor):
+ raise fa.ValidationError("Product {} exists but has no cost for vendor {}".format(
+ product.upc.pretty(), batch.vendor))
+ return product.uuid
+ raise fa.ValidationError("Product not found")
+
+ def configure_row_fieldset(self, fs):
+
+ if self.creating:
+ fs.configure(
+ include=[
+ fs.item_lookup,
+ fs.cases_ordered,
+ fs.units_ordered,
+ ])
+
+ elif self.editing:
+ fs.configure(
+ include=[
+ fs.upc.readonly(),
+ fs.product.readonly(),
+ fs.cases_ordered,
+ fs.units_ordered,
+ ])
+
+ def before_create_row(self, form):
+ row = form.fieldset.model
+ batch = self.get_instance()
+ row.sequence = max([0] + [r.sequence for r in batch.data_rows]) + 1
+ row.batch = batch
+ # TODO: this seems heavy-handed but works..
+ row.product_uuid = self.item_lookup(form.fieldset.item_lookup.value)
+
+ def after_create_row(self, row):
+ self.handler.refresh_row(row)
+
+ def redirect_after_create_row(self, row):
+ self.request.session.flash("Added item: {} {}".format(row.upc.pretty(), row.product))
+ return self.redirect(self.request.current_route_url())
+
+ # TODO: redirect to new purchase...
+ # def get_execute_success_url(self, batch, result, **kwargs):
+ # # return self.get_action_url('view', batch)
+ # return
+
+
+def includeme(config):
+ PurchaseBatchView.defaults(config)
diff --git a/tailbone/views/purchases/core.py b/tailbone/views/purchases/core.py
new file mode 100644
index 00000000..6c568a43
--- /dev/null
+++ b/tailbone/views/purchases/core.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+################################################################################
+#
+# Rattail -- Retail Software Framework
+# Copyright © 2010-2016 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 .
+#
+################################################################################
+"""
+Views for "true" purchase orders
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+from rattail import enum
+from rattail.db import model
+
+import formalchemy as fa
+
+from tailbone import forms
+from tailbone.db import Session
+from tailbone.views import MasterView
+
+
+class PurchaseView(MasterView):
+ """
+ Master view for purchase orders.
+ """
+ model_class = model.Purchase
+ creatable = False
+ editable = False
+
+ has_rows = True
+ model_row_class = model.PurchaseItem
+ row_model_title = 'Purchase Item'
+
+ def _preconfigure_grid(self, g):
+ g.joiners['store'] = lambda q: q.join(model.Store)
+ g.filters['store'] = g.make_filter('store', model.Store.name)
+ g.sorters['store'] = g.make_sorter(model.Store.name)
+
+ g.joiners['vendor'] = lambda q: q.join(model.Vendor)
+ g.filters['vendor'] = g.make_filter('vendor', model.Vendor.name,
+ default_active=True, default_verb='contains')
+ g.sorters['vendor'] = g.make_sorter(model.Vendor.name)
+
+ g.joiners['buyer'] = lambda q: q.join(model.Employee).join(model.Person)
+ g.filters['buyer'] = g.make_filter('buyer', model.Person.display_name,
+ default_active=True, default_verb='contains')
+ g.sorters['buyer'] = g.make_sorter(model.Person.display_name)
+
+ g.filters['date_ordered'].label = "Ordered"
+ g.filters['date_ordered'].default_active = True
+ g.filters['date_ordered'].default_verb = 'equal'
+
+ g.default_sortkey = 'date_ordered'
+ g.default_sortdir = 'desc'
+
+ g.date_ordered.set(label="Ordered")
+ g.status.set(renderer=forms.renderers.EnumFieldRenderer(enum.PURCHASE_STATUS))
+
+ def configure_grid(self, g):
+ g.configure(
+ include=[
+ g.store,
+ g.vendor,
+ g.buyer,
+ g.date_ordered,
+ g.status,
+ ],
+ readonly=True)
+
+ def _preconfigure_fieldset(self, fs):
+ fs.vendor.set(renderer=forms.renderers.VendorFieldRenderer)
+ fs.status.set(renderer=forms.renderers.EnumFieldRenderer(enum.PURCHASE_STATUS),
+ readonly=True)
+ fs.po_number.set(label="PO Number")
+ fs.po_total.set(label="PO Total")
+
+ def configure_fieldset(self, fs):
+ fs.configure(
+ include=[
+ fs.store,
+ fs.vendor,
+ fs.buyer,
+ fs.date_ordered,
+ fs.po_number,
+ fs.po_total,
+ fs.status,
+ fs.created,
+ fs.created_by,
+ ])
+
+ def get_parent(self, item):
+ return item.purchase
+
+ def get_row_data(self, purchase):
+ return Session.query(model.PurchaseItem)\
+ .filter(model.PurchaseItem.purchase == purchase)
+
+ def _preconfigure_row_grid(self, g):
+ g.default_sortkey = 'sequence'
+ g.sequence.set(label="Seq")
+ g.upc.set(label="UPC")
+ g.brand_name.set(label="Brand")
+ g.cases_ordered.set(label="Cases")
+ g.units_ordered.set(label="Units")
+ g.po_total.set(label="PO Total")
+
+ def configure_row_grid(self, g):
+ g.configure(
+ include=[
+ g.sequence,
+ g.upc,
+ g.brand_name,
+ g.description,
+ g.size,
+ g.cases_ordered,
+ g.units_ordered,
+ g.po_total,
+ ],
+ readonly=True)
+
+ def _preconfigure_row_fieldset(self, fs):
+ fs.vendor_code.set(label="Vendor Item Code")
+ fs.upc.set(label="UPC")
+ fs.po_unit_cost.set(label="PO Unit Cost")
+ fs.po_total.set(label="PO Total")
+ fs.append(fa.Field('department', value=lambda i: '{} {}'.format(i.department_number, i.department_name)))
+
+ def configure_row_fieldset(self, fs):
+
+ fs.configure(
+ include=[
+ fs.sequence,
+ fs.vendor_code,
+ fs.upc,
+ fs.product,
+ fs.department,
+ fs.case_quantity,
+ fs.cases_ordered,
+ fs.units_ordered,
+ fs.po_unit_cost,
+ fs.po_total,
+ ])
+
+
+def includeme(config):
+ PurchaseView.defaults(config)