Purge things for legacy (jquery) mobile, and unused template themes
gosh it feels good to get rid of this stuff... fingers crossed that nothing was broken, but am thinking it's safe
This commit is contained in:
parent
fac00e6ecd
commit
708641a8f1
70 changed files with 196 additions and 4886 deletions
|
@ -150,34 +150,6 @@ class PurchasingBatchView(BatchMasterView):
|
|||
'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
|
||||
def batch_mode(self):
|
||||
raise NotImplementedError("Please define `batch_mode` for your purchasing batch view")
|
||||
|
@ -518,8 +490,8 @@ class PurchasingBatchView(BatchMasterView):
|
|||
total = purchase.invoice_total
|
||||
return '{} for ${:0,.2f} ({})'.format(date, total or 0, purchase.department or purchase.buyer)
|
||||
|
||||
def get_batch_kwargs(self, batch, mobile=False):
|
||||
kwargs = super(PurchasingBatchView, self).get_batch_kwargs(batch, mobile=mobile)
|
||||
def get_batch_kwargs(self, batch, **kwargs):
|
||||
kwargs = super(PurchasingBatchView, self).get_batch_kwargs(batch, **kwargs)
|
||||
kwargs['mode'] = self.batch_mode
|
||||
kwargs['truck_dump'] = batch.truck_dump
|
||||
kwargs['invoice_parser_key'] = batch.invoice_parser_key
|
||||
|
@ -596,9 +568,6 @@ class PurchasingBatchView(BatchMasterView):
|
|||
# query = super(PurchasingBatchView, self).get_row_data(batch)
|
||||
# return query.options(orm.joinedload(model.PurchaseBatchRow.credits))
|
||||
|
||||
def sort_mobile_row_data(self, query):
|
||||
return query.order_by(model.PurchaseBatchRow.modified.desc())
|
||||
|
||||
def configure_row_grid(self, g):
|
||||
super(PurchasingBatchView, self).configure_row_grid(g)
|
||||
|
||||
|
@ -760,104 +729,6 @@ class PurchasingBatchView(BatchMasterView):
|
|||
g.set_type('credit_total', 'currency')
|
||||
return HTML.literal(g.render_grid())
|
||||
|
||||
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('po_total_calculated', 'currency')
|
||||
f.set_type('invoice_unit_cost', 'currency')
|
||||
f.set_type('invoice_total', 'currency')
|
||||
f.set_type('invoice_total_calculated', '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 mobile_new_product(self):
|
||||
"""
|
||||
View which allows user to create a new Product and add a row for it to
|
||||
the Purchasing Batch.
|
||||
"""
|
||||
batch = self.get_instance()
|
||||
batch_url = self.get_action_url('view', batch, mobile=True)
|
||||
form = forms.Form(schema=self.make_new_product_schema(),
|
||||
request=self.request,
|
||||
mobile=True,
|
||||
cancel_url=batch_url)
|
||||
|
||||
if form.validate(newstyle=True):
|
||||
product = model.Product()
|
||||
product.item_id = form.validated['item_id']
|
||||
product.description = form.validated['description']
|
||||
row = self.model_row_class()
|
||||
row.product = product
|
||||
self.handler.add_row(batch, row)
|
||||
self.Session.flush()
|
||||
return self.redirect(self.get_row_action_url('edit', row, mobile=True))
|
||||
|
||||
return self.render_to_response('new_product', {
|
||||
'form': form,
|
||||
'dform': form.make_deform_form(),
|
||||
'instance_title': self.get_instance_title(batch),
|
||||
'instance_url': batch_url,
|
||||
}, mobile=True)
|
||||
|
||||
def make_new_product_schema(self):
|
||||
"""
|
||||
Must return a ``colander.Schema`` instance for use with the form in the
|
||||
:meth:`mobile_new_product()` view.
|
||||
"""
|
||||
return NewProduct()
|
||||
|
||||
# def item_lookup(self, value, field=None):
|
||||
# """
|
||||
# Try to locate a single product using ``value`` as a lookup code.
|
||||
|
@ -956,9 +827,9 @@ class PurchasingBatchView(BatchMasterView):
|
|||
# return self.redirect(self.request.current_route_url())
|
||||
|
||||
# TODO: seems like this should be master behavior, controlled by setting?
|
||||
def redirect_after_edit_row(self, row, mobile=False):
|
||||
def redirect_after_edit_row(self, row, **kwargs):
|
||||
parent = self.get_parent(row)
|
||||
return self.redirect(self.get_action_url('view', parent, mobile=mobile))
|
||||
return self.redirect(self.get_action_url('view', parent))
|
||||
|
||||
# def get_execute_success_url(self, batch, result, **kwargs):
|
||||
# # if batch execution yielded a Purchase, redirect to it
|
||||
|
@ -977,21 +848,12 @@ class PurchasingBatchView(BatchMasterView):
|
|||
permission_prefix = cls.get_permission_prefix()
|
||||
model_key = cls.get_model_key()
|
||||
model_title = cls.get_model_title()
|
||||
legacy_mobile = cls.legacy_mobile_enabled(rattail_config)
|
||||
|
||||
# eligible purchases (AJAX)
|
||||
config.add_route('{}.eligible_purchases'.format(route_prefix), '{}/eligible-purchases'.format(url_prefix))
|
||||
config.add_view(cls, attr='eligible_purchases', route_name='{}.eligible_purchases'.format(route_prefix),
|
||||
renderer='json', permission='{}.view'.format(permission_prefix))
|
||||
|
||||
# add new product
|
||||
if legacy_mobile and cls.supports_new_product:
|
||||
config.add_tailbone_permission(permission_prefix, '{}.new_product'.format(permission_prefix),
|
||||
"Create new Product when adding row to {}".format(model_title))
|
||||
config.add_route('mobile.{}.new_product'.format(route_prefix), '{}/{{{}}}/new-product'.format(url_prefix, model_key))
|
||||
config.add_view(cls, attr='mobile_new_product', route_name='mobile.{}.new_product'.format(route_prefix),
|
||||
permission='{}.new_product'.format(permission_prefix))
|
||||
|
||||
|
||||
@classmethod
|
||||
def defaults(cls, config):
|
||||
|
|
|
@ -51,12 +51,7 @@ class OrderingBatchView(PurchasingBatchView):
|
|||
model_title = "Ordering Batch"
|
||||
model_title_plural = "Ordering Batches"
|
||||
index_title = "Ordering"
|
||||
mobile_creatable = True
|
||||
rows_editable = True
|
||||
mobile_rows_creatable = True
|
||||
mobile_rows_quickable = True
|
||||
mobile_rows_editable = True
|
||||
mobile_rows_deletable = True
|
||||
has_worksheet = True
|
||||
|
||||
labels = {
|
||||
|
@ -86,21 +81,6 @@ class OrderingBatchView(PurchasingBatchView):
|
|||
'executed_by',
|
||||
]
|
||||
|
||||
mobile_form_fields = [
|
||||
'vendor',
|
||||
'department',
|
||||
'date_ordered',
|
||||
'po_number',
|
||||
'po_total',
|
||||
'created',
|
||||
'created_by',
|
||||
'notes',
|
||||
'status_code',
|
||||
'complete',
|
||||
'executed',
|
||||
'executed_by',
|
||||
]
|
||||
|
||||
row_labels = {
|
||||
'po_total_calculated': "PO Total",
|
||||
}
|
||||
|
@ -161,8 +141,8 @@ class OrderingBatchView(PurchasingBatchView):
|
|||
if self.creating or not batch.executed or not batch.purchase:
|
||||
f.remove_field('purchase')
|
||||
|
||||
def get_batch_kwargs(self, batch, mobile=False):
|
||||
kwargs = super(OrderingBatchView, self).get_batch_kwargs(batch, mobile=mobile)
|
||||
def get_batch_kwargs(self, batch, **kwargs):
|
||||
kwargs = super(OrderingBatchView, self).get_batch_kwargs(batch, **kwargs)
|
||||
kwargs['ship_method'] = batch.ship_method
|
||||
kwargs['notes_to_vendor'] = batch.notes_to_vendor
|
||||
return kwargs
|
||||
|
@ -387,60 +367,6 @@ class OrderingBatchView(PurchasingBatchView):
|
|||
'batch_po_total_display': '${:0,.2f}'.format(batch.po_total_calculated or batch.po_total or 0),
|
||||
}
|
||||
|
||||
def render_mobile_listitem(self, batch, i):
|
||||
return "({}) {} on {} for ${:0,.2f}".format(batch.id_str, batch.vendor,
|
||||
batch.date_ordered, batch.po_total or 0)
|
||||
|
||||
def mobile_create(self):
|
||||
"""
|
||||
Mobile view for creating a new ordering batch
|
||||
"""
|
||||
mode = self.batch_mode
|
||||
data = {'mode': mode}
|
||||
|
||||
vendor = None
|
||||
if self.request.method == 'POST' and self.request.POST.get('vendor'):
|
||||
vendor = self.Session.query(model.Vendor).get(self.request.POST['vendor'])
|
||||
if vendor:
|
||||
|
||||
# fetch first to avoid flush below
|
||||
store = self.rattail_config.get_store(self.Session())
|
||||
|
||||
batch = self.model_class()
|
||||
batch.mode = mode
|
||||
batch.vendor = vendor
|
||||
batch.store = store
|
||||
batch.buyer = self.request.user.employee
|
||||
batch.created_by = self.request.user
|
||||
batch.po_total = 0
|
||||
kwargs = self.get_batch_kwargs(batch, mobile=True)
|
||||
batch = self.handler.make_batch(self.Session(), **kwargs)
|
||||
if self.handler.should_populate(batch):
|
||||
self.handler.populate(batch)
|
||||
return self.redirect(self.request.route_url('mobile.ordering.view', uuid=batch.uuid))
|
||||
|
||||
data['index_title'] = self.get_index_title()
|
||||
data['index_url'] = self.get_index_url(mobile=True)
|
||||
data['mode_title'] = self.enum.PURCHASE_BATCH_MODE[mode].capitalize()
|
||||
|
||||
data['vendor_use_autocomplete'] = self.rattail_config.getbool(
|
||||
'rattail', 'vendor.use_autocomplete', default=True)
|
||||
if not data['vendor_use_autocomplete']:
|
||||
vendors = self.Session.query(model.Vendor)\
|
||||
.order_by(model.Vendor.name)
|
||||
options = [(tags.Option(vendor.name, vendor.uuid))
|
||||
for vendor in vendors]
|
||||
options.insert(0, tags.Option("(please choose)", ''))
|
||||
data['vendor_options'] = options
|
||||
|
||||
return self.render_to_response('create', data, mobile=True)
|
||||
|
||||
def configure_mobile_row_form(self, f):
|
||||
super(OrderingBatchView, self).configure_mobile_row_form(f)
|
||||
if self.editing:
|
||||
# TODO: probably should take `allow_cases` into account here...
|
||||
f.focus_spec = '[name="units_ordered"]'
|
||||
|
||||
def download_excel(self):
|
||||
"""
|
||||
Download ordering batch as Excel spreadsheet.
|
||||
|
|
|
@ -48,78 +48,11 @@ from webhelpers2.html import tags, HTML
|
|||
|
||||
from tailbone import forms, grids
|
||||
from tailbone.views.purchasing import PurchasingBatchView
|
||||
from tailbone.forms.receiving import ReceiveRow as MobileReceivingForm
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MobileItemStatusFilter(grids.filters.MobileFilter):
|
||||
|
||||
value_choices = ['incomplete', 'unexpected', 'damaged', 'expired', 'all']
|
||||
|
||||
def filter_equal(self, query, value):
|
||||
|
||||
# NOTE: this is only relevant for truck dump or "from scratch"
|
||||
if value == 'received':
|
||||
return query.filter(sa.or_(
|
||||
model.PurchaseBatchRow.cases_received != 0,
|
||||
model.PurchaseBatchRow.units_received != 0))
|
||||
|
||||
if value == 'incomplete':
|
||||
# looking for any rows with "ordered" quantity, but where the
|
||||
# status does *not* signify a "settled" row so to speak
|
||||
# TODO: would be nice if we had a simple flag to leverage?
|
||||
return query.filter(sa.or_(model.PurchaseBatchRow.cases_ordered != 0,
|
||||
model.PurchaseBatchRow.units_ordered != 0))\
|
||||
.filter(~model.PurchaseBatchRow.status_code.in_((
|
||||
model.PurchaseBatchRow.STATUS_OK,
|
||||
model.PurchaseBatchRow.STATUS_PRODUCT_NOT_FOUND,
|
||||
model.PurchaseBatchRow.STATUS_CASE_QUANTITY_DIFFERS)))
|
||||
|
||||
if value == 'invalid':
|
||||
return query.filter(model.PurchaseBatchRow.status_code.in_((
|
||||
model.PurchaseBatchRow.STATUS_PRODUCT_NOT_FOUND,
|
||||
model.PurchaseBatchRow.STATUS_COST_NOT_FOUND,
|
||||
model.PurchaseBatchRow.STATUS_CASE_QUANTITY_UNKNOWN,
|
||||
model.PurchaseBatchRow.STATUS_CASE_QUANTITY_DIFFERS,
|
||||
)))
|
||||
|
||||
if value == 'unexpected':
|
||||
# looking for any rows which have "received" quantity but which
|
||||
# do *not* have any "ordered" quantity
|
||||
return query.filter(sa.and_(
|
||||
sa.or_(
|
||||
model.PurchaseBatchRow.cases_ordered == None,
|
||||
model.PurchaseBatchRow.cases_ordered == 0),
|
||||
sa.or_(
|
||||
model.PurchaseBatchRow.units_ordered == None,
|
||||
model.PurchaseBatchRow.units_ordered == 0),
|
||||
sa.or_(
|
||||
model.PurchaseBatchRow.cases_received != 0,
|
||||
model.PurchaseBatchRow.units_received != 0,
|
||||
model.PurchaseBatchRow.cases_damaged != 0,
|
||||
model.PurchaseBatchRow.units_damaged != 0,
|
||||
model.PurchaseBatchRow.cases_expired != 0,
|
||||
model.PurchaseBatchRow.units_expired != 0)))
|
||||
|
||||
if value == 'damaged':
|
||||
return query.filter(sa.or_(
|
||||
model.PurchaseBatchRow.cases_damaged != 0,
|
||||
model.PurchaseBatchRow.units_damaged != 0))
|
||||
|
||||
if value == 'expired':
|
||||
return query.filter(sa.or_(
|
||||
model.PurchaseBatchRow.cases_expired != 0,
|
||||
model.PurchaseBatchRow.units_expired != 0))
|
||||
|
||||
return query
|
||||
|
||||
def iter_choices(self):
|
||||
for value in self.value_choices:
|
||||
yield value, prettify(value)
|
||||
|
||||
|
||||
class ReceivingBatchView(PurchasingBatchView):
|
||||
"""
|
||||
Master view for receiving batches
|
||||
|
@ -132,11 +65,6 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
downloadable = True
|
||||
bulk_deletable = True
|
||||
rows_editable = True
|
||||
mobile_creatable = True
|
||||
mobile_rows_filterable = True
|
||||
mobile_rows_creatable = True
|
||||
mobile_rows_quickable = True
|
||||
mobile_rows_deletable = True
|
||||
|
||||
allow_from_po = False
|
||||
allow_from_scratch = True
|
||||
|
@ -207,11 +135,6 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
'executed_by',
|
||||
]
|
||||
|
||||
mobile_form_fields = [
|
||||
'vendor',
|
||||
'department',
|
||||
]
|
||||
|
||||
row_grid_columns = [
|
||||
'sequence',
|
||||
'upc',
|
||||
|
@ -295,20 +218,9 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
if batch.executed or batch.complete:
|
||||
return False
|
||||
|
||||
# can "always" delete rows from truck dump parent...
|
||||
# can always delete rows from truck dump parent
|
||||
if batch.is_truck_dump_parent():
|
||||
|
||||
# ...but only on desktop!
|
||||
if not self.mobile:
|
||||
return True
|
||||
|
||||
# ...for mobile we only allow deletion of rows which did *not* come
|
||||
# from a child batch, i.e. can delete ad-hoc rows only
|
||||
# TODO: should have a better way to detect this; for now we rely on
|
||||
# the fact that only rows from an invoice or similar would have
|
||||
# order quantities
|
||||
if not (row.cases_ordered or row.units_ordered):
|
||||
return True
|
||||
return True
|
||||
|
||||
# can always delete rows from truck dump child
|
||||
elif batch.is_truck_dump_child():
|
||||
|
@ -466,33 +378,32 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
kwargs['batch_vendor_map'] = vmap
|
||||
return kwargs
|
||||
|
||||
def get_batch_kwargs(self, batch, mobile=False):
|
||||
kwargs = super(ReceivingBatchView, self).get_batch_kwargs(batch, mobile=mobile)
|
||||
if not mobile:
|
||||
batch_type = self.request.POST['batch_type']
|
||||
if batch_type == 'from_scratch':
|
||||
kwargs.pop('truck_dump_batch', None)
|
||||
kwargs.pop('truck_dump_batch_uuid', None)
|
||||
elif batch_type == 'truck_dump_children_first':
|
||||
kwargs['truck_dump'] = True
|
||||
kwargs['truck_dump_children_first'] = True
|
||||
kwargs['order_quantities_known'] = True
|
||||
# TODO: this makes sense in some cases, but all?
|
||||
# (should just omit that field when not relevant)
|
||||
kwargs['date_ordered'] = None
|
||||
elif batch_type == 'truck_dump_children_last':
|
||||
kwargs['truck_dump'] = True
|
||||
kwargs['truck_dump_ready'] = True
|
||||
# TODO: this makes sense in some cases, but all?
|
||||
# (should just omit that field when not relevant)
|
||||
kwargs['date_ordered'] = None
|
||||
elif batch_type.startswith('truck_dump_child'):
|
||||
truck_dump = self.get_instance()
|
||||
kwargs['store'] = truck_dump.store
|
||||
kwargs['vendor'] = truck_dump.vendor
|
||||
kwargs['truck_dump_batch'] = truck_dump
|
||||
else:
|
||||
raise NotImplementedError
|
||||
def get_batch_kwargs(self, batch, **kwargs):
|
||||
kwargs = super(ReceivingBatchView, self).get_batch_kwargs(batch, **kwargs)
|
||||
batch_type = self.request.POST['batch_type']
|
||||
if batch_type == 'from_scratch':
|
||||
kwargs.pop('truck_dump_batch', None)
|
||||
kwargs.pop('truck_dump_batch_uuid', None)
|
||||
elif batch_type == 'truck_dump_children_first':
|
||||
kwargs['truck_dump'] = True
|
||||
kwargs['truck_dump_children_first'] = True
|
||||
kwargs['order_quantities_known'] = True
|
||||
# TODO: this makes sense in some cases, but all?
|
||||
# (should just omit that field when not relevant)
|
||||
kwargs['date_ordered'] = None
|
||||
elif batch_type == 'truck_dump_children_last':
|
||||
kwargs['truck_dump'] = True
|
||||
kwargs['truck_dump_ready'] = True
|
||||
# TODO: this makes sense in some cases, but all?
|
||||
# (should just omit that field when not relevant)
|
||||
kwargs['date_ordered'] = None
|
||||
elif batch_type.startswith('truck_dump_child'):
|
||||
truck_dump = self.get_instance()
|
||||
kwargs['store'] = truck_dump.store
|
||||
kwargs['vendor'] = truck_dump.vendor
|
||||
kwargs['truck_dump_batch'] = truck_dump
|
||||
else:
|
||||
raise NotImplementedError
|
||||
return kwargs
|
||||
|
||||
def department_for_purchase(self, purchase):
|
||||
|
@ -608,140 +519,6 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
url = self.request.route_url('receiving.view', uuid=truck_dump.uuid)
|
||||
return tags.link_to(text, url)
|
||||
|
||||
def render_mobile_listitem(self, batch, i):
|
||||
title = "({}) {} for ${:0,.2f} - {}, {}".format(
|
||||
batch.id_str,
|
||||
batch.vendor,
|
||||
batch.invoice_total or batch.po_total or 0,
|
||||
batch.department,
|
||||
batch.created_by)
|
||||
return title
|
||||
|
||||
def make_mobile_row_filters(self):
|
||||
"""
|
||||
Returns a set of filters for the mobile row grid.
|
||||
"""
|
||||
batch = self.get_instance()
|
||||
filters = grids.filters.GridFilterSet()
|
||||
|
||||
# visible filter options will depend on whether batch came from purchase
|
||||
if batch.order_quantities_known:
|
||||
value_choices = ['incomplete', 'unexpected', 'damaged', 'expired', 'invalid', 'all']
|
||||
default_status = 'incomplete'
|
||||
else:
|
||||
value_choices = ['received', 'damaged', 'expired', 'invalid', 'all']
|
||||
default_status = 'all'
|
||||
|
||||
# remove 'expired' filter option if not relevant
|
||||
if 'expired' in value_choices and not self.handler.allow_expired_credits():
|
||||
value_choices.remove('expired')
|
||||
|
||||
filters['status'] = MobileItemStatusFilter('status',
|
||||
value_choices=value_choices,
|
||||
default_value=default_status)
|
||||
return filters
|
||||
|
||||
def mobile_create(self):
|
||||
"""
|
||||
Mobile view for creating a new receiving batch
|
||||
"""
|
||||
mode = self.batch_mode
|
||||
data = {'mode': mode}
|
||||
phase = 1
|
||||
|
||||
schema = MobileNewReceivingBatch().bind(session=self.Session())
|
||||
form = forms.Form(schema=schema, request=self.request)
|
||||
if form.validate(newstyle=True):
|
||||
phase = form.validated['phase']
|
||||
|
||||
if form.validated['workflow'] == 'from_scratch':
|
||||
if not self.allow_from_scratch:
|
||||
raise NotImplementedError("Requested workflow not supported: from_scratch")
|
||||
batch = self.model_class()
|
||||
batch.store = self.rattail_config.get_store(self.Session())
|
||||
batch.mode = mode
|
||||
batch.vendor = self.Session.query(model.Vendor).get(form.validated['vendor'])
|
||||
batch.created_by = self.request.user
|
||||
batch.date_received = localtime(self.rattail_config).date()
|
||||
kwargs = self.get_batch_kwargs(batch, mobile=True)
|
||||
batch = self.handler.make_batch(self.Session(), **kwargs)
|
||||
return self.redirect(self.get_action_url('view', batch, mobile=True))
|
||||
|
||||
elif form.validated['workflow'] == 'truck_dump':
|
||||
if not self.allow_truck_dump:
|
||||
raise NotImplementedError("Requested workflow not supported: truck_dump")
|
||||
batch = self.model_class()
|
||||
batch.store = self.rattail_config.get_store(self.Session())
|
||||
batch.mode = mode
|
||||
batch.truck_dump = True
|
||||
batch.vendor = self.Session.query(model.Vendor).get(form.validated['vendor'])
|
||||
batch.created_by = self.request.user
|
||||
batch.date_received = localtime(self.rattail_config).date()
|
||||
kwargs = self.get_batch_kwargs(batch, mobile=True)
|
||||
batch = self.handler.make_batch(self.Session(), **kwargs)
|
||||
return self.redirect(self.get_action_url('view', batch, mobile=True))
|
||||
|
||||
elif form.validated['workflow'] == 'from_po':
|
||||
if not self.allow_from_po:
|
||||
raise NotImplementedError("Requested workflow not supported: from_po")
|
||||
|
||||
vendor = self.Session.query(model.Vendor).get(form.validated['vendor'])
|
||||
data['vendor'] = vendor
|
||||
|
||||
schema = self.make_mobile_receiving_from_po_schema()
|
||||
po_form = forms.Form(schema=schema, request=self.request)
|
||||
if phase == 2:
|
||||
if po_form.validate(newstyle=True):
|
||||
batch = self.model_class()
|
||||
batch.store = self.rattail_config.get_store(self.Session())
|
||||
batch.mode = mode
|
||||
batch.vendor = vendor
|
||||
batch.buyer = self.request.user.employee
|
||||
batch.created_by = self.request.user
|
||||
batch.date_received = localtime(self.rattail_config).date()
|
||||
self.assign_purchase_order(batch, po_form)
|
||||
kwargs = self.get_batch_kwargs(batch, mobile=True)
|
||||
batch = self.handler.make_batch(self.Session(), **kwargs)
|
||||
if self.handler.should_populate(batch):
|
||||
self.handler.populate(batch)
|
||||
return self.redirect(self.get_action_url('view', batch, mobile=True))
|
||||
|
||||
else:
|
||||
phase = 2
|
||||
|
||||
else:
|
||||
raise NotImplementedError("Requested workflow not supported: {}".format(form.validated['workflow']))
|
||||
|
||||
data['form'] = form
|
||||
data['dform'] = form.make_deform_form()
|
||||
data['mode_title'] = self.enum.PURCHASE_BATCH_MODE[mode].capitalize()
|
||||
data['phase'] = phase
|
||||
|
||||
if phase == 1:
|
||||
data['vendor_use_autocomplete'] = self.rattail_config.getbool(
|
||||
'rattail', 'vendor.use_autocomplete', default=True)
|
||||
if not data['vendor_use_autocomplete']:
|
||||
vendors = self.Session.query(model.Vendor)\
|
||||
.order_by(model.Vendor.name)
|
||||
options = [(tags.Option(vendor.name, vendor.uuid))
|
||||
for vendor in vendors]
|
||||
options.insert(0, tags.Option("(please choose)", ''))
|
||||
data['vendor_options'] = options
|
||||
|
||||
elif phase == 2:
|
||||
purchases = self.eligible_purchases(vendor.uuid, mode=mode)
|
||||
data['purchases'] = [(p['key'], p['display']) for p in purchases['purchases']]
|
||||
data['purchase_order_fieldname'] = self.purchase_order_fieldname
|
||||
|
||||
return self.render_to_response('create', data, mobile=True)
|
||||
|
||||
def make_mobile_receiving_from_po_schema(self):
|
||||
schema = colander.MappingSchema()
|
||||
schema.add(colander.SchemaNode(colander.String(),
|
||||
name=self.purchase_order_fieldname,
|
||||
validator=self.validate_purchase))
|
||||
return schema.bind(session=self.Session())
|
||||
|
||||
@staticmethod
|
||||
@colander.deferred
|
||||
def validate_purchase(node, kw):
|
||||
|
@ -766,20 +543,6 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
if department:
|
||||
batch.department_uuid = department.uuid
|
||||
|
||||
def configure_mobile_form(self, f):
|
||||
super(ReceivingBatchView, self).configure_mobile_form(f)
|
||||
batch = f.model_instance
|
||||
|
||||
# truck_dump
|
||||
if not self.creating:
|
||||
if not batch.is_truck_dump_parent():
|
||||
f.remove_field('truck_dump')
|
||||
|
||||
# department
|
||||
if not self.creating:
|
||||
if batch.is_truck_dump_parent():
|
||||
f.remove_field('department')
|
||||
|
||||
def configure_row_grid(self, g):
|
||||
super(ReceivingBatchView, self).configure_row_grid(g)
|
||||
g.set_label('department_name', "Department")
|
||||
|
@ -858,7 +621,7 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
if row.product and row.product.is_pack_item():
|
||||
return self.get_row_action_url('transform_unit', row)
|
||||
|
||||
def receive_row(self, mobile=False):
|
||||
def receive_row(self, **kwargs):
|
||||
"""
|
||||
Primary desktop view for row-level receiving.
|
||||
"""
|
||||
|
@ -866,7 +629,6 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
# tries to pave the way for shared logic, i.e. where the latter would
|
||||
# simply invoke this method and return the result. however we're not
|
||||
# there yet...for now it's only tested for desktop
|
||||
self.mobile = mobile
|
||||
self.viewing = True
|
||||
row = self.get_row_instance()
|
||||
batch = row.batch
|
||||
|
@ -890,23 +652,14 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
'quick_receive_all': False,
|
||||
}
|
||||
|
||||
if mobile:
|
||||
context['quick_receive'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive',
|
||||
default=True)
|
||||
if batch.order_quantities_known:
|
||||
context['quick_receive_all'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive_all',
|
||||
default=False)
|
||||
|
||||
schema = ReceiveRowForm().bind(session=self.Session())
|
||||
form = forms.Form(schema=schema, request=self.request)
|
||||
form.cancel_url = self.get_row_action_url('view', row, mobile=mobile)
|
||||
form.cancel_url = self.get_row_action_url('view', row)
|
||||
form.set_widget('mode', forms.widgets.JQuerySelectWidget(values=[(m, m) for m in possible_modes]))
|
||||
form.set_widget('quantity', forms.widgets.CasesUnitsWidget(amount_required=True,
|
||||
one_amount_only=True))
|
||||
form.set_type('expiration_date', 'date_jquery')
|
||||
|
||||
if not mobile:
|
||||
form.remove_field('quick_receive')
|
||||
form.remove_field('quick_receive')
|
||||
|
||||
if form.validate(newstyle=True):
|
||||
|
||||
|
@ -921,20 +674,17 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
# whether or not it was 'CS' since the unit_uom can vary
|
||||
# TODO: should this be done for desktop too somehow?
|
||||
sticky_case = None
|
||||
if mobile and not form.validated['quick_receive']:
|
||||
cases = form.validated['cases']
|
||||
units = form.validated['units']
|
||||
if cases and not units:
|
||||
sticky_case = True
|
||||
elif units and not cases:
|
||||
sticky_case = False
|
||||
# if mobile and not form.validated['quick_receive']:
|
||||
# cases = form.validated['cases']
|
||||
# units = form.validated['units']
|
||||
# if cases and not units:
|
||||
# sticky_case = True
|
||||
# elif units and not cases:
|
||||
# sticky_case = False
|
||||
if sticky_case is not None:
|
||||
self.request.session['tailbone.mobile.receiving.sticky_uom_is_case'] = sticky_case
|
||||
|
||||
if mobile:
|
||||
return self.redirect(self.get_action_url('view', batch, mobile=True))
|
||||
else:
|
||||
return self.redirect(self.get_row_action_url('view', row))
|
||||
return self.redirect(self.get_row_action_url('view', row))
|
||||
|
||||
# unit_uom can vary by product
|
||||
context['unit_uom'] = 'LB' if row.product and row.product.weighed else 'EA'
|
||||
|
@ -968,9 +718,9 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
# effective uom can vary in a few ways...the basic default is 'CS' if
|
||||
# self.default_uom_is_case is true, otherwise whatever unit_uom is.
|
||||
sticky_case = None
|
||||
if mobile:
|
||||
# TODO: should do this for desktop also, but rename the session variable
|
||||
sticky_case = self.request.session.get('tailbone.mobile.receiving.sticky_uom_is_case')
|
||||
# if mobile:
|
||||
# # TODO: should do this for desktop also, but rename the session variable
|
||||
# sticky_case = self.request.session.get('tailbone.mobile.receiving.sticky_uom_is_case')
|
||||
if sticky_case is None:
|
||||
context['uom'] = 'CS' if self.default_uom_is_case else context['unit_uom']
|
||||
elif sticky_case:
|
||||
|
@ -980,37 +730,37 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
if context['uom'] == 'CS' and row.units_ordered and not row.cases_ordered:
|
||||
context['uom'] = context['unit_uom']
|
||||
|
||||
# TODO: should do this for desktop in addition to mobile?
|
||||
if mobile and batch.order_quantities_known and not row.cases_ordered and not row.units_ordered:
|
||||
warn = True
|
||||
if batch.is_truck_dump_parent() and row.product:
|
||||
uuids = [child.uuid for child in batch.truck_dump_children]
|
||||
if uuids:
|
||||
count = self.Session.query(model.PurchaseBatchRow)\
|
||||
.filter(model.PurchaseBatchRow.batch_uuid.in_(uuids))\
|
||||
.filter(model.PurchaseBatchRow.product == row.product)\
|
||||
.count()
|
||||
if count:
|
||||
warn = False
|
||||
if warn:
|
||||
self.request.session.flash("This item was NOT on the original purchase order.", 'receiving-warning')
|
||||
# # TODO: should do this for desktop in addition to mobile?
|
||||
# if mobile and batch.order_quantities_known and not row.cases_ordered and not row.units_ordered:
|
||||
# warn = True
|
||||
# if batch.is_truck_dump_parent() and row.product:
|
||||
# uuids = [child.uuid for child in batch.truck_dump_children]
|
||||
# if uuids:
|
||||
# count = self.Session.query(model.PurchaseBatchRow)\
|
||||
# .filter(model.PurchaseBatchRow.batch_uuid.in_(uuids))\
|
||||
# .filter(model.PurchaseBatchRow.product == row.product)\
|
||||
# .count()
|
||||
# if count:
|
||||
# warn = False
|
||||
# if warn:
|
||||
# self.request.session.flash("This item was NOT on the original purchase order.", 'receiving-warning')
|
||||
|
||||
# TODO: should do this for desktop in addition to mobile?
|
||||
if mobile:
|
||||
# maybe alert user if they've already received some of this product
|
||||
alert_received = self.rattail_config.getbool('tailbone', 'receiving.alert_already_received',
|
||||
default=False)
|
||||
if alert_received:
|
||||
if self.handler.get_units_confirmed(row):
|
||||
msg = "You have already received some of this product; last update was {}.".format(
|
||||
humanize.naturaltime(make_utc() - row.modified))
|
||||
self.request.session.flash(msg, 'receiving-warning')
|
||||
# # TODO: should do this for desktop in addition to mobile?
|
||||
# if mobile:
|
||||
# # maybe alert user if they've already received some of this product
|
||||
# alert_received = self.rattail_config.getbool('tailbone', 'receiving.alert_already_received',
|
||||
# default=False)
|
||||
# if alert_received:
|
||||
# if self.handler.get_units_confirmed(row):
|
||||
# msg = "You have already received some of this product; last update was {}.".format(
|
||||
# humanize.naturaltime(make_utc() - row.modified))
|
||||
# self.request.session.flash(msg, 'receiving-warning')
|
||||
|
||||
context['form'] = form
|
||||
context['dform'] = form.make_deform_form()
|
||||
context['parent_url'] = self.get_action_url('view', batch, mobile=mobile)
|
||||
context['parent_url'] = self.get_action_url('view', batch)
|
||||
context['parent_title'] = self.get_instance_title(batch)
|
||||
return self.render_to_response('receive_row', context, mobile=mobile)
|
||||
return self.render_to_response('receive_row', context)
|
||||
|
||||
def declare_credit(self):
|
||||
"""
|
||||
|
@ -1418,8 +1168,8 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
self.Session.flush()
|
||||
return row
|
||||
|
||||
def redirect_after_edit_row(self, row, mobile=False):
|
||||
return self.redirect(self.get_row_action_url('view', row, mobile=mobile))
|
||||
def redirect_after_edit_row(self, row, **kwargs):
|
||||
return self.redirect(self.get_row_action_url('view', row))
|
||||
|
||||
def update_row_cost(self):
|
||||
"""
|
||||
|
@ -1463,287 +1213,16 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
},
|
||||
}
|
||||
|
||||
def render_mobile_row_listitem(self, row, i):
|
||||
key = self.render_product_key_value(row)
|
||||
description = row.product.full_description if row.product else row.description
|
||||
return "({}) {}".format(key, description)
|
||||
|
||||
def make_mobile_row_grid_kwargs(self, **kwargs):
|
||||
kwargs = super(ReceivingBatchView, self).make_mobile_row_grid_kwargs(**kwargs)
|
||||
|
||||
# use custom `receive_row` instead of `view_row`
|
||||
# TODO: should still use `view_row` in some cases? e.g. executed batch
|
||||
kwargs['url'] = lambda obj: self.get_row_action_url('receive', obj, mobile=True)
|
||||
|
||||
return kwargs
|
||||
|
||||
def save_quick_row_form(self, form):
|
||||
batch = self.get_instance()
|
||||
entry = form.validated['quick_entry']
|
||||
row = self.handler.quick_entry(self.Session(), batch, entry)
|
||||
return row
|
||||
|
||||
def redirect_after_quick_row(self, row, mobile=False):
|
||||
if mobile:
|
||||
return self.redirect(self.get_row_action_url('receive', row, mobile=mobile))
|
||||
return super(ReceivingBatchView, self).redirect_after_quick_row(row, mobile=mobile)
|
||||
|
||||
def get_row_image_url(self, row):
|
||||
if self.rattail_config.getbool('rattail.batch', 'purchase.mobile_images', default=True):
|
||||
return pod.get_image_url(self.rattail_config, row.upc)
|
||||
|
||||
def get_mobile_data(self, session=None):
|
||||
query = super(ReceivingBatchView, self).get_mobile_data(session=session)
|
||||
|
||||
# do not expose truck dump child batches on mobile
|
||||
# TODO: is there any case where we *would* want to?
|
||||
query = query.filter(model.PurchaseBatch.truck_dump_batch == None)
|
||||
|
||||
return query
|
||||
|
||||
def mobile_view_row(self):
|
||||
"""
|
||||
Mobile view for receiving batch row items. Note that this also handles
|
||||
updating a row.
|
||||
"""
|
||||
self.mobile = True
|
||||
self.viewing = True
|
||||
row = self.get_row_instance()
|
||||
batch = row.batch
|
||||
permission_prefix = self.get_permission_prefix()
|
||||
form = self.make_mobile_row_form(row)
|
||||
context = {
|
||||
'row': row,
|
||||
'batch': batch,
|
||||
'parent_instance': batch,
|
||||
'instance': row,
|
||||
'instance_title': self.get_row_instance_title(row),
|
||||
'parent_model_title': self.get_model_title(),
|
||||
'product_image_url': self.get_row_image_url(row),
|
||||
'form': form,
|
||||
'allow_expired': self.handler.allow_expired_credits(),
|
||||
'allow_cases': self.handler.allow_cases(),
|
||||
'quick_receive': False,
|
||||
'quick_receive_all': False,
|
||||
}
|
||||
|
||||
context['quick_receive'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive',
|
||||
default=True)
|
||||
if batch.order_quantities_known:
|
||||
context['quick_receive_all'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive_all',
|
||||
default=False)
|
||||
|
||||
if self.request.has_perm('{}.create_row'.format(permission_prefix)):
|
||||
schema = MobileReceivingForm().bind(session=self.Session())
|
||||
update_form = forms.Form(schema=schema, request=self.request)
|
||||
# TODO: this seems hacky, but avoids "complex" date value parsing
|
||||
update_form.set_widget('expiration_date', dfwidget.TextInputWidget())
|
||||
if update_form.validate(newstyle=True):
|
||||
row = self.Session.query(model.PurchaseBatchRow).get(update_form.validated['row'])
|
||||
mode = update_form.validated['mode']
|
||||
cases = update_form.validated['cases']
|
||||
units = update_form.validated['units']
|
||||
|
||||
# handler takes care of the row receiving logic for us
|
||||
kwargs = dict(update_form.validated)
|
||||
del kwargs['row']
|
||||
self.handler.receive_row(row, **kwargs)
|
||||
|
||||
# keep track of last-used uom, although we just track
|
||||
# whether or not it was 'CS' since the unit_uom can vary
|
||||
sticky_case = None
|
||||
if not update_form.validated['quick_receive']:
|
||||
if cases and not units:
|
||||
sticky_case = True
|
||||
elif units and not cases:
|
||||
sticky_case = False
|
||||
if sticky_case is not None:
|
||||
self.request.session['tailbone.mobile.receiving.sticky_uom_is_case'] = sticky_case
|
||||
|
||||
return self.redirect(self.get_action_url('view', batch, mobile=True))
|
||||
|
||||
# unit_uom can vary by product
|
||||
context['unit_uom'] = 'LB' if row.product and row.product.weighed else 'EA'
|
||||
|
||||
if context['quick_receive'] and context['quick_receive_all']:
|
||||
if context['allow_cases']:
|
||||
context['quick_receive_uom'] = 'CS'
|
||||
raise NotImplementedError("TODO: add CS support for quick_receive_all")
|
||||
else:
|
||||
context['quick_receive_uom'] = context['unit_uom']
|
||||
accounted_for = self.handler.get_units_accounted_for(row)
|
||||
remainder = self.handler.get_units_ordered(row) - accounted_for
|
||||
|
||||
if accounted_for:
|
||||
# some product accounted for; button should receive "remainder" only
|
||||
if remainder:
|
||||
remainder = pretty_quantity(remainder)
|
||||
context['quick_receive_quantity'] = remainder
|
||||
context['quick_receive_text'] = "Receive Remainder ({} {})".format(remainder, context['unit_uom'])
|
||||
else:
|
||||
# unless there is no remainder, in which case disable it
|
||||
context['quick_receive'] = False
|
||||
|
||||
else: # nothing yet accounted for, button should receive "all"
|
||||
if not remainder:
|
||||
raise ValueError("why is remainder empty?")
|
||||
remainder = pretty_quantity(remainder)
|
||||
context['quick_receive_quantity'] = remainder
|
||||
context['quick_receive_text'] = "Receive ALL ({} {})".format(remainder, context['unit_uom'])
|
||||
|
||||
# effective uom can vary in a few ways...the basic default is 'CS' if
|
||||
# self.default_uom_is_case is true, otherwise whatever unit_uom is.
|
||||
sticky_case = self.request.session.get('tailbone.mobile.receiving.sticky_uom_is_case')
|
||||
if sticky_case is None:
|
||||
context['uom'] = 'CS' if self.default_uom_is_case else context['unit_uom']
|
||||
elif sticky_case:
|
||||
context['uom'] = 'CS'
|
||||
else:
|
||||
context['uom'] = context['unit_uom']
|
||||
if context['uom'] == 'CS' and row.units_ordered and not row.cases_ordered:
|
||||
context['uom'] = context['unit_uom']
|
||||
|
||||
if batch.order_quantities_known and not row.cases_ordered and not row.units_ordered:
|
||||
warn = True
|
||||
if batch.is_truck_dump_parent() and row.product:
|
||||
uuids = [child.uuid for child in batch.truck_dump_children]
|
||||
if uuids:
|
||||
count = self.Session.query(model.PurchaseBatchRow)\
|
||||
.filter(model.PurchaseBatchRow.batch_uuid.in_(uuids))\
|
||||
.filter(model.PurchaseBatchRow.product == row.product)\
|
||||
.count()
|
||||
if count:
|
||||
warn = False
|
||||
if warn:
|
||||
self.request.session.flash("This item was NOT on the original purchase order.", 'receiving-warning')
|
||||
return self.render_to_response('view_row', context, mobile=True)
|
||||
|
||||
def mobile_receive_row(self):
|
||||
"""
|
||||
Mobile view for row-level receiving.
|
||||
"""
|
||||
self.mobile = True
|
||||
self.viewing = True
|
||||
row = self.get_row_instance()
|
||||
batch = row.batch
|
||||
permission_prefix = self.get_permission_prefix()
|
||||
form = self.make_mobile_row_form(row)
|
||||
context = {
|
||||
'row': row,
|
||||
'batch': batch,
|
||||
'parent_instance': batch,
|
||||
'instance': row,
|
||||
'instance_title': self.get_row_instance_title(row),
|
||||
'parent_model_title': self.get_model_title(),
|
||||
'product_image_url': self.get_row_image_url(row),
|
||||
'form': form,
|
||||
'allow_expired': self.handler.allow_expired_credits(),
|
||||
'allow_cases': self.handler.allow_cases(),
|
||||
'quick_receive': False,
|
||||
'quick_receive_all': False,
|
||||
}
|
||||
|
||||
context['quick_receive'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive',
|
||||
default=True)
|
||||
if batch.order_quantities_known:
|
||||
context['quick_receive_all'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive_all',
|
||||
default=False)
|
||||
|
||||
if self.request.has_perm('{}.create_row'.format(permission_prefix)):
|
||||
schema = MobileReceivingForm().bind(session=self.Session())
|
||||
update_form = forms.Form(schema=schema, request=self.request)
|
||||
# TODO: this seems hacky, but avoids "complex" date value parsing
|
||||
update_form.set_widget('expiration_date', dfwidget.TextInputWidget())
|
||||
if update_form.validate(newstyle=True):
|
||||
row = self.Session.query(model.PurchaseBatchRow).get(update_form.validated['row'])
|
||||
mode = update_form.validated['mode']
|
||||
cases = update_form.validated['cases']
|
||||
units = update_form.validated['units']
|
||||
|
||||
# handler takes care of the row receiving logic for us
|
||||
kwargs = dict(update_form.validated)
|
||||
del kwargs['row']
|
||||
self.handler.receive_row(row, **kwargs)
|
||||
|
||||
# keep track of last-used uom, although we just track
|
||||
# whether or not it was 'CS' since the unit_uom can vary
|
||||
sticky_case = None
|
||||
if not update_form.validated['quick_receive']:
|
||||
if cases and not units:
|
||||
sticky_case = True
|
||||
elif units and not cases:
|
||||
sticky_case = False
|
||||
if sticky_case is not None:
|
||||
self.request.session['tailbone.mobile.receiving.sticky_uom_is_case'] = sticky_case
|
||||
|
||||
return self.redirect(self.get_action_url('view', batch, mobile=True))
|
||||
|
||||
# unit_uom can vary by product
|
||||
context['unit_uom'] = 'LB' if row.product and row.product.weighed else 'EA'
|
||||
|
||||
if context['quick_receive'] and context['quick_receive_all']:
|
||||
if context['allow_cases']:
|
||||
context['quick_receive_uom'] = 'CS'
|
||||
raise NotImplementedError("TODO: add CS support for quick_receive_all")
|
||||
else:
|
||||
context['quick_receive_uom'] = context['unit_uom']
|
||||
accounted_for = self.handler.get_units_accounted_for(row)
|
||||
remainder = self.handler.get_units_ordered(row) - accounted_for
|
||||
|
||||
if accounted_for:
|
||||
# some product accounted for; button should receive "remainder" only
|
||||
if remainder:
|
||||
remainder = pretty_quantity(remainder)
|
||||
context['quick_receive_quantity'] = remainder
|
||||
context['quick_receive_text'] = "Receive Remainder ({} {})".format(remainder, context['unit_uom'])
|
||||
else:
|
||||
# unless there is no remainder, in which case disable it
|
||||
context['quick_receive'] = False
|
||||
|
||||
else: # nothing yet accounted for, button should receive "all"
|
||||
if not remainder:
|
||||
raise ValueError("why is remainder empty?")
|
||||
remainder = pretty_quantity(remainder)
|
||||
context['quick_receive_quantity'] = remainder
|
||||
context['quick_receive_text'] = "Receive ALL ({} {})".format(remainder, context['unit_uom'])
|
||||
|
||||
# effective uom can vary in a few ways...the basic default is 'CS' if
|
||||
# self.default_uom_is_case is true, otherwise whatever unit_uom is.
|
||||
sticky_case = self.request.session.get('tailbone.mobile.receiving.sticky_uom_is_case')
|
||||
if sticky_case is None:
|
||||
context['uom'] = 'CS' if self.default_uom_is_case else context['unit_uom']
|
||||
elif sticky_case:
|
||||
context['uom'] = 'CS'
|
||||
else:
|
||||
context['uom'] = context['unit_uom']
|
||||
if context['uom'] == 'CS' and row.units_ordered and not row.cases_ordered:
|
||||
context['uom'] = context['unit_uom']
|
||||
|
||||
if batch.order_quantities_known and not row.cases_ordered and not row.units_ordered:
|
||||
warn = True
|
||||
if batch.is_truck_dump_parent() and row.product:
|
||||
uuids = [child.uuid for child in batch.truck_dump_children]
|
||||
if uuids:
|
||||
count = self.Session.query(model.PurchaseBatchRow)\
|
||||
.filter(model.PurchaseBatchRow.batch_uuid.in_(uuids))\
|
||||
.filter(model.PurchaseBatchRow.product == row.product)\
|
||||
.count()
|
||||
if count:
|
||||
warn = False
|
||||
if warn:
|
||||
self.request.session.flash("This item was NOT on the original purchase order.", 'receiving-warning')
|
||||
|
||||
# maybe alert user if they've already received some of this product
|
||||
alert_received = self.rattail_config.getbool('tailbone', 'receiving.alert_already_received',
|
||||
default=False)
|
||||
if alert_received:
|
||||
if self.handler.get_units_confirmed(row):
|
||||
msg = "You have already received some of this product; last update was {}.".format(
|
||||
humanize.naturaltime(make_utc() - row.modified))
|
||||
self.request.session.flash(msg, 'receiving-warning')
|
||||
|
||||
return self.render_to_response('receive_row', context, mobile=True)
|
||||
|
||||
def auto_receive(self):
|
||||
"""
|
||||
View which can "auto-receive" all items in the batch. Meant only as a
|
||||
|
@ -1804,16 +1283,11 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
instance_url_prefix = cls.get_instance_url_prefix()
|
||||
model_key = cls.get_model_key()
|
||||
permission_prefix = cls.get_permission_prefix()
|
||||
legacy_mobile = cls.legacy_mobile_enabled(rattail_config)
|
||||
|
||||
# row-level receiving
|
||||
config.add_route('{}.receive_row'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/receive'.format(url_prefix))
|
||||
config.add_view(cls, attr='receive_row', route_name='{}.receive_row'.format(route_prefix),
|
||||
permission='{}.edit_row'.format(permission_prefix))
|
||||
if legacy_mobile:
|
||||
config.add_route('mobile.{}.receive_row'.format(route_prefix), '/mobile{}/{{uuid}}/rows/{{row_uuid}}/receive'.format(url_prefix))
|
||||
config.add_view(cls, attr='mobile_receive_row', route_name='mobile.{}.receive_row'.format(route_prefix),
|
||||
permission='{}.edit_row'.format(permission_prefix))
|
||||
|
||||
# declare credit for row
|
||||
config.add_route('{}.declare_credit'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/declare-credit'.format(url_prefix))
|
||||
|
@ -1854,40 +1328,6 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
cls._defaults(config)
|
||||
|
||||
|
||||
# TODO: this is a stopgap measure to fix an obvious bug, which exists when the
|
||||
# session is not provided by the view at runtime (i.e. when it was instead
|
||||
# being provided by the type instance, which was created upon app startup).
|
||||
@colander.deferred
|
||||
def valid_vendor(node, kw):
|
||||
session = kw['session']
|
||||
def validate(node, value):
|
||||
vendor = session.query(model.Vendor).get(value)
|
||||
if not vendor:
|
||||
raise colander.Invalid(node, "Vendor not found")
|
||||
return vendor.uuid
|
||||
return validate
|
||||
|
||||
|
||||
class MobileNewReceivingBatch(colander.MappingSchema):
|
||||
|
||||
vendor = colander.SchemaNode(colander.String(),
|
||||
validator=valid_vendor)
|
||||
|
||||
workflow = colander.SchemaNode(colander.String(),
|
||||
validator=colander.OneOf([
|
||||
'from_po',
|
||||
'from_scratch',
|
||||
'truck_dump',
|
||||
]))
|
||||
|
||||
phase = colander.SchemaNode(colander.Int())
|
||||
|
||||
|
||||
class MobileNewReceivingFromPO(colander.MappingSchema):
|
||||
|
||||
purchase = colander.SchemaNode(colander.String())
|
||||
|
||||
|
||||
class ReceiveRowForm(colander.MappingSchema):
|
||||
|
||||
mode = colander.SchemaNode(colander.String(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue