Refactor purchasing batch views per master4

This commit is contained in:
Lance Edgar 2018-02-05 12:59:34 -06:00
parent dfc5e0f50e
commit 8137d715df
6 changed files with 328 additions and 186 deletions

View file

@ -1,6 +1,6 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<%inherit file="/mobile/master/create_row.mako" /> <%inherit file="/mobile/master/create_row.mako" />
<%def name="title()">${h.link_to(index_title, index_url)} &raquo; ${h.link_to(instance_title, instance_url)} &raquo; Add Item</%def> <%def name="page_title()">${h.link_to(index_title, index_url)} &raquo; ${h.link_to(instance_title, instance_url)} &raquo; Add Item</%def>
${parent.body()} ${parent.body()}

View file

@ -288,5 +288,9 @@ ${h.end_form()}
<td class="case-qty">${h.pretty_quantity(cost.case_size)} ${"LB" if cost.product.weighed else "EA"}</td> <td class="case-qty">${h.pretty_quantity(cost.case_size)} ${"LB" if cost.product.weighed else "EA"}</td>
<td class="code">${cost.code or ''}</td> <td class="code">${cost.code or ''}</td>
<td class="preferred">${'X' if cost.preference == 1 else ''}</td> <td class="preferred">${'X' if cost.preference == 1 else ''}</td>
<td class="unit-cost">$${'{:0.2f}'.format(cost.unit_cost)}</td> <td class="unit-cost">
% if cost.unit_cost is not None:
$${'{:0.2f}'.format(cost.unit_cost)}
% endif
</td>
</%def> </%def>

View file

@ -1,4 +1,4 @@
## -*- coding: utf-8 -*- ## -*- coding: utf-8; -*-
<html> <html>
<head> <head>
<title>Receiving Worksheet</title> <title>Receiving Worksheet</title>
@ -76,7 +76,7 @@
<tbody> <tbody>
% for item in purchase.items: % for item in purchase.items:
<tr> <tr>
<td>${item.upc.pretty()}</td> <td>${item.upc.pretty() if item.upc else item.item_id}</td>
<td>${item.vendor_code or ''}</td> <td>${item.vendor_code or ''}</td>
<td>${(item.brand_name or '')[:15]}</td> <td>${(item.brand_name or '')[:15]}</td>
<td>${item.description or ''}</td> <td>${item.description or ''}</td>

View file

@ -36,8 +36,9 @@ from deform import widget as dfwidget
from pyramid import httpexceptions from pyramid import httpexceptions
from webhelpers2.html import tags from webhelpers2.html import tags
from tailbone import forms, forms2 # from tailbone import forms
from tailbone.views.batch import BatchMasterView3 as BatchMasterView from tailbone import forms2
from tailbone.views.batch import BatchMasterView4 as BatchMasterView
class PurchasingBatchView(BatchMasterView): class PurchasingBatchView(BatchMasterView):
@ -56,27 +57,11 @@ class PurchasingBatchView(BatchMasterView):
'date_ordered', 'date_ordered',
'created', 'created',
'created_by', 'created_by',
'rowcount',
'status_code', 'status_code',
'executed', 'executed',
] ]
# row_grid_columns = [
# 'sequence',
# 'upc',
# # 'item_id',
# 'brand_name',
# 'description',
# 'size',
# 'cases_ordered',
# 'units_ordered',
# 'cases_received',
# 'units_received',
# 'po_total',
# 'invoice_total',
# 'credits',
# 'status_code',
# ]
form_fields = [ form_fields = [
'id', 'id',
'store', 'store',
@ -104,6 +89,88 @@ class PurchasingBatchView(BatchMasterView):
'executed_by', 'executed_by',
] ]
row_labels = {
'upc': "UPC",
'item_id': "Item ID",
'brand_name': "Brand",
'po_line_number': "PO Line Number",
'po_unit_cost': "PO Unit Cost",
'po_total': "PO Total",
}
# row_grid_columns = [
# 'sequence',
# 'upc',
# # 'item_id',
# 'brand_name',
# 'description',
# 'size',
# 'cases_ordered',
# 'units_ordered',
# 'cases_received',
# 'units_received',
# 'po_total',
# 'invoice_total',
# 'credits',
# 'status_code',
# ]
row_form_fields = [
'upc',
'item_id',
'product',
'brand_name',
'description',
'size',
'case_quantity',
'cases_ordered',
'units_ordered',
'cases_received',
'units_received',
'cases_damaged',
'units_damaged',
'cases_expired',
'units_expired',
'cases_mispick',
'units_mispick',
'po_line_number',
'po_unit_cost',
'po_total',
'invoice_line_number',
'invoice_unit_cost',
'invoice_total',
'status_code',
'credits',
]
mobile_row_form_fields = [
'upc',
'item_id',
'product',
'brand_name',
'description',
'size',
'case_quantity',
'cases_ordered',
'units_ordered',
'cases_received',
'units_received',
'cases_damaged',
'units_damaged',
'cases_expired',
'units_expired',
'cases_mispick',
'units_mispick',
# 'po_line_number',
'po_unit_cost',
'po_total',
# 'invoice_line_number',
'invoice_unit_cost',
'invoice_total',
'status_code',
# 'credits',
]
@property @property
def batch_mode(self): def batch_mode(self):
raise NotImplementedError("Please define `batch_mode` for your purchasing batch view") raise NotImplementedError("Please define `batch_mode` for your purchasing batch view")
@ -156,7 +223,11 @@ class PurchasingBatchView(BatchMasterView):
# TODO: this hardly seems complete... # TODO: this hardly seems complete...
# store # store
if not self.creating: if self.creating:
f.replace('store', 'store_uuid')
f.set_widget('store_uuid', dfwidget.SelectWidget(values=self.get_store_values()))
f.set_label('store_uuid', "Store")
else:
f.set_readonly('store') f.set_readonly('store')
f.set_renderer('store', self.render_store) f.set_renderer('store', self.render_store)
@ -189,12 +260,13 @@ class PurchasingBatchView(BatchMasterView):
# department # department
f.set_renderer('department', self.render_department) f.set_renderer('department', self.render_department)
if self.creating: if self.creating:
f.replace('department', 'department_uuid') if 'department' in f.fields:
f.set_node('department_uuid', colander.String()) f.replace('department', 'department_uuid')
dept_options = self.get_department_options() f.set_node('department_uuid', colander.String())
dept_values = [(v, k) for k, v in dept_options] dept_options = self.get_department_options()
f.set_widget('department_uuid', dfwidget.SelectWidget(values=dept_values)) dept_values = [(v, k) for k, v in dept_options]
f.set_label('department_uuid', "Department") f.set_widget('department_uuid', dfwidget.SelectWidget(values=dept_values))
f.set_label('department_uuid', "Department")
else: else:
f.set_readonly('department') f.set_readonly('department')
@ -271,6 +343,12 @@ class PurchasingBatchView(BatchMasterView):
'vendor_contact', 'vendor_contact',
'status_code') 'status_code')
def configure_mobile_form(self, f):
super(PurchasingBatchView, self).configure_mobile_form(f)
# currency fields
f.set_type('po_total', 'currency')
def render_store(self, batch, field): def render_store(self, batch, field):
store = batch.store store = batch.store
if not store: if not store:
@ -330,6 +408,37 @@ class PurchasingBatchView(BatchMasterView):
return tags.link_to(text, url) return tags.link_to(text, url)
return text return text
def get_store_values(self):
stores = self.Session.query(model.Store)\
.order_by(model.Store.id)
return [(s.uuid, "({}) {}".format(s.id, s.name))
for s in stores]
def get_vendors(self):
return self.Session.query(model.Vendor)\
.order_by(model.Vendor.name)
def get_vendor_values(self):
vendors = self.get_vendors()
return [(v.uuid, "({}) {}".format(v.id, v.name))
for v in vendors]
def get_vendor_values(self):
vendors = self.get_vendors()
return [(v.uuid, "({}) {}".format(v.id, v.name))
for v in vendors]
def get_buyers(self):
return self.Session.query(model.Employee)\
.join(model.Person)\
.filter(model.Employee.status == self.enum.EMPLOYEE_STATUS_CURRENT)\
.order_by(model.Person.display_name)
def get_buyer_values(self):
buyers = self.get_buyers()
return [(b.uuid, six.text_type(b))
for b in buyers]
def get_department_options(self): def get_department_options(self):
departments = self.Session.query(model.Department).order_by(model.Department.number) departments = self.Session.query(model.Department).order_by(model.Department.number)
return [('{} {}'.format(d.number, d.name), d.uuid) for d in departments] return [('{} {}'.format(d.number, d.name), d.uuid) for d in departments]
@ -456,8 +565,6 @@ class PurchasingBatchView(BatchMasterView):
g.set_type('invoice_total', 'currency') g.set_type('invoice_total', 'currency')
g.set_type('credits', 'boolean') g.set_type('credits', 'boolean')
g.set_label('upc', "UPC")
g.set_label('brand_name', "Brand")
g.set_label('cases_ordered', "Cases Ord.") g.set_label('cases_ordered', "Cases Ord.")
g.set_label('units_ordered', "Units Ord.") g.set_label('units_ordered', "Units Ord.")
g.set_label('cases_received', "Cases Rec.") g.set_label('cases_received', "Cases Rec.")
@ -475,30 +582,125 @@ class PurchasingBatchView(BatchMasterView):
if row.status_code in (row.STATUS_INCOMPLETE, row.STATUS_ORDERED_RECEIVED_DIFFER): if row.status_code in (row.STATUS_INCOMPLETE, row.STATUS_ORDERED_RECEIVED_DIFFER):
return 'notice' return 'notice'
def _preconfigure_row_fieldset(self, fs): def configure_row_form(self, f):
super(PurchasingBatchView, self)._preconfigure_row_fieldset(fs) super(PurchasingBatchView, self).configure_row_form(f)
fs.upc.set(label="UPC") row = f.model_instance
fs.item_id.set(label="Item ID") if self.creating:
fs.brand_name.set(label="Brand") batch = self.get_instance()
fs.case_quantity.set(renderer=forms.renderers.QuantityFieldRenderer, readonly=True) else:
fs.cases_ordered.set(renderer=forms.renderers.QuantityFieldRenderer) batch = self.get_parent(row)
fs.units_ordered.set(renderer=forms.renderers.QuantityFieldRenderer)
fs.cases_received.set(renderer=forms.renderers.QuantityFieldRenderer) # readonly fields
fs.units_received.set(renderer=forms.renderers.QuantityFieldRenderer) f.set_readonly('case_quantity')
fs.cases_damaged.set(renderer=forms.renderers.QuantityFieldRenderer) f.set_readonly('credits')
fs.units_damaged.set(renderer=forms.renderers.QuantityFieldRenderer)
fs.cases_expired.set(renderer=forms.renderers.QuantityFieldRenderer) # quantity fields
fs.units_expired.set(renderer=forms.renderers.QuantityFieldRenderer) f.set_type('case_quantity', 'quantity')
fs.cases_mispick.set(renderer=forms.renderers.QuantityFieldRenderer) f.set_type('cases_ordered', 'quantity')
fs.units_mispick.set(renderer=forms.renderers.QuantityFieldRenderer) f.set_type('units_ordered', 'quantity')
fs.po_line_number.set(label="PO Line Number") f.set_type('cases_received', 'quantity')
fs.po_unit_cost.set(label="PO Unit Cost", renderer=forms.renderers.CurrencyFieldRenderer) f.set_type('units_received', 'quantity')
fs.po_total.set(label="PO Total", renderer=forms.renderers.CurrencyFieldRenderer) f.set_type('cases_damaged', 'quantity')
fs.invoice_unit_cost.set(renderer=forms.renderers.CurrencyFieldRenderer) f.set_type('units_damaged', 'quantity')
fs.invoice_total.set(renderer=forms.renderers.CurrencyFieldRenderer) f.set_type('cases_expired', 'quantity')
fs.credits.set(readonly=True) f.set_type('units_expired', 'quantity')
# fs.append(fa.Field('item_lookup', label="Item Lookup Code", required=True, f.set_type('cases_mispick', 'quantity')
# validate=self.item_lookup)) f.set_type('units_mispick', 'quantity')
# currency fields
f.set_type('po_unit_cost', 'currency')
f.set_type('po_total', 'currency')
f.set_type('invoice_unit_cost', 'currency')
f.set_type('invoice_total', 'currency')
if self.creating:
f.remove_fields(
'upc',
'product',
'po_total',
'invoice_total',
)
if self.batch_mode == self.enum.PURCHASE_BATCH_MODE_ORDERING:
f.remove_fields('cases_received',
'units_received')
elif self.batch_mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING:
f.remove_fields('cases_ordered',
'units_ordered')
elif self.editing:
f.set_readonly('upc')
f.set_readonly('product')
f.remove_fields('po_total',
'invoice_total',
'status_code')
elif self.viewing:
if row.product:
f.remove_fields('brand_name',
'description',
'size')
else:
f.remove_field('product')
def configure_mobile_row_form(self, f):
super(PurchasingBatchView, self).configure_mobile_row_form(f)
# row = f.model_instance
# if self.creating:
# batch = self.get_instance()
# else:
# batch = self.get_parent(row)
# # readonly fields
# f.set_readonly('case_quantity')
# f.set_readonly('credits')
# quantity fields
f.set_type('case_quantity', 'quantity')
f.set_type('cases_ordered', 'quantity')
f.set_type('units_ordered', 'quantity')
f.set_type('cases_received', 'quantity')
f.set_type('units_received', 'quantity')
f.set_type('cases_damaged', 'quantity')
f.set_type('units_damaged', 'quantity')
f.set_type('cases_expired', 'quantity')
f.set_type('units_expired', 'quantity')
f.set_type('cases_mispick', 'quantity')
f.set_type('units_mispick', 'quantity')
# currency fields
f.set_type('po_unit_cost', 'currency')
f.set_type('po_total', 'currency')
f.set_type('invoice_unit_cost', 'currency')
f.set_type('invoice_total', 'currency')
# if self.creating:
# f.remove_fields(
# 'upc',
# 'product',
# 'po_total',
# 'invoice_total',
# )
# if self.batch_mode == self.enum.PURCHASE_BATCH_MODE_ORDERING:
# f.remove_fields('cases_received',
# 'units_received')
# elif self.batch_mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING:
# f.remove_fields('cases_ordered',
# 'units_ordered')
# elif self.editing:
# f.set_readonly('upc')
# f.set_readonly('product')
# f.remove_fields('po_total',
# 'invoice_total',
# 'status_code')
# elif self.viewing:
# if row.product:
# f.remove_fields('brand_name',
# 'description',
# 'size')
# else:
# f.remove_field('product')
# def item_lookup(self, value, field=None): # def item_lookup(self, value, field=None):
# """ # """
@ -519,71 +721,6 @@ class PurchasingBatchView(BatchMasterView):
# return product.uuid # return product.uuid
# raise fa.ValidationError("Product not found") # raise fa.ValidationError("Product not found")
def configure_row_fieldset(self, fs):
try:
batch = self.get_instance()
except httpexceptions.HTTPNotFound:
batch = self.get_row_instance().batch
fs.configure(
include=[
# fs.item_lookup,
fs.upc,
fs.item_id,
fs.product,
fs.brand_name,
fs.description,
fs.size,
fs.case_quantity,
fs.cases_ordered,
fs.units_ordered,
fs.cases_received,
fs.units_received,
fs.cases_damaged,
fs.units_damaged,
fs.cases_expired,
fs.units_expired,
fs.cases_mispick,
fs.units_mispick,
fs.po_line_number,
fs.po_unit_cost,
fs.po_total,
fs.invoice_line_number,
fs.invoice_unit_cost,
fs.invoice_total,
fs.status_code,
fs.credits,
])
if self.creating:
del fs.upc
del fs.product
del fs.po_total
del fs.invoice_total
if self.batch_mode == self.enum.PURCHASE_BATCH_MODE_ORDERING:
del fs.cases_received
del fs.units_received
elif self.batch_mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING:
del fs.cases_ordered
del fs.units_ordered
elif self.editing:
# del fs.item_lookup
fs.upc.set(readonly=True)
fs.product.set(readonly=True)
del fs.po_total
del fs.invoice_total
del fs.status_code
elif self.viewing:
# del fs.item_lookup
if fs.model.product:
del (fs.brand_name,
fs.description,
fs.size)
else:
del fs.product
# def before_create_row(self, form): # def before_create_row(self, form):
# row = form.fieldset.model # row = form.fieldset.model
# batch = self.get_instance() # batch = self.get_instance()
@ -594,34 +731,38 @@ class PurchasingBatchView(BatchMasterView):
# def after_create_row(self, row): # def after_create_row(self, row):
# self.handler.refresh_row(row) # self.handler.refresh_row(row)
# def after_edit_row(self, row): def save_edit_row_form(self, form):
# batch = row.batch row = form.model_instance
batch = row.batch
# # first undo any totals previously in effect for the row # first undo any totals previously in effect for the row
# if batch.mode == self.enum.PURCHASE_BATCH_MODE_ORDERING and row.po_total: if batch.mode == self.enum.PURCHASE_BATCH_MODE_ORDERING and row.po_total:
# batch.po_total -= row.po_total batch.po_total -= row.po_total
# elif batch.mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING and row.invoice_total: elif batch.mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING and row.invoice_total:
# batch.invoice_total -= row.invoice_total batch.invoice_total -= row.invoice_total
# self.handler.refresh_row(row) row = super(PurchasingBatchView, self).save_edit_row_form(form)
# TODO: is this needed?
# self.handler.refresh_row(row)
return row
# def redirect_after_create_row(self, row): # def redirect_after_create_row(self, row):
# 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): def delete_row(self):
# """ """
# Update the PO total in addition to marking row as removed. Update the batch totals in addition to marking row as removed.
# """ """
# row = self.Session.query(self.model_row_class).get(self.request.matchdict['uuid']) row = self.Session.query(self.model_row_class).get(self.request.matchdict['row_uuid'])
# if not row: if not row:
# raise httpexceptions.HTTPNotFound() raise self.notfound()
# if row.po_total: batch = row.batch
# row.batch.po_total -= row.po_total if row.po_total:
# if row.invoice_total: batch.po_total -= row.po_total
# row.batch.invoice_total -= row.invoice_total if row.invoice_total:
# row.removed = True batch.invoice_total -= row.invoice_total
# return self.redirect(self.get_action_url('view', row.batch)) return super(PurchasingBatchView, self).delete_row()
# def get_execute_success_url(self, batch, result, **kwargs): # def get_execute_success_url(self, batch, result, **kwargs):
# # if batch execution yielded a Purchase, redirect to it # # if batch execution yielded a Purchase, redirect to it

View file

@ -38,7 +38,6 @@ from rattail.time import localtime
from pyramid.response import FileResponse from pyramid.response import FileResponse
from tailbone import forms
from tailbone.views.purchasing import PurchasingBatchView from tailbone.views.purchasing import PurchasingBatchView
@ -55,6 +54,21 @@ class OrderingBatchView(PurchasingBatchView):
rows_editable = True rows_editable = True
mobile_rows_editable = True mobile_rows_editable = True
mobile_form_fields = [
'vendor',
'department',
'date_ordered',
'po_number',
'po_total',
'created',
'created_by',
'notes',
'status_code',
'complete',
'executed',
'executed_by',
]
row_grid_columns = [ row_grid_columns = [
'sequence', 'sequence',
'upc', 'upc',
@ -233,7 +247,9 @@ class OrderingBatchView(PurchasingBatchView):
if cases_ordered or units_ordered: if cases_ordered or units_ordered:
row.cases_ordered = cases_ordered or None row.cases_ordered = cases_ordered or None
row.units_ordered = units_ordered or None row.units_ordered = units_ordered or None
row.removed = False if row.removed:
row.removed = False
batch.rowcount += 1
self.handler.refresh_row(row) self.handler.refresh_row(row)
else: else:
row.removed = True row.removed = True
@ -246,6 +262,7 @@ class OrderingBatchView(PurchasingBatchView):
row.cases_ordered = cases_ordered or None row.cases_ordered = cases_ordered or None
row.units_ordered = units_ordered or None row.units_ordered = units_ordered or None
self.handler.refresh_row(row) self.handler.refresh_row(row)
batch.rowcount += 1
return { return {
'row_cases_ordered': '' if not row or row.removed else int(row.cases_ordered or 0), 'row_cases_ordered': '' if not row or row.removed else int(row.cases_ordered or 0),
@ -292,31 +309,6 @@ class OrderingBatchView(PurchasingBatchView):
data['mode_title'] = self.enum.PURCHASE_BATCH_MODE[mode].capitalize() data['mode_title'] = self.enum.PURCHASE_BATCH_MODE[mode].capitalize()
return self.render_to_response('create', data, mobile=True) return self.render_to_response('create', data, mobile=True)
def preconfigure_mobile_fieldset(self, fs):
super(OrderingBatchView, self).preconfigure_mobile_fieldset(fs)
fs.vendor.set(attrs={'hyperlink': False})
def configure_mobile_fieldset(self, fs):
fields = [
fs.vendor,
fs.department,
fs.date_ordered,
fs.po_number,
fs.po_total,
fs.created,
fs.created_by,
fs.notes,
fs.status_code,
fs.complete,
]
batch = fs.model
if (self.viewing or self.deleting) and batch.executed:
fields.extend([
fs.executed,
fs.executed_by,
])
fs.configure(include=fields)
def download_excel(self): def download_excel(self):
""" """
Download ordering batch as Excel spreadsheet. Download ordering batch as Excel spreadsheet.

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2017 Lance Edgar # Copyright © 2010-2018 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -35,7 +35,6 @@ from rattail.db import model, api
from rattail.gpc import GPC from rattail.gpc import GPC
from rattail.util import pretty_quantity, prettify from rattail.util import pretty_quantity, prettify
import formalchemy as fa
import formencode as fe import formencode as fe
from webhelpers2.html import tags from webhelpers2.html import tags
@ -95,6 +94,11 @@ class ReceivingBatchView(PurchasingBatchView):
mobile_rows_filterable = True mobile_rows_filterable = True
mobile_rows_creatable = True mobile_rows_creatable = True
mobile_form_fields = [
'vendor',
'department',
]
row_grid_columns = [ row_grid_columns = [
'sequence', 'sequence',
'upc', 'upc',
@ -112,6 +116,14 @@ class ReceivingBatchView(PurchasingBatchView):
'status_code', 'status_code',
] ]
row_form_fields = [
'vendor',
'department',
'complete',
'executed',
'executed_by',
]
@property @property
def batch_mode(self): def batch_mode(self):
return self.enum.PURCHASE_BATCH_MODE_RECEIVING return self.enum.PURCHASE_BATCH_MODE_RECEIVING
@ -186,21 +198,14 @@ class ReceivingBatchView(PurchasingBatchView):
kwargs['sms_transaction_number'] = batch.sms_transaction_number kwargs['sms_transaction_number'] = batch.sms_transaction_number
return kwargs return kwargs
def configure_mobile_fieldset(self, fs): def configure_mobile_form(self, f):
fs.configure(include=[ super(ReceivingBatchView, self).configure_mobile_form(f)
fs.vendor.with_renderer(fa.TextFieldRenderer),
fs.department.with_renderer(fa.TextFieldRenderer), # vendor
fs.complete, # fs.vendor.with_renderer(fa.TextFieldRenderer),
fs.executed,
fs.executed_by, # department
]) # fs.department.with_renderer(fa.TextFieldRenderer),
batch = fs.model
if not batch.executed:
del [fs.executed, fs.executed_by]
if not batch.complete:
del fs.complete
else:
del fs.complete
def render_mobile_row_listitem(self, row, i): def render_mobile_row_listitem(self, row, i):
description = row.product.full_description if row.product else row.description description = row.product.full_description if row.product else row.description