update grids per edbob changes, add order worksheet report

This commit is contained in:
Lance Edgar 2012-08-06 15:05:09 -07:00
parent 84b1eec937
commit a4f2b6d5c2
10 changed files with 446 additions and 156 deletions

View file

@ -26,6 +26,8 @@
``rattail.pyramid.forms`` -- Rattail Forms ``rattail.pyramid.forms`` -- Rattail Forms
""" """
from webhelpers.html import literal
import formalchemy import formalchemy
# from formalchemy.fields import SelectFieldRenderer # from formalchemy.fields import SelectFieldRenderer
@ -70,11 +72,11 @@ class PriceFieldRenderer(formalchemy.FieldRenderer):
if price: if price:
if price.price is not None and price.pack_price is not None: if price.price is not None and price.pack_price is not None:
if price.multiple > 1: if price.multiple > 1:
return '$ %0.2f / %u  ($ %0.2f / %u)' % ( return literal('$ %0.2f / %u  ($ %0.2f / %u)' % (
price.price, price.multiple, price.price, price.multiple,
price.pack_price, price.pack_multiple) price.pack_price, price.pack_multiple))
return '$ %0.2f  ($ %0.2f / %u)' % ( return literal('$ %0.2f  ($ %0.2f / %u)' % (
price.price, price.pack_price, price.pack_multiple) price.price, price.pack_price, price.pack_multiple))
if price.price is not None: if price.price is not None:
if price.multiple > 1: if price.multiple > 1:
return '$ %0.2f / %u' % (price.price, price.multiple) return '$ %0.2f / %u' % (price.price, price.multiple)

View file

@ -0,0 +1,115 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html style="direction: ltr;" xmlns="http://www.w3.org/1999/xhtml" lang="en-us">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>Ordering Worksheet : ${vendor.name}</title>
<style type="text/css">
h1 {
font-size: 24px;
margin: 10px 0px;
}
h2 {
font-size: 20px;
margin: 0px;
padding: 0px;
}
table {
border-bottom: 1px solid #000000;
border-left: 1px solid #000000;
border-collapse: collapse;
empty-cells: show;
}
th {
border-right: 1px solid #000000;
border-top: 1px solid #000000;
padding: 4px 8px;
}
th.department {
border-left: none;
font-size: 1.2em;
padding: 15px;
text-align: left;
text-transform: uppercase;
}
th.subdepartment {
border-left: none;
padding: 15px;
text-align: left;
}
td {
border-right: 1px solid #000000;
border-top: 1px solid #000000;
padding: 2px 4px;
white-space: nowrap;
}
td.upc,
td.case-qty,
td.code,
td.preferred {
text-align: center;
}
td.scratch_pad {
width: 20px;
}
td.spacer {
height: 50px;
}
</style>
</head>
<body>
<h1>Ordering Worksheet</h1>
<h2>Vendor:&nbsp; ${vendor.name} (${vendor.id})</h2>
<h2>Phone:&nbsp; ${vendor.phone or ''}</h2>
<h2>Contact:&nbsp; ${vendor.contact or ''}</h2>
<h3>generated on ${date} at ${time}</h3>
<br clear="all" />
<table>
% for dept in sorted(costs, key=lambda x: x.name):
<tr>
<th class="department" colspan="19">Department:&nbsp; ${dept.name} (${dept.number})</th>
</tr>
% for subdept in sorted(costs[dept], key=lambda x: x.name):
<tr>
<th class="subdepartment" colspan="19">Subdepartment:&nbsp; ${subdept.name} (${subdept.number})</th>
</tr>
<tr>
<th>UPC</th>
<th>Description</th>
<th>Case Qty.</th>
<th>Vend. Code</th>
<th>Preferred</th>
<th colspan="14">Order Scratch Pad</th>
</tr>
% for cost in sorted(costs[dept][subdept], key=lambda x: x.product.description):
<tr>
<td class="upc">${get_upc(cost.product)}</td>
<td class="desc">${cost.product.description}</td>
<td class="case-qty">${cost.case_size} ${rattail.UNIT_OF_MEASURE.get(cost.product.unit_of_measure, '')}</td>
<td class="code">${cost.code or ''}</td>
<td class="preferred">${'X' if cost.preference == 1 else ''}</td>
% for i in range(14):
<td class="scratch_pad">&nbsp;</td>
% endfor
</tr>
% endfor
<tr>
<td class="spacer" colspan="19">
</tr>
% endfor
% endfor
</table>
</body>
</html>

View file

@ -38,7 +38,7 @@
<script language="javascript" type="text/javascript"> <script language="javascript" type="text/javascript">
$(function() { $(function() {
$('div.grid.Product a.print-label').live('click', function() { $('div.grid a.print-label').live('click', function() {
var quantity = $('#label-quantity').val(); var quantity = $('#label-quantity').val();
if (isNaN(quantity)) { if (isNaN(quantity)) {
alert("You must provide a valid label quantity."); alert("You must provide a valid label quantity.");

View file

@ -0,0 +1,2 @@
<%inherit file="/base.mako" />
${parent.body()}

View file

@ -0,0 +1,84 @@
<%inherit file="/reports/base.mako" />
<%def name="title()">Report : Ordering Worksheet</%def>
<%def name="head_tags()">
${parent.head_tags()}
<style type="text/css">
div.grid {
clear: none;
}
</style>
</%def>
<p>Please provide the following criteria to generate your report:</p>
<br />
${h.form(request.current_route_url())}
${h.hidden('departments', value='')}
<div class="field-couple">
${h.hidden('vendor', value='')}
<label for="vendor-name">Vendor:</label>
${h.text('vendor-name', size='40', value='')}
<div id="vendor-display" style="display: none;">
<span>(no vendor)</span>&nbsp;
<button type="button" id="change-vendor">Change</button>
</div>
</div>
<div class="field-couple">
<label>Departments:</label>
<div class="grid"></div>
</div>
<div class="buttons">
${h.submit('submit', "Generate Report")}
</div>
${h.end_form()}
<script type="text/javascript">
$(function() {
var autocompleter = $('#vendor-name').autocomplete({
serviceUrl: '${url('vendors.autocomplete')}',
width: 300,
onSelect: function(value, data) {
$('#vendor').val(data);
$('#vendor-name').hide();
$('#vendor-name').val('');
$('#vendor-display span').html(value);
$('#vendor-display').show();
loading($('div.grid'));
$('div.grid').load('${url('departments.by_vendor')}', {'uuid': data});
},
});
$('#vendor-name').focus();
$('#change-vendor').click(function() {
$('#vendor').val('');
$('#vendor-display').hide();
$('#vendor-name').show();
$('#vendor-name').focus();
$('div.grid').empty();
});
$('form').submit(function() {
var depts = [];
$('div.grid table tbody tr').each(function() {
if ($(this).find('td.checkbox input[type=checkbox]').is(':checked')) {
depts.push(get_uuid(this));
}
$('#departments').val(depts.toString());
return true;
});
});
});
</script>

View file

@ -26,80 +26,92 @@
``rattail.pyramid.views.departments`` -- Department Views ``rattail.pyramid.views.departments`` -- Department Views
""" """
import transaction # import transaction
from pyramid.httpexceptions import HTTPFound # from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config # from pyramid.view import view_config
from edbob.pyramid import filters # from edbob.pyramid import Session
from edbob.pyramid import forms from edbob.pyramid.views import SearchableAlchemyGridView, AlchemyGridView
from edbob.pyramid import grids
from edbob.pyramid import Session
import rattail import rattail
@view_config(route_name='departments.list', renderer='/departments/index.mako') # @view_config(route_name='department.delete')
def list_departments(context, request): # def delete_department(context, request):
# uuid = request.matchdict['uuid']
# dept = Session.query(rattail.Department).get(uuid) if uuid else None
# assert dept
# with transaction.manager:
# q = Session.query(rattail.Product)
# q = q.filter(rattail.Product.department_uuid == dept.uuid)
# if q.count():
# q.update({'department_uuid': None}, synchronize_session=False)
# Session.delete(dept)
# return HTTPFound(location=request.route_url('departments.list'))
fmap = filters.get_filter_map(
rattail.Department,
exact=['number'],
ilike=['name'])
config = filters.get_search_config( class DepartmentsGrid(SearchableAlchemyGridView):
'departments.list', request, fmap,
include_filter_name=True,
filter_type_name='lk')
search = filters.get_search_form(config) mapped_class = rattail.Department
route_name = 'departments'
route_url = '/departments'
renderer = '/departments/index.mako'
permission = 'departments.list'
sort = 'name'
config = grids.get_grid_config( def filter_map(self):
'departments.list', request, search, return self.make_filter_map(ilike=['name'])
filter_map=fmap, sort='name', deletable=True)
smap = grids.get_sort_map( def filter_config(self):
rattail.Department, return self.make_filter_config(
['number', 'name']) include_filter_name=True,
filter_type_name='lk')
def query(config): def sort_map(self):
q = Session.query(rattail.Department) return self.make_sort_map('number', 'name')
q = filters.filter_query(q, config)
q = grids.sort_query(q, config, smap) def grid(self):
g = self.make_grid()
g.configure(
include=[
g.number,
g.name,
],
readonly=True)
return g
class DepartmentsByVendorGrid(AlchemyGridView):
mapped_class = rattail.Department
route_name = 'departments.by_vendor'
route_url = '/departments/by-vendor'
checkboxes = True
partial_only = True
def query(self):
q = self.make_query()
q = q.outerjoin(rattail.Product)
q = q.join(rattail.ProductCost)
q = q.join(rattail.Vendor)
q = q.filter(rattail.Vendor.uuid == self.request.params['uuid'])
q = q.distinct()
q = q.order_by(rattail.Department.name)
return q return q
departments = grids.get_pager(query, config) def grid(self):
g = forms.AlchemyGrid( g = self.make_grid()
rattail.Department, departments, config, g.configure(
gridurl=request.route_url('departments.list'), include=[
delurl='department.delete', g.name,
) ],
readonly=True)
g.configure( return g
include=[
g.number,
g.name,
],
readonly=True)
grid = g.render(class_='hoverable departments')
return grids.render_grid(request, grid, search)
@view_config(route_name='department.delete')
def delete_department(context, request):
uuid = request.matchdict['uuid']
dept = Session.query(rattail.Department).get(uuid) if uuid else None
assert dept
with transaction.manager:
q = Session.query(rattail.Product)
q = q.filter(rattail.Product.department_uuid == dept.uuid)
if q.count():
q.update({'department_uuid': None}, synchronize_session=False)
Session.delete(dept)
return HTTPFound(location=request.route_url('departments.list'))
def includeme(config): def includeme(config):
config.add_route('departments.list', '/departments') # config.add_route('department.delete', '/department/{uuid}/delete')
config.add_route('department.delete', '/department/{uuid}/delete') # config.scan(__name__)
config.scan(__name__)
DepartmentsGrid.add_route(config)
DepartmentsByVendorGrid.add_route(config)

View file

@ -26,64 +26,64 @@
``rattail.pyramid.views.employees`` -- Employee Views ``rattail.pyramid.views.employees`` -- Employee Views
""" """
from sqlalchemy import and_
import edbob import edbob
from edbob.pyramid.filters import filter_ilike from edbob.pyramid import grids
from edbob.pyramid.forms import AssociationProxyField from edbob.pyramid.forms import AssociationProxyField
from edbob.pyramid.grids import sorter from edbob.pyramid.views import SearchableAlchemyGridView
from edbob.pyramid.views import GridView
from edbob.pyramid.views.crud import Crud from edbob.pyramid.views.crud import Crud
import rattail import rattail
class EmployeeGrid(GridView): class EmployeesGrid(SearchableAlchemyGridView):
mapped_class = rattail.Employee mapped_class = rattail.Employee
route_name = 'employees.list' route_name = 'employees'
route_prefix = 'employee' route_url = '/employees'
renderer = '/employees/index.mako'
sort = 'first_name'
def filter_map(self): def filter_map(self):
return self.make_filter_map( return self.make_filter_map(
first_name=filter_ilike(edbob.Person.first_name), first_name=grids.search.filter_ilike(edbob.Person.first_name),
last_name=filter_ilike(edbob.Person.last_name)) last_name=grids.search.filter_ilike(edbob.Person.last_name),
phone=grids.search.filter_ilike(edbob.PersonPhone.number))
def search_config(self, fmap): def filter_config(self):
return self.make_search_config( return self.make_filter_config(
fmap,
include_filter_first_name=True, include_filter_first_name=True,
filter_type_first_name='lk', filter_type_first_name='lk',
include_filter_last_name=True, include_filter_last_name=True,
filter_type_last_name='lk') filter_type_last_name='lk',
filter_label_phone="Phone Number")
def grid_config(self, search, fmap):
kwargs = {}
if self.request.has_perm('employees.delete'):
kwargs['deletable'] = True
return self.make_grid_config(
search, fmap,
sort='first_name', **kwargs)
def sort_map(self): def sort_map(self):
return self.make_sort_map( return self.make_sort_map(
first_name=sorter(edbob.Person.first_name), first_name=grids.util.sorter(edbob.Person.first_name),
last_name=sorter(edbob.Person.last_name)) last_name=grids.util.sorter(edbob.Person.last_name),
phone=grids.util.sorter(edbob.PersonPhone.number))
def query(self, config): def query(self):
q = self.make_query(config) q = self.make_query()
q = q.join(edbob.Person) q = q.join(edbob.Person)
q = q.outerjoin(edbob.PersonPhone, and_(
edbob.PersonPhone.parent_uuid == rattail.Employee.person_uuid,
edbob.PersonPhone.preference == 1))
if not self.request.has_perm('employees.edit'): if not self.request.has_perm('employees.edit'):
q = q.filter(rattail.Employee.status == rattail.EMPLOYEE_STATUS_CURRENT) q = q.filter(rattail.Employee.status == rattail.EMPLOYEE_STATUS_CURRENT)
return q return q
def grid(self, data, config): def grid(self):
g = self.make_grid(data, config) g = self.make_grid()
g.append(AssociationProxyField('first_name')) g.append(AssociationProxyField('first_name'))
g.append(AssociationProxyField('last_name')) g.append(AssociationProxyField('last_name'))
g.configure( g.configure(
include=[ include=[
g.first_name, g.first_name,
g.last_name, g.last_name,
# g.status, g.phone.label("Phone Number"),
], ],
readonly=True) readonly=True)
return g return g
@ -104,5 +104,5 @@ class EmployeeCrud(Crud):
def includeme(config): def includeme(config):
EmployeeGrid.add_route(config, 'employees.list', '/employees') EmployeesGrid.add_route(config)
EmployeeCrud.add_routes(config) EmployeeCrud.add_routes(config)

View file

@ -30,24 +30,24 @@ from webhelpers.html.tags import link_to
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from edbob.pyramid.filters import filter_ilike
from edbob.pyramid.grids import sorter
from edbob.pyramid.views import GridView
from edbob.pyramid.views.crud import Crud
import edbob import edbob
from edbob.pyramid import Session from edbob.pyramid import Session
from edbob.pyramid import grids
from edbob.pyramid.views import SearchableAlchemyGridView
from edbob.pyramid.views.crud import Crud
import rattail import rattail
import rattail.labels import rattail.labels
from rattail.pyramid.forms import UpcFieldRenderer, PriceFieldRenderer from rattail.pyramid.forms import UpcFieldRenderer, PriceFieldRenderer
class ProductGrid(GridView): class ProductsGrid(SearchableAlchemyGridView):
mapped_class = rattail.Product mapped_class = rattail.Product
route_name = 'products.list' route_name = 'products'
route_prefix = 'product' route_url = '/products'
renderer = '/products/index.mako'
sort = 'description'
def join_map(self): def join_map(self):
return { return {
@ -71,15 +71,15 @@ class ProductGrid(GridView):
return self.make_filter_map( return self.make_filter_map(
exact=['upc'], exact=['upc'],
ilike=['description', 'size'], ilike=['description', 'size'],
brand=filter_ilike(rattail.Brand.name), brand=grids.search.filter_ilike(rattail.Brand.name),
department=filter_ilike(rattail.Department.name), department=grids.search.filter_ilike(rattail.Department.name),
subdepartment=filter_ilike(rattail.Subdepartment.name)) subdepartment=grids.search.filter_ilike(rattail.Subdepartment.name))
def search_config(self, fmap): def filter_config(self):
return self.make_search_config( return self.make_filter_config(
fmap,
include_filter_upc=True, include_filter_upc=True,
filter_type_upc='eq', filter_type_upc='eq',
filter_label_upc="UPC",
include_filter_brand=True, include_filter_brand=True,
filter_type_brand='lk', filter_type_brand='lk',
include_filter_description=True, include_filter_description=True,
@ -87,37 +87,26 @@ class ProductGrid(GridView):
include_filter_department=True, include_filter_department=True,
filter_type_department='lk') filter_type_department='lk')
def search_form(self, config):
return self.make_search_form(
config, upc="UPC")
def grid_config(self, search, fmap):
kwargs = {}
if self.request.has_perm('products.delete'):
kwargs['deletable'] = True
return self.make_grid_config(
search, fmap, sort='description', **kwargs)
def sort_map(self): def sort_map(self):
return self.make_sort_map( return self.make_sort_map(
'upc', 'description', 'size', 'upc', 'description', 'size',
brand=sorter(rattail.Brand.name), brand=grids.util.sorter(rattail.Brand.name),
department=sorter(rattail.Department.name), department=grids.util.sorter(rattail.Department.name),
subdepartment=sorter(rattail.Subdepartment.name), subdepartment=grids.util.sorter(rattail.Subdepartment.name),
regular_price=sorter(rattail.ProductPrice.price), regular_price=grids.util.sorter(rattail.ProductPrice.price),
current_price=sorter(rattail.ProductPrice.price)) current_price=grids.util.sorter(rattail.ProductPrice.price))
def query(self, config): def query(self):
q = self.make_query(config) q = self.make_query()
q = q.options(joinedload(rattail.Product.brand))
q = q.options(joinedload(rattail.Product.department)) q = q.options(joinedload(rattail.Product.department))
q = q.options(joinedload(rattail.Product.subdepartment)) q = q.options(joinedload(rattail.Product.subdepartment))
q = q.options(joinedload(rattail.Product.brand))
q = q.options(joinedload(rattail.Product.regular_price)) q = q.options(joinedload(rattail.Product.regular_price))
q = q.options(joinedload(rattail.Product.current_price)) q = q.options(joinedload(rattail.Product.current_price))
return q return q
def grid(self, data, config): def grid(self):
g = self.make_grid(data, config) g = self.make_grid()
g.upc.set(renderer=UpcFieldRenderer) g.upc.set(renderer=UpcFieldRenderer)
g.regular_price.set(renderer=PriceFieldRenderer) g.regular_price.set(renderer=PriceFieldRenderer)
g.current_price.set(renderer=PriceFieldRenderer) g.current_price.set(renderer=PriceFieldRenderer)
@ -127,7 +116,6 @@ class ProductGrid(GridView):
g.brand, g.brand,
g.description, g.description,
g.size, g.size,
# g.department,
g.subdepartment, g.subdepartment,
g.regular_price.label("Reg. Price"), g.regular_price.label("Reg. Price"),
g.current_price.label("Cur. Price"), g.current_price.label("Cur. Price"),
@ -175,7 +163,7 @@ def print_label(request):
def includeme(config): def includeme(config):
ProductGrid.add_route(config, 'products.list', '/products') ProductsGrid.add_route(config)
ProductCrud.add_routes(config) ProductCrud.add_routes(config)
config.add_route('products.print_label', '/products/label') config.add_route('products.print_label', '/products/label')

View file

@ -0,0 +1,94 @@
#!/usr/bin/env python
"""
``dtail.views.reports`` -- Report Views
"""
import os
import os.path
import re
from mako.template import Template
from pyramid.response import Response
import edbob
from edbob.pyramid import Session
import rattail
def ordering_report(request):
"""
This is the "Ordering Worksheet" report.
"""
if request.params.get('vendor'):
vendor = Session.query(rattail.Vendor).get(request.params['vendor'])
if vendor:
departments = []
uuids = request.params.get('departments')
if uuids:
for uuid in uuids.split(','):
dept = Session.query(rattail.Department).get(uuid)
if dept:
departments.append(dept)
body = write_ordering_worksheet(vendor, departments)
response = Response(content_type='text/html')
response.headers['Content-Length'] = len(body)
response.headers['Content-Disposition'] = 'attachment; filename=ordering.html'
response.body = body
return response
return {}
def write_ordering_worksheet(vendor, departments):
"""
Rendering engine for the ordering worksheet report.
"""
q = Session.query(rattail.ProductCost)
q = q.join(rattail.Product)
q = q.filter(rattail.ProductCost.vendor == vendor)
q = q.filter(rattail.Product.department_uuid.in_([x.uuid for x in departments]))
costs = {}
for cost in q:
dept = cost.product.department
subdept = cost.product.subdepartment
costs.setdefault(dept, {})
costs[dept].setdefault(subdept, [])
costs[dept][subdept].append(cost)
plu_upc_pattern = re.compile(r'^0000000(\d{5})$')
weighted_upc_pattern = re.compile(r'^02(\d{5})00000$')
def get_upc(prod):
upc = '%012u' % prod.upc
m = plu_upc_pattern.match(upc)
if m:
return str(int(m.group(1)))
m = weighted_upc_pattern.match(upc)
if m:
return str(int(m.group(1)))
return upc
now = edbob.local_time()
data = dict(
vendor=vendor,
costs=costs,
date=now.strftime('%a %d %b %Y'),
time=now.strftime('%I:%M %p'),
get_upc=get_upc,
rattail=rattail,
)
report = os.path.join(os.path.dirname(__file__), os.pardir, 'reports', 'ordering_worksheet.mako')
report = os.path.abspath(report)
template = Template(filename=report, disable_unicode=True)
return template.render(**data)
def includeme(config):
config.add_route('reports.ordering', '/reports/ordering')
config.add_view(ordering_report, route_name='reports.ordering', renderer='/reports/ordering.mako')

View file

@ -26,48 +26,41 @@
``rattail.pyramid.views.vendors`` -- Vendor Views ``rattail.pyramid.views.vendors`` -- Vendor Views
""" """
from edbob.pyramid.views import GridView, AutocompleteView from edbob.pyramid.views import SearchableAlchemyGridView, AutocompleteView
from edbob.pyramid.views.crud import Crud from edbob.pyramid.views.crud import Crud
import rattail import rattail
class VendorGrid(GridView): class VendorsGrid(SearchableAlchemyGridView):
mapped_class = rattail.Vendor mapped_class = rattail.Vendor
route_name = 'vendors.list' route_name = 'vendors'
route_prefix = 'vendor' route_url = '/vendors'
renderer = '/vendors/index.mako'
permission = 'vendors.list'
sort = 'name'
def filter_map(self): def filter_map(self):
return self.make_filter_map( return self.make_filter_map(ilike=['name'])
exact=['id'],
ilike=['name'])
def search_config(self, fmap): def filter_config(self):
return self.make_search_config( return self.make_filter_config(
fmap,
include_filter_name=True, include_filter_name=True,
filter_type_name='lk') filter_type_name='lk',
filter_label_id="ID")
def search_form(self, config):
return self.make_search_form(config, id="ID")
def grid_config(self, search, fmap):
kwargs = {}
if self.request.has_perm('vendors.delete'):
kwargs['deletable'] = True
return self.make_grid_config(
search, fmap, sort='name', **kwargs)
def sort_map(self): def sort_map(self):
return self.make_sort_map('id', 'name') return self.make_sort_map('id', 'name')
def grid(self, data, config): def grid(self):
g = self.make_grid(data, config) g = self.make_grid()
g.configure( g.configure(
include=[ include=[
g.id.label("ID"), g.id.label("ID"),
g.name, g.name,
g.phone,
g.contact,
], ],
readonly=True) readonly=True)
return g return g
@ -95,6 +88,6 @@ class VendorAutocomplete(AutocompleteView):
def includeme(config): def includeme(config):
VendorGrid.add_route(config, 'vendors.list', '/vendors') VendorsGrid.add_route(config)
VendorCrud.add_routes(config) VendorCrud.add_routes(config)
VendorAutocomplete.add_route(config) VendorAutocomplete.add_route(config)