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
"""
from webhelpers.html import literal
import formalchemy
# from formalchemy.fields import SelectFieldRenderer
@ -70,11 +72,11 @@ class PriceFieldRenderer(formalchemy.FieldRenderer):
if price:
if price.price is not None and price.pack_price is not None:
if price.multiple > 1:
return '$ %0.2f / %u  ($ %0.2f / %u)' % (
return literal('$ %0.2f / %u  ($ %0.2f / %u)' % (
price.price, price.multiple,
price.pack_price, price.pack_multiple)
return '$ %0.2f  ($ %0.2f / %u)' % (
price.price, price.pack_price, price.pack_multiple)
price.pack_price, price.pack_multiple))
return literal('$ %0.2f  ($ %0.2f / %u)' % (
price.price, price.pack_price, price.pack_multiple))
if price.price is not None:
if price.multiple > 1:
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">
$(function() {
$('div.grid.Product a.print-label').live('click', function() {
$('div.grid a.print-label').live('click', function() {
var quantity = $('#label-quantity').val();
if (isNaN(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
"""
import transaction
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
# import transaction
# from pyramid.httpexceptions import HTTPFound
# from pyramid.view import view_config
from edbob.pyramid import filters
from edbob.pyramid import forms
from edbob.pyramid import grids
from edbob.pyramid import Session
# from edbob.pyramid import Session
from edbob.pyramid.views import SearchableAlchemyGridView, AlchemyGridView
import rattail
@view_config(route_name='departments.list', renderer='/departments/index.mako')
def list_departments(context, request):
# @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'))
fmap = filters.get_filter_map(
rattail.Department,
exact=['number'],
ilike=['name'])
config = filters.get_search_config(
'departments.list', request, fmap,
class DepartmentsGrid(SearchableAlchemyGridView):
mapped_class = rattail.Department
route_name = 'departments'
route_url = '/departments'
renderer = '/departments/index.mako'
permission = 'departments.list'
sort = 'name'
def filter_map(self):
return self.make_filter_map(ilike=['name'])
def filter_config(self):
return self.make_filter_config(
include_filter_name=True,
filter_type_name='lk')
search = filters.get_search_form(config)
config = grids.get_grid_config(
'departments.list', request, search,
filter_map=fmap, sort='name', deletable=True)
smap = grids.get_sort_map(
rattail.Department,
['number', 'name'])
def query(config):
q = Session.query(rattail.Department)
q = filters.filter_query(q, config)
q = grids.sort_query(q, config, smap)
return q
departments = grids.get_pager(query, config)
g = forms.AlchemyGrid(
rattail.Department, departments, config,
gridurl=request.route_url('departments.list'),
delurl='department.delete',
)
def sort_map(self):
return self.make_sort_map('number', 'name')
def grid(self):
g = self.make_grid()
g.configure(
include=[
g.number,
g.name,
],
readonly=True)
grid = g.render(class_='hoverable departments')
return grids.render_grid(request, grid, search)
return g
@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'))
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
def grid(self):
g = self.make_grid()
g.configure(
include=[
g.name,
],
readonly=True)
return g
def includeme(config):
config.add_route('departments.list', '/departments')
config.add_route('department.delete', '/department/{uuid}/delete')
config.scan(__name__)
# config.add_route('department.delete', '/department/{uuid}/delete')
# config.scan(__name__)
DepartmentsGrid.add_route(config)
DepartmentsByVendorGrid.add_route(config)

View file

@ -26,64 +26,64 @@
``rattail.pyramid.views.employees`` -- Employee Views
"""
from sqlalchemy import and_
import edbob
from edbob.pyramid.filters import filter_ilike
from edbob.pyramid import grids
from edbob.pyramid.forms import AssociationProxyField
from edbob.pyramid.grids import sorter
from edbob.pyramid.views import GridView
from edbob.pyramid.views import SearchableAlchemyGridView
from edbob.pyramid.views.crud import Crud
import rattail
class EmployeeGrid(GridView):
class EmployeesGrid(SearchableAlchemyGridView):
mapped_class = rattail.Employee
route_name = 'employees.list'
route_prefix = 'employee'
route_name = 'employees'
route_url = '/employees'
renderer = '/employees/index.mako'
sort = 'first_name'
def filter_map(self):
return self.make_filter_map(
first_name=filter_ilike(edbob.Person.first_name),
last_name=filter_ilike(edbob.Person.last_name))
first_name=grids.search.filter_ilike(edbob.Person.first_name),
last_name=grids.search.filter_ilike(edbob.Person.last_name),
phone=grids.search.filter_ilike(edbob.PersonPhone.number))
def search_config(self, fmap):
return self.make_search_config(
fmap,
def filter_config(self):
return self.make_filter_config(
include_filter_first_name=True,
filter_type_first_name='lk',
include_filter_last_name=True,
filter_type_last_name='lk')
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)
filter_type_last_name='lk',
filter_label_phone="Phone Number")
def sort_map(self):
return self.make_sort_map(
first_name=sorter(edbob.Person.first_name),
last_name=sorter(edbob.Person.last_name))
first_name=grids.util.sorter(edbob.Person.first_name),
last_name=grids.util.sorter(edbob.Person.last_name),
phone=grids.util.sorter(edbob.PersonPhone.number))
def query(self, config):
q = self.make_query(config)
def query(self):
q = self.make_query()
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'):
q = q.filter(rattail.Employee.status == rattail.EMPLOYEE_STATUS_CURRENT)
return q
def grid(self, data, config):
g = self.make_grid(data, config)
def grid(self):
g = self.make_grid()
g.append(AssociationProxyField('first_name'))
g.append(AssociationProxyField('last_name'))
g.configure(
include=[
g.first_name,
g.last_name,
# g.status,
g.phone.label("Phone Number"),
],
readonly=True)
return g
@ -104,5 +104,5 @@ class EmployeeCrud(Crud):
def includeme(config):
EmployeeGrid.add_route(config, 'employees.list', '/employees')
EmployeesGrid.add_route(config)
EmployeeCrud.add_routes(config)

View file

@ -30,24 +30,24 @@ from webhelpers.html.tags import link_to
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
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.labels
from rattail.pyramid.forms import UpcFieldRenderer, PriceFieldRenderer
class ProductGrid(GridView):
class ProductsGrid(SearchableAlchemyGridView):
mapped_class = rattail.Product
route_name = 'products.list'
route_prefix = 'product'
route_name = 'products'
route_url = '/products'
renderer = '/products/index.mako'
sort = 'description'
def join_map(self):
return {
@ -71,15 +71,15 @@ class ProductGrid(GridView):
return self.make_filter_map(
exact=['upc'],
ilike=['description', 'size'],
brand=filter_ilike(rattail.Brand.name),
department=filter_ilike(rattail.Department.name),
subdepartment=filter_ilike(rattail.Subdepartment.name))
brand=grids.search.filter_ilike(rattail.Brand.name),
department=grids.search.filter_ilike(rattail.Department.name),
subdepartment=grids.search.filter_ilike(rattail.Subdepartment.name))
def search_config(self, fmap):
return self.make_search_config(
fmap,
def filter_config(self):
return self.make_filter_config(
include_filter_upc=True,
filter_type_upc='eq',
filter_label_upc="UPC",
include_filter_brand=True,
filter_type_brand='lk',
include_filter_description=True,
@ -87,37 +87,26 @@ class ProductGrid(GridView):
include_filter_department=True,
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):
return self.make_sort_map(
'upc', 'description', 'size',
brand=sorter(rattail.Brand.name),
department=sorter(rattail.Department.name),
subdepartment=sorter(rattail.Subdepartment.name),
regular_price=sorter(rattail.ProductPrice.price),
current_price=sorter(rattail.ProductPrice.price))
brand=grids.util.sorter(rattail.Brand.name),
department=grids.util.sorter(rattail.Department.name),
subdepartment=grids.util.sorter(rattail.Subdepartment.name),
regular_price=grids.util.sorter(rattail.ProductPrice.price),
current_price=grids.util.sorter(rattail.ProductPrice.price))
def query(self, config):
q = self.make_query(config)
def query(self):
q = self.make_query()
q = q.options(joinedload(rattail.Product.brand))
q = q.options(joinedload(rattail.Product.department))
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.current_price))
return q
def grid(self, data, config):
g = self.make_grid(data, config)
def grid(self):
g = self.make_grid()
g.upc.set(renderer=UpcFieldRenderer)
g.regular_price.set(renderer=PriceFieldRenderer)
g.current_price.set(renderer=PriceFieldRenderer)
@ -127,7 +116,6 @@ class ProductGrid(GridView):
g.brand,
g.description,
g.size,
# g.department,
g.subdepartment,
g.regular_price.label("Reg. Price"),
g.current_price.label("Cur. Price"),
@ -175,7 +163,7 @@ def print_label(request):
def includeme(config):
ProductGrid.add_route(config, 'products.list', '/products')
ProductsGrid.add_route(config)
ProductCrud.add_routes(config)
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
"""
from edbob.pyramid.views import GridView, AutocompleteView
from edbob.pyramid.views import SearchableAlchemyGridView, AutocompleteView
from edbob.pyramid.views.crud import Crud
import rattail
class VendorGrid(GridView):
class VendorsGrid(SearchableAlchemyGridView):
mapped_class = rattail.Vendor
route_name = 'vendors.list'
route_prefix = 'vendor'
route_name = 'vendors'
route_url = '/vendors'
renderer = '/vendors/index.mako'
permission = 'vendors.list'
sort = 'name'
def filter_map(self):
return self.make_filter_map(
exact=['id'],
ilike=['name'])
return self.make_filter_map(ilike=['name'])
def search_config(self, fmap):
return self.make_search_config(
fmap,
def filter_config(self):
return self.make_filter_config(
include_filter_name=True,
filter_type_name='lk')
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)
filter_type_name='lk',
filter_label_id="ID")
def sort_map(self):
return self.make_sort_map('id', 'name')
def grid(self, data, config):
g = self.make_grid(data, config)
def grid(self):
g = self.make_grid()
g.configure(
include=[
g.id.label("ID"),
g.name,
g.phone,
g.contact,
],
readonly=True)
return g
@ -95,6 +88,6 @@ class VendorAutocomplete(AutocompleteView):
def includeme(config):
VendorGrid.add_route(config, 'vendors.list', '/vendors')
VendorsGrid.add_route(config)
VendorCrud.add_routes(config)
VendorAutocomplete.add_route(config)