More tweaks for receiving batch workflows
now first step requires choice of vendor and workflow. supports receiving from PO at least for native use case.
This commit is contained in:
parent
a2b7f882bc
commit
801c56f06e
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2020 Lance Edgar
|
||||
# Copyright © 2010-2021 Lance Edgar
|
||||
#
|
||||
# This file is part of Rattail.
|
||||
#
|
||||
|
@ -126,13 +126,7 @@ class ReceivingBatchViews(APIBatchView):
|
|||
}
|
||||
|
||||
def render_eligible_purchase(self, purchase):
|
||||
if purchase.status == self.enum.PURCHASE_STATUS_ORDERED:
|
||||
date = purchase.date_ordered
|
||||
total = purchase.po_total
|
||||
elif purchase.status == self.enum.PURCHASE_STATUS_RECEIVED:
|
||||
date = purchase.date_received
|
||||
total = purchase.invoice_total
|
||||
return '{} for ${:0,.2f} ({})'.format(date, total or 0, purchase.department or purchase.buyer)
|
||||
return self.handler.render_eligible_purchase(purchase)
|
||||
|
||||
@classmethod
|
||||
def defaults(cls, config):
|
||||
|
|
|
@ -212,7 +212,7 @@
|
|||
|
||||
<section class="modal-card-body">
|
||||
<p class="block has-text-weight-bold">
|
||||
What will actually happen when this batch is executed?
|
||||
What will happen when this batch is executed?
|
||||
</p>
|
||||
<p class="block">
|
||||
${handler.describe_execution(batch) or "TODO: handler does not provide a description for this batch"}
|
||||
|
@ -374,7 +374,7 @@
|
|||
## end 'external_worksheet'
|
||||
% endif
|
||||
|
||||
% if not batch.executed and master.has_perm('execute'):
|
||||
% if execute_enabled and master.has_perm('execute'):
|
||||
|
||||
ThisPageData.showExecutionDialog = false
|
||||
|
||||
|
@ -402,7 +402,7 @@
|
|||
</script>
|
||||
% endif
|
||||
|
||||
% if not batch.executed and master.has_perm('execute'):
|
||||
% if execute_enabled and master.has_perm('execute'):
|
||||
<script type="text/javascript">
|
||||
|
||||
## ExecuteForm
|
||||
|
|
|
@ -292,10 +292,20 @@
|
|||
<div class="object-helper">
|
||||
<h3>Development Tools</h3>
|
||||
<div class="object-helper-content">
|
||||
${h.form(url('{}.auto_receive'.format(route_prefix), uuid=batch.uuid), class_='autodisable')}
|
||||
${h.csrf_token(request)}
|
||||
${h.submit('submit', "Auto-Receive All Items")}
|
||||
${h.end_form()}
|
||||
% if use_buefy:
|
||||
${h.form(url('{}.auto_receive'.format(route_prefix), uuid=batch.uuid), ref='auto_receive_all_form')}
|
||||
${h.csrf_token(request)}
|
||||
<once-button type="is-primary"
|
||||
@click="$refs.auto_receive_all_form.submit()"
|
||||
text="Auto-Receive All Items">
|
||||
</once-button>
|
||||
${h.end_form()}
|
||||
% else:
|
||||
${h.form(url('{}.auto_receive'.format(route_prefix), uuid=batch.uuid), class_='autodisable')}
|
||||
${h.csrf_token(request)}
|
||||
${h.submit('submit', "Auto-Receive All Items")}
|
||||
${h.end_form()}
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
|
|
@ -94,9 +94,9 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
form_fields = [
|
||||
'id',
|
||||
'batch_type', # TODO: ideally would get rid of this one
|
||||
'receiving_workflow',
|
||||
'store',
|
||||
'vendor',
|
||||
'receiving_workflow',
|
||||
'description',
|
||||
'truck_dump',
|
||||
'truck_dump_children_first',
|
||||
|
@ -220,7 +220,7 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
# just farm out to the default logic. we will of course configure our
|
||||
# form differently, based on workflow, but this create() method at
|
||||
# least will not need customization for that.
|
||||
if 'workflow_key' in self.request.matchdict:
|
||||
if self.request.matched_route.name.endswith('create_workflow'):
|
||||
|
||||
# however we do have one more thing to check - the workflow
|
||||
# requested must of course be valid!
|
||||
|
@ -241,25 +241,49 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
if form:
|
||||
return super(ReceivingBatchView, self).create(form=form, **kwargs)
|
||||
|
||||
# okay, at this point we need the user to select a workflow...
|
||||
# okay, at this point we need the user to select a vendor and workflow
|
||||
self.creating = True
|
||||
use_buefy = self.get_use_buefy()
|
||||
context = {}
|
||||
|
||||
# form to accept user choice of workflow
|
||||
schema = NewBatchType().bind(valid_workflows=valid_workflows)
|
||||
# form to accept user choice of vendor/workflow
|
||||
schema = NewReceivingBatch().bind(valid_workflows=valid_workflows)
|
||||
form = forms.Form(schema=schema, request=self.request,
|
||||
use_buefy=use_buefy)
|
||||
|
||||
# configure vendor field
|
||||
use_autocomplete = self.rattail_config.getbool(
|
||||
'rattail', 'vendor.use_autocomplete', default=True)
|
||||
if use_autocomplete:
|
||||
vendor_display = ""
|
||||
if self.request.method == 'POST':
|
||||
if self.request.POST.get('vendor'):
|
||||
vendor = self.Session.query(model.Vendor).get(self.request.POST['vendor'])
|
||||
if vendor:
|
||||
vendor_display = six.text_type(vendor)
|
||||
vendors_url = self.request.route_url('vendors.autocomplete')
|
||||
form.set_widget('vendor', forms.widgets.JQueryAutocompleteWidget(
|
||||
field_display=vendor_display, service_url=vendors_url))
|
||||
else:
|
||||
vendors = self.Session.query(model.Vendor)\
|
||||
.order_by(model.Vendor.id)
|
||||
vendor_values = [(vendor.uuid, "({}) {}".format(vendor.id, vendor.name))
|
||||
for vendor in vendors]
|
||||
if use_buefy:
|
||||
form.set_widget('vendor', dfwidget.SelectWidget(values=vendor_values))
|
||||
else:
|
||||
form.set_widget('vendor', forms.widgets.JQuerySelectWidget(values=vendor_values))
|
||||
|
||||
# configure workflow field
|
||||
values = [(workflow['workflow_key'], workflow['display'])
|
||||
for workflow in workflows]
|
||||
if use_buefy:
|
||||
# if workflows:
|
||||
# form.set_default('workflow', workflows[0]['workflow_key'])
|
||||
form.set_widget('workflow',
|
||||
dfwidget.SelectWidget(values=values))
|
||||
else:
|
||||
form.set_widget('workflow',
|
||||
forms.widgets.JQuerySelectWidget(values=values))
|
||||
|
||||
form.submit_label = "Continue"
|
||||
form.cancel_url = self.get_index_url()
|
||||
|
||||
|
@ -267,8 +291,10 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
# just redirect to the appropriate "new batch of type X" page
|
||||
if form.validate(newstyle=True):
|
||||
workflow_key = form.validated['workflow']
|
||||
url = self.request.route_url('{}.create_type'.format(route_prefix),
|
||||
workflow_key=workflow_key)
|
||||
vendor_uuid = form.validated['vendor']
|
||||
url = self.request.route_url('{}.create_workflow'.format(route_prefix),
|
||||
workflow_key=workflow_key,
|
||||
vendor_uuid=vendor_uuid)
|
||||
raise self.redirect(url)
|
||||
|
||||
context['form'] = form
|
||||
|
@ -315,13 +341,24 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
|
||||
def configure_form(self, f):
|
||||
super(ReceivingBatchView, self).configure_form(f)
|
||||
model = self.model
|
||||
batch = f.model_instance
|
||||
allow_truck_dump = self.handler.allow_truck_dump_receiving()
|
||||
workflow = self.request.matchdict.get('workflow_key')
|
||||
route_prefix = self.get_route_prefix()
|
||||
use_buefy = self.get_use_buefy()
|
||||
|
||||
# cancel should take us back to choosing a workflow, when creating
|
||||
# tweak some things if we are in "step 2" of creating new batch
|
||||
if self.creating and workflow:
|
||||
|
||||
# display vendor but do not allow changing
|
||||
vendor = self.Session.query(model.Vendor).get(
|
||||
self.request.matchdict['vendor_uuid'])
|
||||
assert vendor
|
||||
f.set_readonly('vendor_uuid')
|
||||
f.set_default('vendor_uuid', six.text_type(vendor))
|
||||
|
||||
# cancel should take us back to choosing a workflow
|
||||
f.cancel_url = self.request.route_url('{}.create'.format(route_prefix))
|
||||
|
||||
# receiving_workflow
|
||||
|
@ -415,7 +452,10 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
parsers = sorted(iter_invoice_parsers(), key=lambda p: p.display)
|
||||
parser_values = [(p.key, p.display) for p in parsers]
|
||||
parser_values.insert(0, ('', "(please choose)"))
|
||||
f.set_widget('invoice_parser_key', forms.widgets.JQuerySelectWidget(values=parser_values))
|
||||
if use_buefy:
|
||||
f.set_widget('invoice_parser_key', dfwidget.SelectWidget(values=parser_values))
|
||||
else:
|
||||
f.set_widget('invoice_parser_key', forms.widgets.JQuerySelectWidget(values=parser_values))
|
||||
else:
|
||||
f.remove_field('invoice_parser_key')
|
||||
|
||||
|
@ -428,7 +468,18 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
f.set_widget('store_uuid', dfwidget.HiddenWidget())
|
||||
|
||||
# purchase
|
||||
if self.creating:
|
||||
if (self.creating and workflow == 'from_po'
|
||||
and self.purchase_order_fieldname == 'purchase'):
|
||||
if use_buefy:
|
||||
f.replace('purchase', 'purchase_uuid')
|
||||
purchases = self.handler.get_eligible_purchases(
|
||||
vendor, self.enum.PURCHASE_BATCH_MODE_RECEIVING)
|
||||
values = [(p.uuid, self.handler.render_eligible_purchase(p))
|
||||
for p in purchases]
|
||||
f.set_widget('purchase_uuid', dfwidget.SelectWidget(values=values))
|
||||
f.set_label('purchase_uuid', "Purchase Order")
|
||||
f.set_required('purchase_uuid')
|
||||
else:
|
||||
f.remove_field('purchase')
|
||||
|
||||
# department
|
||||
|
@ -449,14 +500,24 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
if self.creating:
|
||||
f.remove('receiving_complete')
|
||||
|
||||
# now that all fields are setup, we get rid of some, based on workflow
|
||||
if self.creating:
|
||||
# now that all fields are setup, some final tweaks based on workflow
|
||||
if self.creating and workflow:
|
||||
|
||||
if workflow == 'from_scratch':
|
||||
f.remove('truck_dump_batch_uuid',
|
||||
'invoice_file',
|
||||
'invoice_parser_key')
|
||||
|
||||
elif workflow == 'from_invoice':
|
||||
f.remove('truck_dump_batch_uuid')
|
||||
f.set_required('invoice_file')
|
||||
f.set_required('invoice_parser_key')
|
||||
|
||||
elif workflow == 'from_po':
|
||||
f.remove('truck_dump_batch_uuid',
|
||||
'invoice_file',
|
||||
'invoice_parser_key')
|
||||
|
||||
elif workflow == 'truck_dump_children_first':
|
||||
f.remove('truck_dump_batch_uuid',
|
||||
'invoice_file',
|
||||
|
@ -497,9 +558,20 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
def get_batch_kwargs(self, batch, **kwargs):
|
||||
kwargs = super(ReceivingBatchView, self).get_batch_kwargs(batch, **kwargs)
|
||||
batch_type = self.request.POST['batch_type']
|
||||
|
||||
# must pull vendor from URL if it was not in form data
|
||||
if 'vendor_uuid' not in kwargs and 'vendor' not in kwargs:
|
||||
if 'vendor_uuid' in self.request.matchdict:
|
||||
kwargs['vendor_uuid'] = self.request.matchdict['vendor_uuid']
|
||||
|
||||
if batch_type == 'from_scratch':
|
||||
kwargs.pop('truck_dump_batch', None)
|
||||
kwargs.pop('truck_dump_batch_uuid', None)
|
||||
elif batch_type == 'from_invoice':
|
||||
pass
|
||||
elif batch_type == 'from_po':
|
||||
# TODO: how to best handle this field? this doesn't seem flexible
|
||||
kwargs['purchase_key'] = batch.purchase_uuid
|
||||
elif batch_type == 'truck_dump_children_first':
|
||||
kwargs['truck_dump'] = True
|
||||
kwargs['truck_dump_children_first'] = True
|
||||
|
@ -750,6 +822,7 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
# simply invoke this method and return the result. however we're not
|
||||
# there yet...for now it's only tested for desktop
|
||||
self.viewing = True
|
||||
use_buefy = self.get_use_buefy()
|
||||
row = self.get_row_instance()
|
||||
batch = row.batch
|
||||
permission_prefix = self.get_permission_prefix()
|
||||
|
@ -773,9 +846,13 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
}
|
||||
|
||||
schema = ReceiveRowForm().bind(session=self.Session())
|
||||
form = forms.Form(schema=schema, request=self.request)
|
||||
form = forms.Form(schema=schema, request=self.request, use_buefy=use_buefy)
|
||||
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]))
|
||||
mode_values = [(mode, mode) for mode in possible_modes]
|
||||
if use_buefy:
|
||||
form.set_widget('mode', dfwidget.SelectWidget(values=mode_values))
|
||||
else:
|
||||
form.set_widget('mode', forms.widgets.JQuerySelectWidget(values=mode_values))
|
||||
form.set_widget('quantity', forms.widgets.CasesUnitsWidget(amount_required=True,
|
||||
one_amount_only=True))
|
||||
form.set_type('expiration_date', 'date_jquery')
|
||||
|
@ -1412,8 +1489,8 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
permission_prefix = cls.get_permission_prefix()
|
||||
|
||||
# new receiving batch using workflow X
|
||||
config.add_route('{}.create_type'.format(route_prefix), '{}/new/{{workflow_key}}'.format(url_prefix))
|
||||
config.add_view(cls, attr='create', route_name='{}.create_type'.format(route_prefix),
|
||||
config.add_route('{}.create_workflow'.format(route_prefix), '{}/new/{{workflow_key}}/{{vendor_uuid}}'.format(url_prefix))
|
||||
config.add_view(cls, attr='create', route_name='{}.create_workflow'.format(route_prefix),
|
||||
permission='{}.create'.format(permission_prefix))
|
||||
|
||||
# row-level receiving
|
||||
|
@ -1466,10 +1543,13 @@ def valid_workflow(node, kw):
|
|||
return validate
|
||||
|
||||
|
||||
class NewBatchType(colander.Schema):
|
||||
class NewReceivingBatch(colander.Schema):
|
||||
"""
|
||||
Schema for choosing which "type" of new receiving batch should be created.
|
||||
"""
|
||||
vendor = colander.SchemaNode(colander.String(),
|
||||
label="Vendor")
|
||||
|
||||
workflow = colander.SchemaNode(colander.String(),
|
||||
validator=valid_workflow)
|
||||
|
||||
|
|
Loading…
Reference in a new issue