Overhaul the Receiving Form to account for "product not found" etc.
Also shows ordered/received/etc. quantities
This commit is contained in:
		
							parent
							
								
									acbb3d289c
								
							
						
					
					
						commit
						ed252c6465
					
				
					 2 changed files with 245 additions and 53 deletions
				
			
		|  | @ -33,10 +33,11 @@ from sqlalchemy import orm | |||
| 
 | ||||
| from rattail import pod | ||||
| from rattail.db import model, api | ||||
| from rattail.db.util import make_full_description | ||||
| from rattail.gpc import GPC | ||||
| from rattail.time import localtime | ||||
| from rattail.core import Object | ||||
| from rattail.util import OrderedDict | ||||
| from rattail.util import OrderedDict, pretty_quantity | ||||
| 
 | ||||
| import formalchemy as fa | ||||
| import formencode as fe | ||||
|  | @ -55,8 +56,13 @@ class ReceivingForm(forms.Schema): | |||
|     filter_extra_fields = True | ||||
|     mode = fe.validators.OneOf(['received', 'damaged', 'expired', 'mispick']) | ||||
|     product = forms.validators.ValidProduct() | ||||
|     cases = fe.validators.Int() | ||||
|     units = fe.validators.Int() | ||||
|     upc = forms.validators.ValidGPC() | ||||
|     brand_name = fe.validators.String() | ||||
|     description = fe.validators.String() | ||||
|     size = fe.validators.String() | ||||
|     case_quantity = fe.validators.Number() | ||||
|     cases = fe.validators.Number() | ||||
|     units = fe.validators.Number() | ||||
|     expiration_date = fe.validators.DateValidator() | ||||
|     ordered_product = forms.validators.ValidProduct() | ||||
| 
 | ||||
|  | @ -361,8 +367,10 @@ class PurchaseBatchView(BatchMasterView): | |||
| 
 | ||||
|     def row_grid_row_attrs(self, row, i): | ||||
|         attrs = {} | ||||
|         if row.status_code in (row.STATUS_INCOMPLETE, | ||||
|                                row.STATUS_ORDERED_RECEIVED_DIFFER): | ||||
|         if row.status_code == row.STATUS_PRODUCT_NOT_FOUND: | ||||
|             attrs['class_'] = 'warning' | ||||
|         elif row.status_code in (row.STATUS_INCOMPLETE, | ||||
|                                  row.STATUS_ORDERED_RECEIVED_DIFFER): | ||||
|             attrs['class_'] = 'notice' | ||||
|         return attrs | ||||
| 
 | ||||
|  | @ -409,6 +417,9 @@ class PurchaseBatchView(BatchMasterView): | |||
|                 fs.item_lookup, | ||||
|                 fs.upc, | ||||
|                 fs.product, | ||||
|                 fs.brand_name, | ||||
|                 fs.description, | ||||
|                 fs.size, | ||||
|                 fs.case_quantity, | ||||
|                 fs.cases_ordered, | ||||
|                 fs.units_ordered, | ||||
|  | @ -450,6 +461,12 @@ class PurchaseBatchView(BatchMasterView): | |||
| 
 | ||||
|         elif self.viewing: | ||||
|             del fs.item_lookup | ||||
|             if fs.model.product: | ||||
|                 del (fs.brand_name, | ||||
|                      fs.description, | ||||
|                      fs.size) | ||||
|             else: | ||||
|                 del fs.product | ||||
| 
 | ||||
|     def before_create_row(self, form): | ||||
|         row = form.fieldset.model | ||||
|  | @ -615,8 +632,8 @@ class PurchaseBatchView(BatchMasterView): | |||
|             if row.po_total and not row.removed: | ||||
|                 batch.po_total -= row.po_total | ||||
|             if cases_ordered or units_ordered: | ||||
|                 row.cases_ordered = cases_ordered | ||||
|                 row.units_ordered = units_ordered | ||||
|                 row.cases_ordered = cases_ordered or None | ||||
|                 row.units_ordered = units_ordered or None | ||||
|                 row.removed = False | ||||
|                 self.handler.refresh_row(row) | ||||
|             else: | ||||
|  | @ -627,13 +644,13 @@ class PurchaseBatchView(BatchMasterView): | |||
|             row.sequence = max([0] + [r.sequence for r in batch.data_rows]) + 1 | ||||
|             row.product = product | ||||
|             batch.data_rows.append(row) | ||||
|             row.cases_ordered = cases_ordered | ||||
|             row.units_ordered = units_ordered | ||||
|             row.cases_ordered = cases_ordered or None | ||||
|             row.units_ordered = units_ordered or None | ||||
|             self.handler.refresh_row(row) | ||||
| 
 | ||||
|         return { | ||||
|             'row_cases_ordered': '' if row.removed else int(row.cases_ordered), | ||||
|             'row_units_ordered': '' if row.removed else int(row.units_ordered), | ||||
|             'row_cases_ordered': '' if row.removed else int(row.cases_ordered or 0), | ||||
|             'row_units_ordered': '' if row.removed else int(row.units_ordered or 0), | ||||
|             'row_po_total': '' if row.removed else '${:0,.2f}'.format(row.po_total), | ||||
|             'batch_po_total': '${:0,.2f}'.format(batch.po_total), | ||||
|         } | ||||
|  | @ -689,7 +706,11 @@ class PurchaseBatchView(BatchMasterView): | |||
|             mode = form.data['mode'] | ||||
|             shipped_product = form.data['product'] | ||||
|             product = form.data['ordered_product'] if mode == 'mispick' else shipped_product | ||||
|             rows = [row for row in batch.active_rows() if row.product is product] | ||||
|             if product: | ||||
|                 rows = [row for row in batch.active_rows() if row.product is product] | ||||
|             else: | ||||
|                 upc = form.data['upc'] | ||||
|                 rows = [row for row in batch.active_rows() if not row.product and row.upc == upc] | ||||
|             if rows: | ||||
|                 if len(rows) > 1: | ||||
|                     log.warning("found {} matching rows in batch {} for product: {}".format( | ||||
|  | @ -698,6 +719,11 @@ class PurchaseBatchView(BatchMasterView): | |||
|             else: | ||||
|                 row = model.PurchaseBatchRow() | ||||
|                 row.product = product | ||||
|                 row.upc = form.data['upc'] | ||||
|                 row.brand_name = form.data['brand_name'] | ||||
|                 row.description = form.data['description'] | ||||
|                 row.size = form.data['size'] | ||||
|                 row.case_quantity = form.data['case_quantity'] | ||||
|                 batch.add_row(row) | ||||
| 
 | ||||
|             cases = form.data['cases'] | ||||
|  | @ -716,9 +742,12 @@ class PurchaseBatchView(BatchMasterView): | |||
| 
 | ||||
|             self.handler.refresh_row(row) | ||||
| 
 | ||||
|             description = make_full_description(form.data['brand_name'], | ||||
|                                                 form.data['description'], | ||||
|                                                 form.data['size']) | ||||
|             self.request.session.flash("({}) {} cases, {} units: {} {}".format( | ||||
|                 form.data['mode'], form.data['cases'] or 0, form.data['units'] or 0, | ||||
|                 product.upc.pretty(), product)) | ||||
|                 form.data['upc'].pretty(), description)) | ||||
|             return self.redirect(self.request.current_route_url()) | ||||
| 
 | ||||
|         title = self.get_instance_title(batch) | ||||
|  | @ -743,35 +772,78 @@ class PurchaseBatchView(BatchMasterView): | |||
|                 'error': "Current batch has already been executed", | ||||
|                 'redirect': self.get_action_url('view', batch), | ||||
|             } | ||||
|         data = None | ||||
|         data = {} | ||||
|         upc = self.request.GET.get('upc', '').strip() | ||||
|         upc = re.sub(r'\D', '', upc) | ||||
|         if upc: | ||||
|             product = api.get_product_by_upc(Session(), upc) | ||||
|             if not product: | ||||
|                 # Try again, assuming caller did not include check digit. | ||||
|                 upc = GPC(upc, calc_check_digit='upc') | ||||
|                 product = api.get_product_by_upc(Session(), upc) | ||||
|             if product and (not product.deleted or self.request.has_perm('products.view_deleted')): | ||||
|                 data = { | ||||
|                     'uuid': product.uuid, | ||||
|                     'upc': unicode(product.upc), | ||||
|                     'upc_pretty': product.upc.pretty(), | ||||
|                     'full_description': product.full_description, | ||||
|                     'image_url': pod.get_image_url(self.rattail_config, product.upc), | ||||
|                 } | ||||
|                 cost = product.cost_for_vendor(batch.vendor) | ||||
|                 if cost: | ||||
|                     data['cost_found'] = True | ||||
|                     if int(cost.case_size) == cost.case_size: | ||||
|                         data['cost_case_size'] = int(cost.case_size) | ||||
| 
 | ||||
|             # first try to locate existing batch row by UPC match | ||||
|             provided = GPC(upc, calc_check_digit=False) | ||||
|             checked = GPC(upc, calc_check_digit='upc') | ||||
|             rows = Session.query(model.PurchaseBatchRow)\ | ||||
|                           .filter(model.PurchaseBatchRow.batch == batch)\ | ||||
|                           .filter(model.PurchaseBatchRow.upc.in_((provided, checked)))\ | ||||
|                           .all() | ||||
|             if rows: | ||||
|                 if len(rows) > 1: | ||||
|                     log.warning("found multiple UPC matches for {} in batch {}: {}".format( | ||||
|                         upc, batch.id_str, batch)) | ||||
|                 row = rows[0] | ||||
|                 data['uuid'] = row.product_uuid | ||||
|                 data['upc'] = unicode(row.upc) | ||||
|                 data['upc_pretty'] = row.upc.pretty() | ||||
|                 data['full_description'] = make_full_description(row.brand_name, row.description, row.size) | ||||
|                 data['brand_name'] = row.brand_name | ||||
|                 data['description'] = row.description | ||||
|                 data['size'] = row.size | ||||
|                 data['case_quantity'] = pretty_quantity(row.case_quantity) | ||||
|                 data['image_url'] = pod.get_image_url(self.rattail_config, row.upc) | ||||
|                 data['found_in_batch'] = True | ||||
|                 data['cases_ordered'] = pretty_quantity(row.cases_ordered, empty_zero=True) | ||||
|                 data['units_ordered'] = pretty_quantity(row.units_ordered, empty_zero=True) | ||||
|                 data['cases_received'] = pretty_quantity(row.cases_received, empty_zero=True) | ||||
|                 data['units_received'] = pretty_quantity(row.units_received, empty_zero=True) | ||||
|                 data['cases_damaged'] = pretty_quantity(row.cases_damaged, empty_zero=True) | ||||
|                 data['units_damaged'] = pretty_quantity(row.units_damaged, empty_zero=True) | ||||
|                 data['cases_expired'] = pretty_quantity(row.cases_expired, empty_zero=True) | ||||
|                 data['units_expired'] = pretty_quantity(row.units_expired, empty_zero=True) | ||||
|                 data['cases_mispick'] = pretty_quantity(row.cases_mispick, empty_zero=True) | ||||
|                 data['units_mispick'] = pretty_quantity(row.units_mispick, empty_zero=True) | ||||
| 
 | ||||
|             else: # no match in our batch, do full product search | ||||
|                 product = api.get_product_by_upc(Session(), provided) | ||||
|                 if not product: | ||||
|                     product = api.get_product_by_upc(Session(), checked) | ||||
|                 if product and (not product.deleted or self.request.has_perm('products.view_deleted')): | ||||
|                     data['uuid'] = product.uuid | ||||
|                     data['upc'] = unicode(product.upc) | ||||
|                     data['upc_pretty'] = product.upc.pretty() | ||||
|                     data['full_description'] = product.full_description | ||||
|                     data['brand_name'] = unicode(product.brand or '') | ||||
|                     data['description'] = product.description | ||||
|                     data['size'] = product.size | ||||
|                     data['case_quantity'] = 1 # default | ||||
|                     cost = product.cost_for_vendor(batch.vendor) | ||||
|                     if cost: | ||||
|                         data['cost_found'] = True | ||||
|                         data['cost_case_size'] = pretty_quantity(cost.case_size) | ||||
|                         data['case_quantity'] = pretty_quantity(cost.case_size) | ||||
|                     else: | ||||
|                         data['cost_case_size'] = '{:0.4f}'.format(cost.case_size) | ||||
|                 else: | ||||
|                     data['cost_found'] = False | ||||
|                 data['found_in_batch'] = product in [row.product for row in batch.active_rows()] | ||||
|                      | ||||
|         return {'product': data} | ||||
|                         data['cost_found'] = False | ||||
|                     data['image_url'] = pod.get_image_url(self.rattail_config, product.upc) | ||||
|                     data['found_in_batch'] = product in [row.product for row in batch.active_rows()] | ||||
| 
 | ||||
|         result = {'product': data or None, 'upc': None} | ||||
|         if not data and upc: | ||||
|             upc = GPC(upc) | ||||
|             result['upc'] = unicode(upc) | ||||
|             result['upc_pretty'] = upc.pretty() | ||||
|             result['image_url'] = pod.get_image_url(self.rattail_config, upc) | ||||
|         return result | ||||
| 
 | ||||
|     def mobile_index(self): | ||||
|         self.mobile = True | ||||
|         return self.render_to_response('mobile_index', {}) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def defaults(cls, config): | ||||
|  | @ -781,6 +853,11 @@ class PurchaseBatchView(BatchMasterView): | |||
|         model_key = cls.get_model_key() | ||||
|         model_title = cls.get_model_title() | ||||
| 
 | ||||
|         # mobile | ||||
|         config.add_route('{}.mobile'.format(route_prefix), '/mobile{}'.format(url_prefix)) | ||||
|         config.add_view(cls, attr='mobile_index', route_name='{}.mobile'.format(route_prefix), | ||||
|                         permission='{}.list'.format(permission_prefix)) | ||||
| 
 | ||||
|         # 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), | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar