diff --git a/tailbone/templates/newbatch/view.mako b/tailbone/templates/newbatch/view.mako index 0f0cfc0a..23e57b6b 100644 --- a/tailbone/templates/newbatch/view.mako +++ b/tailbone/templates/newbatch/view.mako @@ -36,23 +36,35 @@ <%def name="buttons()"> -## TODO: the refreshable thing still seems confusing...
- % if master.refreshable: - % if form.readonly: - % if not batch.executed: - - % 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)): - - % endif + ${self.leading_buttons()} + ${refresh_button()} + ${execute_button()}
+<%def name="leading_buttons()"> + + +<%def name="refresh_button()"> +## TODO: the refreshable thing still seems confusing... + % if master.refreshable: + % if form.readonly: + % if not batch.executed: + + % endif + % elif batch.refreshable: + ${h.submit('save-refresh', "Save & Refresh Data")} + % endif + % endif + + +<%def name="execute_button()"> + % if not batch.executed and request.has_perm('{}.execute'.format(permission_prefix)): + + % endif + + diff --git a/tailbone/templates/purchases/batches/order_form.mako b/tailbone/templates/purchases/batches/order_form.mako new file mode 100644 index 00000000..7b94bfc4 --- /dev/null +++ b/tailbone/templates/purchases/batches/order_form.mako @@ -0,0 +1,161 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base.mako" /> + +<%def name="title()">Purchase Order Form + +<%def name="head_tags()"> + ${parent.head_tags()} + ${h.javascript_link(request.static_url('tailbone:static/js/numeric.js'))} + + + + + +<%def name="context_menu_items()"> +
  • ${h.link_to("Back to Purchase Batch", url('purchases.batch.view', uuid=batch.uuid))}
  • + + + + + + +
    + +
    + +
    ${vendor}
    +
    + +
    + +
    ${batch.date_ordered}
    +
    + +
    + +
    $${'{:0,.2f}'.format(batch.po_total or 0)}
    +
    + +
    + + +
    + + % for department in sorted(departments.itervalues(), key=lambda d: d.name if d else ''): + + + + + % for subdepartment in sorted(department._order_subdepartments.itervalues(), key=lambda s: s.name if s else ''): + + + + + + + + + + + + + + + + + % for cost in subdepartment._order_costs: + + + + + + + + + % for i in range(6): + + % endfor + + + + % endfor + + % endfor + % endfor +
    Department:  ${department.number} ${department.name}
    Subdepartment:  ${subdepartment.number} ${subdepartment.name}
    UPCBrandDescriptionSizeCaseVend. CodePref. PO Total
    ${get_upc(cost.product)}${cost.product.brand or ''}${cost.product.description}${cost.product.size or ''}${cost.case_size} ${"LB" if cost.product.weighed else "EA"}${cost.code or ''}${'X' if cost.preference == 1 else ''}         + ${h.text('cases_ordered_{}'.format(cost.uuid), value=int(cost._batchrow.cases_ordered) if cost._batchrow else None)} + ${'${:0,.2f}'.format(cost._batchrow.po_total) if cost._batchrow else ''}
    +
    diff --git a/tailbone/templates/purchases/batches/view.mako b/tailbone/templates/purchases/batches/view.mako new file mode 100644 index 00000000..f877163d --- /dev/null +++ b/tailbone/templates/purchases/batches/view.mako @@ -0,0 +1,29 @@ +## -*- coding: utf-8 -*- +<%inherit file="/newbatch/view.mako" /> + +<%def name="head_tags()"> + ${parent.head_tags()} + + + +<%def name="leading_buttons()"> + % if not instance.executed and request.has_perm('purchases.batch.order_form'): + + % endif + + +${parent.body()} diff --git a/tailbone/views/purchases/batch.py b/tailbone/views/purchases/batch.py index 19572ca3..6282d519 100644 --- a/tailbone/views/purchases/batch.py +++ b/tailbone/views/purchases/batch.py @@ -30,6 +30,7 @@ from rattail.db import model, api from rattail.gpc import GPC from rattail.db.batch.purchase.handler import PurchaseBatchHandler from rattail.time import localtime +from rattail.core import Object import formalchemy as fa @@ -118,6 +119,14 @@ class PurchaseBatchView(BatchMasterView): # default order date is today 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): super(PurchaseBatchView, self)._preconfigure_row_grid(g) @@ -222,10 +231,150 @@ class PurchaseBatchView(BatchMasterView): self.request.session.flash("Added item: {} {}".format(row.upc.pretty(), row.product)) 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... # def get_execute_success_url(self, batch, result, **kwargs): # # 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):