Add support for new Purchase/Batch views, 'create row' master pattern
More refactoring here but hopefully not that important..
This commit is contained in:
parent
8fe0e96273
commit
a6e43d1658
|
@ -28,6 +28,12 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.newgrid-wrapper .grid-header td.tools p {
|
||||||
|
line-height: 2em;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 0.5em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
/******************************
|
/******************************
|
||||||
* filters
|
* filters
|
||||||
******************************/
|
******************************/
|
||||||
|
|
10
tailbone/templates/master/create_row.mako
Normal file
10
tailbone/templates/master/create_row.mako
Normal file
|
@ -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()">
|
||||||
|
<li>${h.link_to("Back to {}".format(model_title), index_url)}</li>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -10,7 +10,7 @@
|
||||||
<li>${h.link_to("Delete this {}".format(row_model_title), row_action_url('delete', instance))}</li>
|
<li>${h.link_to("Delete this {}".format(row_model_title), row_action_url('delete', instance))}</li>
|
||||||
% endif
|
% endif
|
||||||
% if master.rows_creatable and request.has_perm('{}.create'.format(row_permission_prefix)):
|
% if master.rows_creatable and request.has_perm('{}.create'.format(row_permission_prefix)):
|
||||||
<li>${h.link_to("Create a new {}".format(row_model_title), url('{}.create'.format(row_route_prefix)))}</li>
|
<li>${h.link_to("Create a new {}".format(row_model_title), url('{}.create_row'.format(route_prefix), uuid=row_parent.uuid))}</li>
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
|
11
tailbone/templates/purchases/batches/index.mako
Normal file
11
tailbone/templates/purchases/batches/index.mako
Normal file
|
@ -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'):
|
||||||
|
<li>${h.link_to("Go to Purchases", url('purchases'))}</li>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${parent.body()}
|
11
tailbone/templates/purchases/index.mako
Normal file
11
tailbone/templates/purchases/index.mako
Normal file
|
@ -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'):
|
||||||
|
<li>${h.link_to("Go to Purchase Batches", url('purchases.batch'))}</li>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -76,6 +76,7 @@ def includeme(config):
|
||||||
config.include('tailbone.views.people')
|
config.include('tailbone.views.people')
|
||||||
config.include('tailbone.views.products')
|
config.include('tailbone.views.products')
|
||||||
config.include('tailbone.views.progress')
|
config.include('tailbone.views.progress')
|
||||||
|
config.include('tailbone.views.purchases')
|
||||||
config.include('tailbone.views.reportcodes')
|
config.include('tailbone.views.reportcodes')
|
||||||
config.include('tailbone.views.reports')
|
config.include('tailbone.views.reports')
|
||||||
config.include('tailbone.views.roles')
|
config.include('tailbone.views.roles')
|
||||||
|
|
|
@ -182,10 +182,7 @@ class BatchMasterView(MasterView):
|
||||||
views are encouraged to override this method.
|
views are encouraged to override this method.
|
||||||
"""
|
"""
|
||||||
if self.creating:
|
if self.creating:
|
||||||
fs.configure(
|
fs.configure()
|
||||||
include=[
|
|
||||||
fs.created_by.hidden(),
|
|
||||||
])
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
batch = fs.model
|
batch = fs.model
|
||||||
|
@ -208,21 +205,31 @@ class BatchMasterView(MasterView):
|
||||||
|
|
||||||
def _postconfigure_fieldset(self, fs):
|
def _postconfigure_fieldset(self, fs):
|
||||||
if self.creating:
|
if self.creating:
|
||||||
if 'created' in fs.render_fields:
|
unwanted = [
|
||||||
del fs.created
|
'id',
|
||||||
if 'created_by' in fs.render_fields:
|
'rowcount',
|
||||||
del fs.created_by
|
'created',
|
||||||
if 'executed' in fs.render_fields:
|
'created_by',
|
||||||
del fs.executed
|
'cognized',
|
||||||
if 'executed_by' in fs.render_fields:
|
'cognized_by',
|
||||||
del fs.executed_by
|
'executed',
|
||||||
|
'executed_by',
|
||||||
|
'purge',
|
||||||
|
'data_rows',
|
||||||
|
]
|
||||||
|
for field in unwanted:
|
||||||
|
if field in fs.render_fields:
|
||||||
|
delattr(fs, field)
|
||||||
else:
|
else:
|
||||||
batch = fs.model
|
batch = fs.model
|
||||||
if not batch.executed:
|
if not batch.executed:
|
||||||
if 'executed' in fs.render_fields:
|
unwanted = [
|
||||||
del fs.executed
|
'executed',
|
||||||
if 'executed_by' in fs.render_fields:
|
'executed_by',
|
||||||
del fs.executed_by
|
]
|
||||||
|
for field in unwanted:
|
||||||
|
if field in fs.render_fields:
|
||||||
|
delattr(fs, field)
|
||||||
|
|
||||||
def save_create_form(self, form):
|
def save_create_form(self, form):
|
||||||
"""
|
"""
|
||||||
|
@ -325,11 +332,14 @@ class BatchMasterView(MasterView):
|
||||||
|
|
||||||
return self.render_to_response('edit', context)
|
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:
|
if not batch.executed:
|
||||||
url = self.request.route_url('{}.delete_rows'.format(self.get_route_prefix()), uuid=batch.uuid)
|
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))
|
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):
|
def redirect_after_edit(self, batch):
|
||||||
"""
|
"""
|
||||||
Redirect back to edit batch page after editing a batch, unless the
|
Redirect back to edit batch page after editing a batch, unless the
|
||||||
|
@ -464,7 +474,7 @@ class BatchMasterView(MasterView):
|
||||||
if progress:
|
if progress:
|
||||||
progress.session.load()
|
progress.session.load()
|
||||||
progress.session['error'] = True
|
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()
|
progress.session.save()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -619,7 +629,7 @@ class BatchMasterView(MasterView):
|
||||||
if progress:
|
if progress:
|
||||||
progress.session.load()
|
progress.session.load()
|
||||||
progress.session['error'] = True
|
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()
|
progress.session.save()
|
||||||
|
|
||||||
# If no error, check result flag (false means user canceled).
|
# If no error, check result flag (false means user canceled).
|
||||||
|
|
|
@ -36,6 +36,7 @@ from rattail.util import prettify
|
||||||
import formalchemy
|
import formalchemy
|
||||||
from pyramid import httpexceptions
|
from pyramid import httpexceptions
|
||||||
from pyramid.renderers import get_renderer, render_to_response, render
|
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 import forms, newgrids as grids
|
||||||
from tailbone.views import View
|
from tailbone.views import View
|
||||||
|
@ -179,8 +180,14 @@ class MasterView(View):
|
||||||
tools=self.make_row_grid_tools(instance))
|
tools=self.make_row_grid_tools(instance))
|
||||||
return self.render_to_response('view', context)
|
return self.render_to_response('view', context)
|
||||||
|
|
||||||
def make_row_grid_tools(self, instance):
|
def make_default_row_grid_tools(self, obj):
|
||||||
pass
|
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):
|
def make_row_grid(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -296,6 +303,8 @@ class MasterView(View):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_row_model_title(cls):
|
def get_row_model_title(cls):
|
||||||
|
if hasattr(cls, 'row_model_title'):
|
||||||
|
return cls.row_model_title
|
||||||
return "{} Row".format(cls.get_model_title())
|
return "{} Row".format(cls.get_model_title())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -1022,6 +1031,40 @@ class MasterView(View):
|
||||||
# Associated Rows Stuff
|
# 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):
|
def view_row(self):
|
||||||
"""
|
"""
|
||||||
View for viewing details of a single data row.
|
View for viewing details of a single data row.
|
||||||
|
@ -1061,6 +1104,7 @@ class MasterView(View):
|
||||||
parent = self.get_parent(row)
|
parent = self.get_parent(row)
|
||||||
return self.render_to_response('edit_row', {
|
return self.render_to_response('edit_row', {
|
||||||
'instance': row,
|
'instance': row,
|
||||||
|
'row_parent': parent,
|
||||||
'instance_title': self.get_row_instance_title(row),
|
'instance_title': self.get_row_instance_title(row),
|
||||||
'instance_deletable': self.row_deletable(row),
|
'instance_deletable': self.row_deletable(row),
|
||||||
'index_url': self.get_action_url('view', parent),
|
'index_url': self.get_action_url('view', parent),
|
||||||
|
@ -1138,10 +1182,11 @@ class MasterView(View):
|
||||||
self.configure_row_fieldset(fieldset)
|
self.configure_row_fieldset(fieldset)
|
||||||
|
|
||||||
kwargs.setdefault('action_url', self.request.current_route_url(_query=None))
|
kwargs.setdefault('action_url', self.request.current_route_url(_query=None))
|
||||||
if self.creating:
|
if 'cancel_url' not in kwargs:
|
||||||
kwargs.setdefault('cancel_url', self.get_action_url('view', self.get_parent(instance)))
|
if self.creating:
|
||||||
else:
|
kwargs['cancel_url'] = self.get_action_url('view', self.get_parent(instance))
|
||||||
kwargs.setdefault('cancel_url', self.get_row_action_url('view', instance))
|
else:
|
||||||
|
kwargs['cancel_url'] = self.get_row_action_url('view', instance)
|
||||||
|
|
||||||
form = forms.AlchemyForm(self.request, fieldset, **kwargs)
|
form = forms.AlchemyForm(self.request, fieldset, **kwargs)
|
||||||
form.readonly = self.viewing
|
form.readonly = self.viewing
|
||||||
|
@ -1253,6 +1298,14 @@ class MasterView(View):
|
||||||
|
|
||||||
### sub-rows stuff follows
|
### 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
|
# view row
|
||||||
if cls.has_rows and cls.rows_viewable:
|
if cls.has_rows and cls.rows_viewable:
|
||||||
config.add_route('{}.view'.format(row_route_prefix), '{}/{{uuid}}'.format(row_url_prefix))
|
config.add_route('{}.view'.format(row_route_prefix), '{}/{{uuid}}'.format(row_url_prefix))
|
||||||
|
|
32
tailbone/views/purchases/__init__.py
Normal file
32
tailbone/views/purchases/__init__.py
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
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')
|
232
tailbone/views/purchases/batch.py
Normal file
232
tailbone/views/purchases/batch.py
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
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)
|
163
tailbone/views/purchases/core.py
Normal file
163
tailbone/views/purchases/core.py
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
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)
|
Loading…
Reference in a new issue