Add initial support for viewing new purchase batch as Order Form

This commit is contained in:
Lance Edgar 2016-11-07 20:40:47 -06:00
parent 0477561ca6
commit 758ae7099d
4 changed files with 365 additions and 14 deletions

View file

@ -36,23 +36,35 @@
</%def> </%def>
<%def name="buttons()"> <%def name="buttons()">
## TODO: the refreshable thing still seems confusing...
<div class="buttons"> <div class="buttons">
% if master.refreshable: ${self.leading_buttons()}
% if form.readonly: ${refresh_button()}
% if not batch.executed: ${execute_button()}
<button type="button" id="refresh-data">Refresh Data</button>
% endif
% elif batch.refreshable:
${h.submit('save-refresh', "Save & Refresh Data")}
% endif
% endif
% if not batch.executed and request.has_perm('{}.execute'.format(permission_prefix)):
<button type="button" id="execute-batch"${'' if execute_enabled else ' disabled="disabled"'}>${execute_title}</button>
% endif
</div> </div>
</%def> </%def>
<%def name="leading_buttons()">
</%def>
<%def name="refresh_button()">
## TODO: the refreshable thing still seems confusing...
% if master.refreshable:
% if form.readonly:
% if not batch.executed:
<button type="button" id="refresh-data">Refresh Data</button>
% endif
% elif batch.refreshable:
${h.submit('save-refresh', "Save & Refresh Data")}
% endif
% endif
</%def>
<%def name="execute_button()">
% if not batch.executed and request.has_perm('{}.execute'.format(permission_prefix)):
<button type="button" id="execute-batch"${'' if execute_enabled else ' disabled="disabled"'}>${execute_title}</button>
% endif
</%def>
<ul id="context-menu"> <ul id="context-menu">
${self.context_menu_items()} ${self.context_menu_items()}
</ul> </ul>

View file

@ -0,0 +1,161 @@
## -*- coding: utf-8 -*-
<%inherit file="/base.mako" />
<%def name="title()">Purchase Order Form</%def>
<%def name="head_tags()">
${parent.head_tags()}
${h.javascript_link(request.static_url('tailbone:static/js/numeric.js'))}
<script type="text/javascript">
$(function() {
$('.order-form td.current-order input').keydown(function(event) {
if (key_allowed(event) || key_modifies(event)) {
return true;
}
if (event.which == 13) {
var input = $(this);
var row = input.parents('tr:first');
var data = {
product_uuid: row.data('uuid'),
cases_ordered: input.val() || '0'
};
$.post('${url('purchases.batch.order_form_update', uuid=batch.uuid)}', data, function(data) {
if (data.error) {
alert(data.error);
} else {
input.val(data.row_cases_ordered);
row.find('td:last').html(data.row_po_total);
$('.po-total .field').html(data.batch_po_total);
}
});
}
return false;
});
});
</script>
<style type="text/css">
.order-form th.department {
border-top: 1px solid black;
font-size: 1.2em;
padding: 15px;
text-align: left;
text-transform: uppercase;
}
.order-form th.subdepartment {
background-color: white;
border-bottom: 1px solid black;
border-top: 1px solid black;
padding: 15px;
text-align: left;
}
.order-form td {
border-right: 1px solid #000000;
border-top: 1px solid #000000;
}
.order-form td.upc,
.order-form td.case-qty,
.order-form td.code,
.order-form td.preferred {
text-align: center;
}
.order-form td.scratch_pad {
width: 40px;
}
.order-form td.current-order input {
text-align: center;
width: 3em;
}
.order-form td.po-total {
text-align: right;
}
</style>
</%def>
<%def name="context_menu_items()">
<li>${h.link_to("Back to Purchase Batch", url('purchases.batch.view', uuid=batch.uuid))}</li>
</%def>
<ul id="context-menu">
${self.context_menu_items()}
</ul>
<div class="form-wrapper">
<div class="field-wrapper">
<label>Vendor</label>
<div class="field">${vendor}</div>
</div>
<div class="field-wrapper">
<label>Order Date</label>
<div class="field">${batch.date_ordered}</div>
</div>
<div class="field-wrapper po-total">
<label>PO Total</label>
<div class="field">$${'{:0,.2f}'.format(batch.po_total or 0)}</div>
</div>
</div><!-- form-wrapper -->
<div class="newgrid">
<table class="order-form">
% for department in sorted(departments.itervalues(), key=lambda d: d.name if d else ''):
<thead>
<tr>
<th class="department" colspan="22">Department:&nbsp; ${department.number} ${department.name}</th>
</tr>
% for subdepartment in sorted(department._order_subdepartments.itervalues(), key=lambda s: s.name if s else ''):
<tr>
<th class="subdepartment" colspan="22">Subdepartment:&nbsp; ${subdepartment.number} ${subdepartment.name}</th>
</tr>
<tr>
<th>UPC</th>
<th>Brand</th>
<th>Description</th>
<th>Size</th>
<th>Case</th>
<th>Vend. Code</th>
<th>Pref.</th>
<th colspan="7">&nbsp;</th>
<th>PO Total</th>
</tr>
</thead>
<tbody>
% for cost in subdepartment._order_costs:
<tr data-uuid="${cost.product_uuid}">
<td class="upc">${get_upc(cost.product)}</td>
<td class="brand">${cost.product.brand or ''}</td>
<td class="desc">${cost.product.description}</td>
<td class="size">${cost.product.size or ''}</td>
<td class="case-qty">${cost.case_size} ${"LB" if cost.product.weighed else "EA"}</td>
<td class="code">${cost.code or ''}</td>
<td class="preferred">${'X' if cost.preference == 1 else ''}</td>
% for i in range(6):
<td class="scratch_pad">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
% endfor
<td class="current-order">
${h.text('cases_ordered_{}'.format(cost.uuid), value=int(cost._batchrow.cases_ordered) if cost._batchrow else None)}
</td>
<td class="po-total">${'${:0,.2f}'.format(cost._batchrow.po_total) if cost._batchrow else ''}</td>
</tr>
% endfor
</tbody>
% endfor
% endfor
</table>
</div>

View file

@ -0,0 +1,29 @@
## -*- coding: utf-8 -*-
<%inherit file="/newbatch/view.mako" />
<%def name="head_tags()">
${parent.head_tags()}
<script type="text/javascript">
$(function() {
$('#order-form').click(function() {
% if vendor_cost_count > 199:
if (! confirm("This vendor has ${'{:,d}'.format(vendor_cost_count)} cost records.\n\n" +
"It is not recommended to use Order Form mode for such a large catalog.\n\n" +
"Are you sure you wish to do it anyway?")) {
return;
}
% endif
$(this).button('disable').button('option', 'label', "Working, please wait...");
location.href = '${url('purchases.batch.order_form', uuid=instance.uuid)}';
});
});
</script>
</%def>
<%def name="leading_buttons()">
% if not instance.executed and request.has_perm('purchases.batch.order_form'):
<button type="button" id="order-form">View as Order Form</button>
% endif
</%def>
${parent.body()}

View file

@ -30,6 +30,7 @@ from rattail.db import model, api
from rattail.gpc import GPC from rattail.gpc import GPC
from rattail.db.batch.purchase.handler import PurchaseBatchHandler from rattail.db.batch.purchase.handler import PurchaseBatchHandler
from rattail.time import localtime from rattail.time import localtime
from rattail.core import Object
import formalchemy as fa import formalchemy as fa
@ -118,6 +119,14 @@ class PurchaseBatchView(BatchMasterView):
# default order date is today # default order date is today
fs.model.date_ordered = localtime(self.rattail_config).date() fs.model.date_ordered = localtime(self.rattail_config).date()
def template_kwargs_view(self, **kwargs):
kwargs = super(PurchaseBatchView, self).template_kwargs_view(**kwargs)
vendor = kwargs['batch'].vendor
kwargs['vendor_cost_count'] = Session.query(model.ProductCost)\
.filter(model.ProductCost.vendor == vendor)\
.count()
return kwargs
def _preconfigure_row_grid(self, g): def _preconfigure_row_grid(self, g):
super(PurchaseBatchView, self)._preconfigure_row_grid(g) super(PurchaseBatchView, self)._preconfigure_row_grid(g)
@ -222,11 +231,151 @@ class PurchaseBatchView(BatchMasterView):
self.request.session.flash("Added item: {} {}".format(row.upc.pretty(), row.product)) self.request.session.flash("Added item: {} {}".format(row.upc.pretty(), row.product))
return self.redirect(self.request.current_route_url()) return self.redirect(self.request.current_route_url())
def delete_row(self):
"""
Update the PO total in addition to marking row as removed.
"""
row = self.Session.query(self.model_row_class).get(self.request.matchdict['uuid'])
if not row:
raise httpexceptions.HTTPNotFound()
if row.po_total:
row.batch.po_total -= row.po_total
row.removed = True
return self.redirect(self.get_action_url('view', row.batch))
# TODO: redirect to new purchase... # TODO: redirect to new purchase...
# def get_execute_success_url(self, batch, result, **kwargs): # def get_execute_success_url(self, batch, result, **kwargs):
# # return self.get_action_url('view', batch) # # return self.get_action_url('view', batch)
# return # return
def order_form(self):
"""
View for editing a purchase batch as an order form.
"""
batch = self.get_instance()
vendor = batch.vendor
costs = Session.query(model.ProductCost)\
.join(model.Product)\
.outerjoin(model.Brand)\
.filter(model.ProductCost.vendor == vendor)\
.order_by(model.Brand.name,
model.Product.description,
model.Product.size)
# organize existing batch rows by product
order_items = {}
for row in batch.data_rows:
if not row.removed:
order_items[row.product_uuid] = row
# organize product costs by dept / subdept
departments = {}
for cost in costs:
department = cost.product.department
if department:
departments.setdefault(department.uuid, department)
else:
if None not in departments:
department = Object()
departments[None] = department
department = departments[None]
subdepartments = getattr(department, '_order_subdepartments', None)
if subdepartments is None:
subdepartments = department._order_subdepartments = {}
subdepartment = cost.product.subdepartment
if subdepartment:
subdepartments.setdefault(subdepartment.uuid, subdepartment)
else:
if None not in subdepartments:
subdepartment = Object()
subdepartments[None] = subdepartment
subdepartment = subdepartments[None]
subdept_costs = getattr(subdepartment, '_order_costs', None)
if subdept_costs is None:
subdept_costs = subdepartment._order_costs = []
subdept_costs.append(cost)
cost._batchrow = order_items.get(cost.product_uuid)
title = self.get_instance_title(batch)
return self.render_to_response('order_form', {
'batch': batch,
'instance': batch,
'instance_title': title,
'index_title': "{}: {}".format(self.get_model_title(), title),
'index_url': self.get_action_url('view', batch),
'vendor': vendor,
'departments': departments,
'get_upc': lambda p: p.upc,
})
def order_form_update(self):
"""
Handles AJAX requests to update current batch, from Order Form view.
"""
batch = self.get_instance()
quantity = self.request.POST.get('cases_ordered')
if not quantity or not quantity.isdigit():
return {'error': "Invalid quantity: {}".format(quantity)}
quantity = int(quantity)
uuid = self.request.POST.get('product_uuid')
product = Session.query(model.Product).get(uuid) if uuid else None
if not product:
return {'error': "Product not found"}
rows = [row for row in batch.data_rows if row.product_uuid == uuid]
if rows:
assert len(rows) == 1
row = rows[0]
if row.po_total and not row.removed:
batch.po_total -= row.po_total
if quantity:
row.cases_ordered = quantity
row.removed = False
self.handler.refresh_row(row)
else:
row.removed = True
elif quantity:
row = model.PurchaseBatchRow()
row.sequence = max([0] + [r.sequence for r in batch.data_rows]) + 1
row.product = product
batch.data_rows.append(row)
row.cases_ordered = quantity
self.handler.refresh_row(row)
return {
'row_cases_ordered': '' if row.removed else int(row.cases_ordered),
'row_po_total': '' if row.removed else '${:0,.2f}'.format(row.po_total),
'batch_po_total': '${:0,.2f}'.format(batch.po_total),
}
@classmethod
def defaults(cls, config):
route_prefix = cls.get_route_prefix()
url_prefix = cls.get_url_prefix()
permission_prefix = cls.get_permission_prefix()
model_key = cls.get_model_key()
model_title = cls.get_model_title()
cls._batch_defaults(config)
cls._defaults(config)
# order form
config.add_tailbone_permission(permission_prefix, '{}.order_form'.format(permission_prefix),
"Edit new {} in Order Form mode".format(model_title))
config.add_route('{}.order_form'.format(route_prefix), '{}/{{{}}}/order-form'.format(url_prefix, model_key))
config.add_view(cls, attr='order_form', route_name='{}.order_form'.format(route_prefix),
permission='{}.order_form'.format(permission_prefix))
config.add_route('{}.order_form_update'.format(route_prefix), '{}/{{{}}}/order-form/update'.format(url_prefix, model_key))
config.add_view(cls, attr='order_form_update', route_name='{}.order_form_update'.format(route_prefix),
renderer='json', permission='{}.order_form'.format(permission_prefix))
def includeme(config): def includeme(config):
PurchaseBatchView.defaults(config) PurchaseBatchView.defaults(config)