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
					
				
					 1 changed files with 172 additions and 21 deletions
				
			
		|  | @ -93,7 +93,8 @@ class ReceivingBatchView(PurchasingBatchView): | |||
| 
 | ||||
|     form_fields = [ | ||||
|         'id', | ||||
|         'batch_type', | ||||
|         'batch_type',           # TODO: ideally would get rid of this one | ||||
|         'receiving_workflow', | ||||
|         'store', | ||||
|         'vendor', | ||||
|         'description', | ||||
|  | @ -202,6 +203,79 @@ class ReceivingBatchView(PurchasingBatchView): | |||
|     def batch_mode(self): | ||||
|         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): | ||||
|         batch = row.batch | ||||
| 
 | ||||
|  | @ -243,18 +317,25 @@ class ReceivingBatchView(PurchasingBatchView): | |||
|         super(ReceivingBatchView, self).configure_form(f) | ||||
|         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() | ||||
| 
 | ||||
|         # 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 | ||||
|         if self.creating: | ||||
|             batch_types = OrderedDict() | ||||
|             if self.handler.allow_receiving_from_scratch(): | ||||
|                 batch_types['from_scratch'] = "From Scratch" | ||||
|             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) | ||||
|             f.set_widget('batch_type', dfwidget.HiddenWidget()) | ||||
|             f.set_default('batch_type', workflow) | ||||
|             f.set_hidden('batch_type') | ||||
|         else: | ||||
|             f.remove_field('batch_type') | ||||
| 
 | ||||
|  | @ -361,6 +442,44 @@ class ReceivingBatchView(PurchasingBatchView): | |||
|         # invoice totals | ||||
|         f.set_label('invoice_total', "Invoice Total (Orig.)") | ||||
|         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): | ||||
|         kwargs = super(ReceivingBatchView, self).template_kwargs_create(**kwargs) | ||||
|  | @ -488,6 +607,9 @@ class ReceivingBatchView(PurchasingBatchView): | |||
| 
 | ||||
|         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([ | ||||
|             'batch_type', | ||||
|             'truck_dump_parent', | ||||
|  | @ -502,6 +624,7 @@ class ReceivingBatchView(PurchasingBatchView): | |||
|         # batch_type | ||||
|         f.set_widget('batch_type', forms.widgets.ReadonlyWidget()) | ||||
|         f.set_default('batch_type', 'truck_dump_child_from_invoice') | ||||
|         f.set_hidden('batch_type', False) | ||||
| 
 | ||||
|         # truck_dump_batch_uuid | ||||
|         f.set_readonly('truck_dump_parent') | ||||
|  | @ -1272,6 +1395,13 @@ class ReceivingBatchView(PurchasingBatchView): | |||
|                 progress.session['success_url'] = success_url | ||||
|                 progress.session.save() | ||||
| 
 | ||||
|     @classmethod | ||||
|     def defaults(cls, config): | ||||
|         cls._receiving_defaults(config) | ||||
|         cls._purchasing_defaults(config) | ||||
|         cls._batch_defaults(config) | ||||
|         cls._defaults(config) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def _receiving_defaults(cls, config): | ||||
|         rattail_config = config.registry.settings.get('rattail_config') | ||||
|  | @ -1281,13 +1411,18 @@ class ReceivingBatchView(PurchasingBatchView): | |||
|         model_key = cls.get_model_key() | ||||
|         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 | ||||
|         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), | ||||
|                         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)) | ||||
|         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), | ||||
|                         permission='{}.edit_row'.format(permission_prefix)) | ||||
| 
 | ||||
|  | @ -1298,29 +1433,45 @@ class ReceivingBatchView(PurchasingBatchView): | |||
|                         renderer='json') | ||||
| 
 | ||||
|         # 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), | ||||
|                         permission='{}.create'.format(permission_prefix)) | ||||
| 
 | ||||
|         # 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), | ||||
|                         permission='{}.edit_row'.format(permission_prefix), renderer='json') | ||||
| 
 | ||||
|         # auto-receive all items | ||||
|         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') | ||||
|             config.add_view(cls, attr='auto_receive', route_name='{}.auto_receive'.format(route_prefix), | ||||
|                             permission='admin') | ||||
| 
 | ||||
| 
 | ||||
|     @classmethod | ||||
|     def defaults(cls, config): | ||||
|         cls._receiving_defaults(config) | ||||
|         cls._purchasing_defaults(config) | ||||
|         cls._batch_defaults(config) | ||||
|         cls._defaults(config) | ||||
| @colander.deferred | ||||
| def valid_workflow(node, kw): | ||||
|     """ | ||||
|     Deferred validator for ``workflow`` field, for new batches. | ||||
|     """ | ||||
|     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): | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar