Add new 'receiving form' for purchase batches

This commit is contained in:
Lance Edgar 2016-12-08 17:24:42 -06:00
parent 468a84aa90
commit 6c3d221e98
5 changed files with 323 additions and 36 deletions

View file

@ -335,6 +335,46 @@ class ProductsView(MasterView):
'instance_title': self.get_instance_title(instance),
'form': form})
def search(self):
"""
Locate a product(s) by UPC.
Eventually this should be more generic, or at least offer more fields for
search. For now it operates only on the ``Product.upc`` field.
"""
data = None
upc = self.request.GET.get('upc', '').strip()
upc = re.sub(r'\D', '', upc)
if upc:
product = api.get_product_by_upc(Session(), upc)
if not product:
# Try again, assuming caller did not include check digit.
upc = GPC(upc, calc_check_digit='upc')
product = api.get_product_by_upc(Session(), upc)
if product and (not product.deleted or self.request.has_perm('products.view_deleted')):
data = {
'uuid': product.uuid,
'upc': unicode(product.upc),
'upc_pretty': product.upc.pretty(),
'full_description': product.full_description,
'image_url': pod.get_image_url(self.rattail_config, product.upc),
}
uuid = self.request.GET.get('with_vendor_cost')
if uuid:
vendor = Session.query(model.Vendor).get(uuid)
if not vendor:
return {'error': "Vendor not found"}
cost = product.cost_for_vendor(vendor)
if cost:
data['cost_found'] = True
if int(cost.case_size) == cost.case_size:
data['cost_case_size'] = int(cost.case_size)
else:
data['cost_case_size'] = '{:0.4f}'.format(cost.case_size)
else:
data['cost_found'] = False
return {'product': data}
def get_supported_batches(self):
return {
'labels': 'rattail.batch.labels:LabelBatchHandler',
@ -479,6 +519,11 @@ class ProductsView(MasterView):
config.add_view(cls, attr='make_batch', route_name='products.create_batch',
renderer='/products/batch.mako', permission='batches.create')
# search (by upc)
config.add_route('products.search', '/products/search')
config.add_view(cls, attr='search', route_name='products.search',
renderer='json', permission='products.view')
cls._defaults(config)
@ -535,34 +580,6 @@ class ProductsAutocomplete(AutocompleteView):
return product.full_description
def products_search(request):
"""
Locate a product(s) by UPC.
Eventually this should be more generic, or at least offer more fields for
search. For now it operates only on the ``Product.upc`` field.
"""
product = None
upc = request.GET.get('upc', '').strip()
upc = re.sub(r'\D', '', upc)
if upc:
product = api.get_product_by_upc(Session(), upc)
if not product:
# Try again, assuming caller did not include check digit.
upc = GPC(upc, calc_check_digit='upc')
product = api.get_product_by_upc(Session(), upc)
if product:
if product.deleted and not request.has_perm('products.view_deleted'):
product = None
else:
product = {
'uuid': product.uuid,
'upc': unicode(product.upc or ''),
'full_description': product.full_description,
}
return {'product': product}
def print_labels(request):
profile = request.params.get('profile')
profile = Session.query(model.LabelProfile).get(profile) if profile else None
@ -600,9 +617,5 @@ def includeme(config):
config.add_view(print_labels, route_name='products.print_labels',
renderer='json', permission='products.print_labels')
config.add_route('products.search', '/products/search')
config.add_view(products_search, route_name='products.search',
renderer='json', permission='products.list')
ProductsView.defaults(config)
version_defaults(config, ProductVersionView, 'product')

View file

@ -26,9 +26,12 @@ Views for purchase order batches
from __future__ import unicode_literals, absolute_import
import re
import logging
from sqlalchemy import orm
from rattail import enum
from rattail import enum, pod
from rattail.db import model, api
from rattail.gpc import GPC
from rattail.time import localtime
@ -36,6 +39,7 @@ from rattail.core import Object
from rattail.util import OrderedDict
import formalchemy as fa
import formencode as fe
from pyramid import httpexceptions
from tailbone import forms
@ -43,6 +47,21 @@ from tailbone.db import Session
from tailbone.views.batch import BatchMasterView
log = logging.getLogger(__name__)
class ReceivingForm(forms.Schema):
allow_extra_fields = True
filter_extra_fields = True
mode = fe.validators.OneOf([
'received',
# 'damaged', 'expired', 'mispick',
])
product = forms.validators.ValidProduct()
cases = fe.validators.Int()
units = fe.validators.Int()
class PurchaseBatchView(BatchMasterView):
"""
Master view for purchase order batches.
@ -578,6 +597,95 @@ class PurchaseBatchView(BatchMasterView):
'batch_po_total': '${:0,.2f}'.format(batch.po_total),
}
def receiving_form(self):
"""
Workflow view for receiving items on a purchase batch.
"""
batch = self.get_instance()
if batch.executed:
return self.redirect(self.get_action_url('view', batch))
form = forms.SimpleForm(self.request, schema=ReceivingForm)
if form.validate():
assert form.data['mode'] == 'received' # TODO
product = form.data['product']
rows = [row for row in batch.active_rows() if row.product is product]
if rows:
if len(rows) > 1:
log.warning("found {} matching rows in batch {} for product: {}".format(
len(rows), batch.id_str, product.upc.pretty()))
row = rows[0]
else:
row = model.PurchaseBatchRow()
row.product = product
if form.data['cases']:
row.cases_received = (row.cases_received or 0) + form.data['cases']
if form.data['units']:
row.units_received = (row.units_received or 0) + form.data['units']
if not row.uuid:
batch.add_row(row)
self.handler.refresh_row(row)
self.request.session.flash("({}) {} cases, {} units: {} {}".format(
form.data['mode'], form.data['cases'] or 0, form.data['units'] or 0,
product.upc.pretty(), product))
return self.redirect(self.request.current_route_url())
title = self.get_instance_title(batch)
return self.render_to_response('receive_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': batch.vendor,
'form': forms.FormRenderer(form),
})
def receiving_lookup(self):
"""
Try to locate a product by UPC, and validate it in the context of
current batch, returning some data for client JS.
"""
batch = self.get_instance()
if batch.executed:
return {
'error': "Current batch has already been executed",
'redirect': self.get_action_url('view', batch),
}
data = None
upc = self.request.GET.get('upc', '').strip()
upc = re.sub(r'\D', '', upc)
if upc:
product = api.get_product_by_upc(Session(), upc)
if not product:
# Try again, assuming caller did not include check digit.
upc = GPC(upc, calc_check_digit='upc')
product = api.get_product_by_upc(Session(), upc)
if product and (not product.deleted or self.request.has_perm('products.view_deleted')):
data = {
'uuid': product.uuid,
'upc': unicode(product.upc),
'upc_pretty': product.upc.pretty(),
'full_description': product.full_description,
'image_url': pod.get_image_url(self.rattail_config, product.upc),
}
cost = product.cost_for_vendor(batch.vendor)
if cost:
data['cost_found'] = True
if int(cost.case_size) == cost.case_size:
data['cost_case_size'] = int(cost.case_size)
else:
data['cost_case_size'] = '{:0.4f}'.format(cost.case_size)
else:
data['cost_found'] = False
data['found_in_batch'] = product in [row.product for row in batch.active_rows()]
return {'product': data}
@classmethod
def defaults(cls, config):
route_prefix = cls.get_route_prefix()
@ -595,7 +703,7 @@ class PurchaseBatchView(BatchMasterView):
cls._batch_defaults(config)
cls._defaults(config)
# order form
# ordering 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))
@ -605,6 +713,16 @@ class PurchaseBatchView(BatchMasterView):
config.add_view(cls, attr='order_form_update', route_name='{}.order_form_update'.format(route_prefix),
renderer='json', permission='{}.order_form'.format(permission_prefix))
# receiving form, lookup
config.add_tailbone_permission(permission_prefix, '{}.receiving_form'.format(permission_prefix),
"Edit 'receiving' {} in Receiving Form mode".format(model_title))
config.add_route('{}.receiving_form'.format(route_prefix), '{}/{{{}}}/receiving-form'.format(url_prefix, model_key))
config.add_view(cls, attr='receiving_form', route_name='{}.receiving_form'.format(route_prefix),
permission='{}.receiving_form'.format(permission_prefix))
config.add_route('{}.receiving_lookup'.format(route_prefix), '{}/{{{}}}/receiving-form/lookup'.format(url_prefix, model_key))
config.add_view(cls, attr='receiving_lookup', route_name='{}.receiving_lookup'.format(route_prefix),
renderer='json', permission='{}.receiving_form'.format(permission_prefix))
def includeme(config):
PurchaseBatchView.defaults(config)