Split "new receiving batch" process into 2 steps: choose, create
so that the form used to create the batch can be made custom per-workflow, and it won't have to think about any other workflows since we just use one form at a time for that
This commit is contained in:
parent
ff2e39f67a
commit
a2b7f882bc
|
@ -93,7 +93,8 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
|
|
||||||
form_fields = [
|
form_fields = [
|
||||||
'id',
|
'id',
|
||||||
'batch_type',
|
'batch_type', # TODO: ideally would get rid of this one
|
||||||
|
'receiving_workflow',
|
||||||
'store',
|
'store',
|
||||||
'vendor',
|
'vendor',
|
||||||
'description',
|
'description',
|
||||||
|
@ -202,6 +203,79 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
def batch_mode(self):
|
def batch_mode(self):
|
||||||
return self.enum.PURCHASE_BATCH_MODE_RECEIVING
|
return self.enum.PURCHASE_BATCH_MODE_RECEIVING
|
||||||
|
|
||||||
|
def create(self, form=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Custom view for creating a new receiving batch. We split the process
|
||||||
|
into two steps, 1) choose and 2) create. This is because the specific
|
||||||
|
form details for creating a batch will depend on which "type" of batch
|
||||||
|
creation is to be done, and it's much easier to keep conditional logic
|
||||||
|
for that in the server instead of client-side etc.
|
||||||
|
"""
|
||||||
|
route_prefix = self.get_route_prefix()
|
||||||
|
workflows = self.handler.supported_receiving_workflows()
|
||||||
|
valid_workflows = [workflow['workflow_key']
|
||||||
|
for workflow in workflows]
|
||||||
|
|
||||||
|
# if user has already identified their desired workflow, then we can
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
# however we do have one more thing to check - the workflow
|
||||||
|
# requested must of course be valid!
|
||||||
|
workflow_key = self.request.matchdict['workflow_key']
|
||||||
|
if workflow_key not in valid_workflows:
|
||||||
|
self.request.session.flash(
|
||||||
|
"Not a supported workflow: {}".format(workflow_key),
|
||||||
|
'error')
|
||||||
|
raise self.redirect(self.request.route_url('{}.create'.format(route_prefix)))
|
||||||
|
|
||||||
|
# okay now do the normal thing, per workflow
|
||||||
|
return super(ReceivingBatchView, self).create(**kwargs)
|
||||||
|
|
||||||
|
# on the other hand, if caller provided a form, that means we are in
|
||||||
|
# the middle of some other custom workflow, e.g. "add child to truck
|
||||||
|
# dump parent" or some such. in which case we also defer to the normal
|
||||||
|
# logic, so as to not interfere with that.
|
||||||
|
if form:
|
||||||
|
return super(ReceivingBatchView, self).create(form=form, **kwargs)
|
||||||
|
|
||||||
|
# okay, at this point we need the user to select a 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 = forms.Form(schema=schema, request=self.request,
|
||||||
|
use_buefy=use_buefy)
|
||||||
|
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()
|
||||||
|
|
||||||
|
# if form validates, that means user has chosen a creation type, so we
|
||||||
|
# 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)
|
||||||
|
raise self.redirect(url)
|
||||||
|
|
||||||
|
context['form'] = form
|
||||||
|
if hasattr(form, 'make_deform_form'):
|
||||||
|
context['dform'] = form.make_deform_form()
|
||||||
|
return self.render_to_response('create', context)
|
||||||
|
|
||||||
def row_deletable(self, row):
|
def row_deletable(self, row):
|
||||||
batch = row.batch
|
batch = row.batch
|
||||||
|
|
||||||
|
@ -243,18 +317,25 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
super(ReceivingBatchView, self).configure_form(f)
|
super(ReceivingBatchView, self).configure_form(f)
|
||||||
batch = f.model_instance
|
batch = f.model_instance
|
||||||
allow_truck_dump = self.handler.allow_truck_dump_receiving()
|
allow_truck_dump = self.handler.allow_truck_dump_receiving()
|
||||||
|
workflow = self.request.matchdict.get('workflow_key')
|
||||||
|
route_prefix = self.get_route_prefix()
|
||||||
|
|
||||||
|
# cancel should take us back to choosing a workflow, when creating
|
||||||
|
if self.creating and workflow:
|
||||||
|
f.cancel_url = self.request.route_url('{}.create'.format(route_prefix))
|
||||||
|
|
||||||
|
# receiving_workflow
|
||||||
|
if self.creating and workflow:
|
||||||
|
f.set_readonly('receiving_workflow')
|
||||||
|
f.set_renderer('receiving_workflow', self.render_receiving_workflow)
|
||||||
|
else:
|
||||||
|
f.remove('receiving_workflow')
|
||||||
|
|
||||||
# batch_type
|
# batch_type
|
||||||
if self.creating:
|
if self.creating:
|
||||||
batch_types = OrderedDict()
|
f.set_widget('batch_type', dfwidget.HiddenWidget())
|
||||||
if self.handler.allow_receiving_from_scratch():
|
f.set_default('batch_type', workflow)
|
||||||
batch_types['from_scratch'] = "From Scratch"
|
f.set_hidden('batch_type')
|
||||||
if self.handler.allow_receiving_from_purchase_order():
|
|
||||||
batch_types['from_po'] = "From PO"
|
|
||||||
if allow_truck_dump:
|
|
||||||
batch_types['truck_dump_children_first'] = "Truck Dump (children FIRST)"
|
|
||||||
batch_types['truck_dump_children_last'] = "Truck Dump (children LAST)"
|
|
||||||
f.set_enum('batch_type', batch_types)
|
|
||||||
else:
|
else:
|
||||||
f.remove_field('batch_type')
|
f.remove_field('batch_type')
|
||||||
|
|
||||||
|
@ -361,6 +442,44 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
# invoice totals
|
# invoice totals
|
||||||
f.set_label('invoice_total', "Invoice Total (Orig.)")
|
f.set_label('invoice_total', "Invoice Total (Orig.)")
|
||||||
f.set_label('invoice_total_calculated', "Invoice Total (Calc.)")
|
f.set_label('invoice_total_calculated', "Invoice Total (Calc.)")
|
||||||
|
if self.creating:
|
||||||
|
f.remove('invoice_total_calculated')
|
||||||
|
|
||||||
|
# receiving_complete
|
||||||
|
if self.creating:
|
||||||
|
f.remove('receiving_complete')
|
||||||
|
|
||||||
|
# now that all fields are setup, we get rid of some, based on workflow
|
||||||
|
if self.creating:
|
||||||
|
|
||||||
|
if workflow == 'from_scratch':
|
||||||
|
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',
|
||||||
|
'invoice_parser_key',
|
||||||
|
'date_ordered',
|
||||||
|
'po_number',
|
||||||
|
'invoice_date',
|
||||||
|
'invoice_number')
|
||||||
|
|
||||||
|
elif workflow == 'truck_dump_children_last':
|
||||||
|
f.remove('truck_dump_batch_uuid',
|
||||||
|
'invoice_file',
|
||||||
|
'invoice_parser_key',
|
||||||
|
'date_ordered',
|
||||||
|
'po_number',
|
||||||
|
'invoice_date',
|
||||||
|
'invoice_number')
|
||||||
|
|
||||||
|
def render_receiving_workflow(self, batch, field):
|
||||||
|
key = self.request.matchdict['workflow_key']
|
||||||
|
info = self.handler.receiving_workflow_info(key)
|
||||||
|
if info:
|
||||||
|
return info['display']
|
||||||
|
|
||||||
def template_kwargs_create(self, **kwargs):
|
def template_kwargs_create(self, **kwargs):
|
||||||
kwargs = super(ReceivingBatchView, self).template_kwargs_create(**kwargs)
|
kwargs = super(ReceivingBatchView, self).template_kwargs_create(**kwargs)
|
||||||
|
@ -488,6 +607,9 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
|
|
||||||
self.configure_form(f)
|
self.configure_form(f)
|
||||||
|
|
||||||
|
# cancel should go back to truck dump parent
|
||||||
|
f.cancel_url = self.get_action_url('view', truck_dump)
|
||||||
|
|
||||||
f.set_fields([
|
f.set_fields([
|
||||||
'batch_type',
|
'batch_type',
|
||||||
'truck_dump_parent',
|
'truck_dump_parent',
|
||||||
|
@ -502,6 +624,7 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
# batch_type
|
# batch_type
|
||||||
f.set_widget('batch_type', forms.widgets.ReadonlyWidget())
|
f.set_widget('batch_type', forms.widgets.ReadonlyWidget())
|
||||||
f.set_default('batch_type', 'truck_dump_child_from_invoice')
|
f.set_default('batch_type', 'truck_dump_child_from_invoice')
|
||||||
|
f.set_hidden('batch_type', False)
|
||||||
|
|
||||||
# truck_dump_batch_uuid
|
# truck_dump_batch_uuid
|
||||||
f.set_readonly('truck_dump_parent')
|
f.set_readonly('truck_dump_parent')
|
||||||
|
@ -1272,6 +1395,13 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
progress.session['success_url'] = success_url
|
progress.session['success_url'] = success_url
|
||||||
progress.session.save()
|
progress.session.save()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def defaults(cls, config):
|
||||||
|
cls._receiving_defaults(config)
|
||||||
|
cls._purchasing_defaults(config)
|
||||||
|
cls._batch_defaults(config)
|
||||||
|
cls._defaults(config)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _receiving_defaults(cls, config):
|
def _receiving_defaults(cls, config):
|
||||||
rattail_config = config.registry.settings.get('rattail_config')
|
rattail_config = config.registry.settings.get('rattail_config')
|
||||||
|
@ -1281,13 +1411,18 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
model_key = cls.get_model_key()
|
model_key = cls.get_model_key()
|
||||||
permission_prefix = cls.get_permission_prefix()
|
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),
|
||||||
|
permission='{}.create'.format(permission_prefix))
|
||||||
|
|
||||||
# row-level receiving
|
# row-level receiving
|
||||||
config.add_route('{}.receive_row'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/receive'.format(url_prefix))
|
config.add_route('{}.receive_row'.format(route_prefix), '{}/rows/{{row_uuid}}/receive'.format(instance_url_prefix))
|
||||||
config.add_view(cls, attr='receive_row', route_name='{}.receive_row'.format(route_prefix),
|
config.add_view(cls, attr='receive_row', route_name='{}.receive_row'.format(route_prefix),
|
||||||
permission='{}.edit_row'.format(permission_prefix))
|
permission='{}.edit_row'.format(permission_prefix))
|
||||||
|
|
||||||
# declare credit for row
|
# declare credit for row
|
||||||
config.add_route('{}.declare_credit'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/declare-credit'.format(url_prefix))
|
config.add_route('{}.declare_credit'.format(route_prefix), '{}/rows/{{row_uuid}}/declare-credit'.format(instance_url_prefix))
|
||||||
config.add_view(cls, attr='declare_credit', route_name='{}.declare_credit'.format(route_prefix),
|
config.add_view(cls, attr='declare_credit', route_name='{}.declare_credit'.format(route_prefix),
|
||||||
permission='{}.edit_row'.format(permission_prefix))
|
permission='{}.edit_row'.format(permission_prefix))
|
||||||
|
|
||||||
|
@ -1298,29 +1433,45 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
renderer='json')
|
renderer='json')
|
||||||
|
|
||||||
# add TD child batch, from invoice file
|
# add TD child batch, from invoice file
|
||||||
config.add_route('{}.add_child_from_invoice'.format(route_prefix), '{}/{{{}}}/add-child-from-invoice'.format(url_prefix, model_key))
|
config.add_route('{}.add_child_from_invoice'.format(route_prefix), '{}/add-child-from-invoice'.format(instance_url_prefix))
|
||||||
config.add_view(cls, attr='add_child_from_invoice', route_name='{}.add_child_from_invoice'.format(route_prefix),
|
config.add_view(cls, attr='add_child_from_invoice', route_name='{}.add_child_from_invoice'.format(route_prefix),
|
||||||
permission='{}.create'.format(permission_prefix))
|
permission='{}.create'.format(permission_prefix))
|
||||||
|
|
||||||
# transform TD parent row from "pack" to "unit" item
|
# transform TD parent row from "pack" to "unit" item
|
||||||
config.add_route('{}.transform_unit_row'.format(route_prefix), '{}/{{{}}}/transform-unit'.format(url_prefix, model_key))
|
config.add_route('{}.transform_unit_row'.format(route_prefix), '{}/transform-unit'.format(instance_url_prefix))
|
||||||
config.add_view(cls, attr='transform_unit_row', route_name='{}.transform_unit_row'.format(route_prefix),
|
config.add_view(cls, attr='transform_unit_row', route_name='{}.transform_unit_row'.format(route_prefix),
|
||||||
permission='{}.edit_row'.format(permission_prefix), renderer='json')
|
permission='{}.edit_row'.format(permission_prefix), renderer='json')
|
||||||
|
|
||||||
# auto-receive all items
|
# auto-receive all items
|
||||||
if not rattail_config.production():
|
if not rattail_config.production():
|
||||||
config.add_route('{}.auto_receive'.format(route_prefix), '{}/{{{}}}/auto-receive'.format(url_prefix, model_key),
|
config.add_route('{}.auto_receive'.format(route_prefix), '{}/auto-receive'.format(instance_url_prefix),
|
||||||
request_method='POST')
|
request_method='POST')
|
||||||
config.add_view(cls, attr='auto_receive', route_name='{}.auto_receive'.format(route_prefix),
|
config.add_view(cls, attr='auto_receive', route_name='{}.auto_receive'.format(route_prefix),
|
||||||
permission='admin')
|
permission='admin')
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@colander.deferred
|
||||||
def defaults(cls, config):
|
def valid_workflow(node, kw):
|
||||||
cls._receiving_defaults(config)
|
"""
|
||||||
cls._purchasing_defaults(config)
|
Deferred validator for ``workflow`` field, for new batches.
|
||||||
cls._batch_defaults(config)
|
"""
|
||||||
cls._defaults(config)
|
valid_workflows = kw['valid_workflows']
|
||||||
|
|
||||||
|
def validate(node, value):
|
||||||
|
# we just need to provide possible values, and let stock validator
|
||||||
|
# handle the rest
|
||||||
|
oneof = colander.OneOf(valid_workflows)
|
||||||
|
return oneof(node, value)
|
||||||
|
|
||||||
|
return validate
|
||||||
|
|
||||||
|
|
||||||
|
class NewBatchType(colander.Schema):
|
||||||
|
"""
|
||||||
|
Schema for choosing which "type" of new receiving batch should be created.
|
||||||
|
"""
|
||||||
|
workflow = colander.SchemaNode(colander.String(),
|
||||||
|
validator=valid_workflow)
|
||||||
|
|
||||||
|
|
||||||
class ReceiveRowForm(colander.MappingSchema):
|
class ReceiveRowForm(colander.MappingSchema):
|
||||||
|
|
Loading…
Reference in a new issue