Various tweaks to support mobile inventory batches
still not fully there I think, but pretty close..
This commit is contained in:
		
							parent
							
								
									452cb99349
								
							
						
					
					
						commit
						32d256932e
					
				
					 10 changed files with 341 additions and 39 deletions
				
			
		|  | @ -102,10 +102,16 @@ $(document).on('click', '#datasync-restart', function() { | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| // handle global keypress on receiving "row" page, for sake of scanner wedge
 | // handle global keypress on product batch "row" page, for sake of scanner wedge
 | ||||||
|  | var product_batch_routes = [ | ||||||
|  |     'mobile.batch.inventory.view', | ||||||
|  |     'mobile.receiving.view', | ||||||
|  | ]; | ||||||
| $(document).on('keypress', function(event) { | $(document).on('keypress', function(event) { | ||||||
|     if ($('.ui-page-active [role="main"]').data('route') == 'mobile.receiving.view') { |     var current_route = $('.ui-page-active [role="main"]').data('route'); | ||||||
|         var upc = $('#upc-search'); |     for (var route of product_batch_routes) { | ||||||
|  |         if (current_route == route) { | ||||||
|  |             var upc = $('.ui-page-active #upc-search'); | ||||||
|             if (upc.length) { |             if (upc.length) { | ||||||
|                 if (upc.is(':focus')) { |                 if (upc.is(':focus')) { | ||||||
|                     if (event.which == 13) { |                     if (event.which == 13) { | ||||||
|  | @ -128,16 +134,17 @@ $(document).on('keypress', function(event) { | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| // handle numeric buttons for receiving
 | // when numeric keypad button is clicked, update quantity accordingly
 | ||||||
| // $(document).on('click', '#receiving-quantity-keypad-thingy .ui-btn', function() {
 | $(document).on('click', '.quantity-keypad-thingy .keypad-button', function() { | ||||||
| $(document).on('click', '#receiving-quantity-keypad-thingy .keypad-button', function() { |     var keypad = $(this).parents('.quantity-keypad-thingy'); | ||||||
|     var quantity = $('.receiving-quantity'); |     var quantity = keypad.find('.keypad-quantity'); | ||||||
|     var value = quantity.text(); |     var value = quantity.text(); | ||||||
|     var key = $(this).text(); |     var key = $(this).text(); | ||||||
|     var changed = $('#receiving-quantity-keypad-thingy').data('changed'); |     var changed = keypad.data('changed'); | ||||||
|     if (key == 'Del') { |     if (key == 'Del') { | ||||||
|         if (value.length == 1) { |         if (value.length == 1) { | ||||||
|             quantity.text('0'); |             quantity.text('0'); | ||||||
|  | @ -166,7 +173,7 @@ $(document).on('click', '#receiving-quantity-keypad-thingy .keypad-button', func | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     if (changed) { |     if (changed) { | ||||||
|         $('#receiving-quantity-keypad-thingy').data('changed', true); |         keypad.data('changed', true); | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | @ -214,3 +221,17 @@ $(document).on('click', '.receiving-actions button', function() { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // handle inventory save button
 | ||||||
|  | $(document).on('click', '.inventory-actions button.save', function() { | ||||||
|  |     var form = $(this).parents('form:first'); | ||||||
|  |     var uom = form.find('[name="keypad-uom"]:checked').val(); | ||||||
|  |     var qty = form.find('.keypad-quantity').text(); | ||||||
|  |     if (uom == 'CS') { | ||||||
|  |         form.find('input[name="cases"]').val(qty); | ||||||
|  |     } else { // units
 | ||||||
|  |         form.find('input[name="units"]').val(qty); | ||||||
|  |     } | ||||||
|  |     form.submit(); | ||||||
|  | }); | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								tailbone/templates/mobile/batch/inventory/create.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								tailbone/templates/mobile/batch/inventory/create.mako
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | ## -*- coding: utf-8; -*- | ||||||
|  | <%inherit file="/mobile/master/create.mako" /> | ||||||
|  | 
 | ||||||
|  | <%def name="title()">${h.link_to("Inventory", url('mobile.batch.inventory'))} » New Batch</%def> | ||||||
|  | 
 | ||||||
|  | ${parent.body()} | ||||||
							
								
								
									
										10
									
								
								tailbone/templates/mobile/batch/inventory/index.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								tailbone/templates/mobile/batch/inventory/index.mako
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | ## -*- coding: utf-8; -*- | ||||||
|  | <%inherit file="/mobile/master/index.mako" /> | ||||||
|  | 
 | ||||||
|  | <%def name="title()">Inventory</%def> | ||||||
|  | 
 | ||||||
|  | % if request.has_perm('batch.inventory.create'): | ||||||
|  |     ${h.link_to("New Inventory Batch", url('mobile.batch.inventory.create'), class_='ui-btn ui-corner-all')} | ||||||
|  | % endif | ||||||
|  | 
 | ||||||
|  | ${parent.body()} | ||||||
|  | @ -1,6 +1,16 @@ | ||||||
| ## -*- coding: utf-8; -*- | ## -*- coding: utf-8; -*- | ||||||
| <%inherit file="/mobile/newbatch/view.mako" /> | <%inherit file="/mobile/newbatch/view.mako" /> | ||||||
| 
 | 
 | ||||||
| <%def name="title()">${h.link_to("Inventory", url('mobile.batch.inventory'))} » ${instance.id_str}</%def> | <%def name="title()">${h.link_to("Inventory", url('mobile.batch.inventory'))} » ${batch.id_str}</%def> | ||||||
| 
 | 
 | ||||||
| ${parent.body()} | ${form.render()|n} | ||||||
|  | 
 | ||||||
|  | % if not batch.executed and not batch.complete: | ||||||
|  |     <br /> | ||||||
|  |     ${h.text('upc-search', class_='inventory-upc-search', placeholder="Enter UPC", autocomplete='off', **{'data-type': 'search', 'data-url': url('mobile.batch.inventory.row_from_upc', uuid=batch.uuid)})} | ||||||
|  | % endif | ||||||
|  | 
 | ||||||
|  | % if master.has_rows: | ||||||
|  |     <br /> | ||||||
|  |     ${grid.render_complete()|n} | ||||||
|  | % endif | ||||||
|  |  | ||||||
|  | @ -1,7 +1,64 @@ | ||||||
| ## -*- coding: utf-8; -*- | ## -*- coding: utf-8; -*- | ||||||
| <%inherit file="/mobile/newbatch/view_row.mako" /> | <%inherit file="/mobile/newbatch/view_row.mako" /> | ||||||
|  | <%namespace file="/mobile/keypad.mako" import="keypad" /> | ||||||
| 
 | 
 | ||||||
| ## TODO: this is broken for actual page (header) title | ## TODO: this is broken for actual page (header) title | ||||||
| <%def name="title()">${h.link_to("Inventory", url('mobile.batch.inventory'))} » ${h.link_to(instance.batch.id_str, url('mobile.batch.inventory.view', uuid=instance.batch_uuid))} » row ${row.sequence}</%def> | <%def name="title()">${h.link_to("Inventory", url('mobile.batch.inventory'))} » ${h.link_to(instance.batch.id_str, url('mobile.batch.inventory.view', uuid=instance.batch_uuid))} » ${row.upc.pretty()}</%def> | ||||||
| 
 | 
 | ||||||
| ${parent.body()} | <% | ||||||
|  |    unit_uom = 'LB' if row.product and row.product.weighed else 'EA' | ||||||
|  | 
 | ||||||
|  |    if row.cases: | ||||||
|  |        uom = 'CS' | ||||||
|  |    elif row.units: | ||||||
|  |        uom = 'EA' | ||||||
|  |    elif row.case_quantity: | ||||||
|  |        uom = 'CS' | ||||||
|  |    else: | ||||||
|  |        uom = 'EA' | ||||||
|  | %> | ||||||
|  | 
 | ||||||
|  | <div class="ui-grid-a"> | ||||||
|  |   <div class="ui-block-a"> | ||||||
|  |     % if instance.product: | ||||||
|  |         <h3>${row.brand_name or ""}</h3> | ||||||
|  |         <h3>${row.description} ${row.size}</h3> | ||||||
|  |         <h3>${h.pretty_quantity(row.case_quantity)} ${unit_uom} per CS</h3> | ||||||
|  |     % else: | ||||||
|  |         <h3>${row.description}</h3> | ||||||
|  |     % endif | ||||||
|  |   </div> | ||||||
|  |   <div class="ui-block-b"> | ||||||
|  |     ${h.image(product_image_url, "product image")} | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <p> | ||||||
|  |   currently:   | ||||||
|  |   % if uom == 'CS': | ||||||
|  |       ${h.pretty_quantity(row.cases or 0)} | ||||||
|  |   % else: | ||||||
|  |       ${h.pretty_quantity(row.units or 0)} | ||||||
|  |   % endif | ||||||
|  |   ${uom} | ||||||
|  | </p> | ||||||
|  | 
 | ||||||
|  | % if not row.batch.executed and not row.batch.complete: | ||||||
|  | 
 | ||||||
|  |     ${h.form(request.current_route_url())} | ||||||
|  |     ${h.csrf_token(request)} | ||||||
|  |     ${h.hidden('row', value=row.uuid)} | ||||||
|  |     ${h.hidden('cases')} | ||||||
|  |     ${h.hidden('units')} | ||||||
|  | 
 | ||||||
|  |     ${keypad(unit_uom, uom, quantity=row.cases or row.units or 1)} | ||||||
|  | 
 | ||||||
|  |     <fieldset data-role="controlgroup" data-type="horizontal" class="inventory-actions"> | ||||||
|  |       <button type="button" class="ui-btn-inline ui-corner-all save">Save</button> | ||||||
|  |       <button type="button" class="ui-btn-inline ui-corner-all delete" disabled="disabled">Delete</button> | ||||||
|  |       ${h.link_to("Cancel", url('mobile.batch.inventory.view', uuid=row.batch.uuid), class_='ui-btn ui-btn-inline ui-corner-all')} | ||||||
|  |     </fieldset> | ||||||
|  | 
 | ||||||
|  |     ${h.end_form()} | ||||||
|  | 
 | ||||||
|  | % endif | ||||||
|  |  | ||||||
							
								
								
									
										39
									
								
								tailbone/templates/mobile/keypad.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								tailbone/templates/mobile/keypad.mako
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | ||||||
|  | ## -*- coding: utf-8; -*- | ||||||
|  | 
 | ||||||
|  | <%def name="keypad(unit_uom, selected_uom, quantity=1)"> | ||||||
|  |   <div class="quantity-keypad-thingy" data-changed="false"> | ||||||
|  | 
 | ||||||
|  |     <table> | ||||||
|  |       <tbody> | ||||||
|  |         <tr> | ||||||
|  |           <td>${h.link_to("7", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |           <td>${h.link_to("8", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |           <td>${h.link_to("9", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |         </tr> | ||||||
|  |         <tr> | ||||||
|  |           <td>${h.link_to("4", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |           <td>${h.link_to("5", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |           <td>${h.link_to("6", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |         </tr> | ||||||
|  |         <tr> | ||||||
|  |           <td>${h.link_to("1", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |           <td>${h.link_to("2", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |           <td>${h.link_to("3", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |         </tr> | ||||||
|  |         <tr> | ||||||
|  |           <td>${h.link_to("0", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |           <td>${h.link_to(".", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |           <td>${h.link_to("Del", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}</td> | ||||||
|  |         </tr> | ||||||
|  |       </tbody> | ||||||
|  |     </table> | ||||||
|  | 
 | ||||||
|  |     <fieldset data-role="controlgroup" data-type="horizontal"> | ||||||
|  |       <button type="button" class="ui-btn-active keypad-quantity">${h.pretty_quantity(quantity or 1)}</button> | ||||||
|  |       <button type="button" disabled="disabled"> </button> | ||||||
|  |       ${h.radio('keypad-uom', value='CS', checked=selected_uom == 'CS', label="CS")} | ||||||
|  |       ${h.radio('keypad-uom', value=unit_uom, checked=selected_uom == unit_uom, label=unit_uom)} | ||||||
|  |     </fieldset> | ||||||
|  | 
 | ||||||
|  |   </div> | ||||||
|  | </%def> | ||||||
							
								
								
									
										8
									
								
								tailbone/templates/mobile/master/create.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								tailbone/templates/mobile/master/create.mako
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | ## -*- coding: utf-8; -*- | ||||||
|  | <%inherit file="/mobile/base.mako" /> | ||||||
|  | 
 | ||||||
|  | <%def name="title()">New ${model_title}</%def> | ||||||
|  | 
 | ||||||
|  | <div class="form-wrapper"> | ||||||
|  |   ${form.render()|n} | ||||||
|  | </div><!-- form-wrapper --> | ||||||
|  | @ -204,7 +204,7 @@ class BatchMasterView(MasterView): | ||||||
|         fs.created_by.set(label="Created by", renderer=forms.renderers.UserFieldRenderer, |         fs.created_by.set(label="Created by", renderer=forms.renderers.UserFieldRenderer, | ||||||
|                           readonly=True) |                           readonly=True) | ||||||
|         fs.cognized_by.set(label="Cognized by", renderer=forms.renderers.UserFieldRenderer) |         fs.cognized_by.set(label="Cognized by", renderer=forms.renderers.UserFieldRenderer) | ||||||
|         fs.rowcount.set(label="Row Count") |         fs.rowcount.set(label="Row Count", readonly=True) | ||||||
|         fs.status_code.set(label="Status", renderer=StatusRenderer(self.model_class.STATUS)) |         fs.status_code.set(label="Status", renderer=StatusRenderer(self.model_class.STATUS)) | ||||||
|         fs.executed_by.set(label="Executed by", renderer=forms.renderers.UserFieldRenderer) |         fs.executed_by.set(label="Executed by", renderer=forms.renderers.UserFieldRenderer) | ||||||
|         fs.notes.set(renderer=fa.TextAreaFieldRenderer, size=(80, 10)) |         fs.notes.set(renderer=fa.TextAreaFieldRenderer, size=(80, 10)) | ||||||
|  | @ -322,6 +322,7 @@ class BatchMasterView(MasterView): | ||||||
|         kwargs['notes'] = batch.notes |         kwargs['notes'] = batch.notes | ||||||
|         if hasattr(batch, 'filename'): |         if hasattr(batch, 'filename'): | ||||||
|             kwargs['filename'] = batch.filename |             kwargs['filename'] = batch.filename | ||||||
|  |         kwargs['complete'] = batch.complete | ||||||
|         return kwargs |         return kwargs | ||||||
| 
 | 
 | ||||||
|     # TODO: deprecate / remove this (is it used at all now?) |     # TODO: deprecate / remove this (is it used at all now?) | ||||||
|  | @ -338,13 +339,13 @@ class BatchMasterView(MasterView): | ||||||
|         """ |         """ | ||||||
|         return True |         return True | ||||||
| 
 | 
 | ||||||
|     def redirect_after_create(self, batch): |     def redirect_after_create(self, batch, mobile=False): | ||||||
|         if self.handler.should_populate(batch): |         if self.handler.should_populate(batch): | ||||||
|             return self.redirect(self.get_action_url('prefill', batch)) |             return self.redirect(self.get_action_url('prefill', batch, mobile=mobile)) | ||||||
|         elif self.refresh_after_create: |         elif self.refresh_after_create: | ||||||
|             return self.redirect(self.get_action_url('refresh', batch)) |             return self.redirect(self.get_action_url('refresh', batch, mobile=mobile)) | ||||||
|         else: |         else: | ||||||
|             return self.redirect(self.get_action_url('view', batch)) |             return self.redirect(self.get_action_url('view', batch, mobile=mobile)) | ||||||
| 
 | 
 | ||||||
|     # TODO: some of this at least can go to master now right? |     # TODO: some of this at least can go to master now right? | ||||||
|     def edit(self): |     def edit(self): | ||||||
|  | @ -429,6 +430,7 @@ class BatchMasterView(MasterView): | ||||||
|     def get_mobile_row_data(self, batch): |     def get_mobile_row_data(self, batch): | ||||||
|         return super(BatchMasterView, self).get_mobile_row_data(batch)\ |         return super(BatchMasterView, self).get_mobile_row_data(batch)\ | ||||||
|                                            .order_by(self.model_row_class.sequence) |                                            .order_by(self.model_row_class.sequence) | ||||||
|  | 
 | ||||||
|     def redirect_after_edit(self, batch): |     def redirect_after_edit(self, batch): | ||||||
|         """ |         """ | ||||||
|         If refresh flag is set, do that; otherwise go (back) to view/edit page. |         If refresh flag is set, do that; otherwise go (back) to view/edit page. | ||||||
|  |  | ||||||
|  | @ -26,10 +26,16 @@ Views for inventory batches | ||||||
| 
 | 
 | ||||||
| from __future__ import unicode_literals, absolute_import | from __future__ import unicode_literals, absolute_import | ||||||
| 
 | 
 | ||||||
| from rattail.db import model | import re | ||||||
|  | 
 | ||||||
|  | from rattail import pod | ||||||
|  | from rattail.db import model, api | ||||||
| from rattail.time import localtime | from rattail.time import localtime | ||||||
|  | from rattail.gpc import GPC | ||||||
|  | from rattail.util import pretty_quantity | ||||||
| 
 | 
 | ||||||
| import formalchemy as fa | import formalchemy as fa | ||||||
|  | import formencode as fe | ||||||
| from webhelpers2.html import tags | from webhelpers2.html import tags | ||||||
| 
 | 
 | ||||||
| from tailbone import forms | from tailbone import forms | ||||||
|  | @ -46,7 +52,7 @@ class InventoryBatchView(BatchMasterView): | ||||||
|     route_prefix = 'batch.inventory' |     route_prefix = 'batch.inventory' | ||||||
|     url_prefix = '/batch/inventory' |     url_prefix = '/batch/inventory' | ||||||
|     creatable = False |     creatable = False | ||||||
|     editable = False |     mobile_creatable = True | ||||||
| 
 | 
 | ||||||
|     model_row_class = model.InventoryBatchRow |     model_row_class = model.InventoryBatchRow | ||||||
|     rows_editable = True |     rows_editable = True | ||||||
|  | @ -87,6 +93,7 @@ class InventoryBatchView(BatchMasterView): | ||||||
|                 fs.handheld_batches, |                 fs.handheld_batches, | ||||||
|                 fs.mode, |                 fs.mode, | ||||||
|                 fs.rowcount, |                 fs.rowcount, | ||||||
|  |                 fs.complete, | ||||||
|                 fs.executed, |                 fs.executed, | ||||||
|                 fs.executed_by, |                 fs.executed_by, | ||||||
|             ]) |             ]) | ||||||
|  | @ -100,6 +107,8 @@ class InventoryBatchView(BatchMasterView): | ||||||
|             fs.executed_by, |             fs.executed_by, | ||||||
|         ]) |         ]) | ||||||
|         batch = fs.model |         batch = fs.model | ||||||
|  |         if self.creating: | ||||||
|  |             del fs.rowcount | ||||||
|         if not batch.executed: |         if not batch.executed: | ||||||
|             del [fs.executed, fs.executed_by] |             del [fs.executed, fs.executed_by] | ||||||
|             if not batch.complete: |             if not batch.complete: | ||||||
|  | @ -107,6 +116,89 @@ class InventoryBatchView(BatchMasterView): | ||||||
|         else: |         else: | ||||||
|             del fs.complete |             del fs.complete | ||||||
| 
 | 
 | ||||||
|  |     # TODO: this view can create new rows, with only a GET query.  that should | ||||||
|  |     # probably be changed to require POST; for now we just require the "create | ||||||
|  |     # batch row" perm and call it good.. | ||||||
|  |     def mobile_row_from_upc(self): | ||||||
|  |         """ | ||||||
|  |         Locate and/or create a row within the batch, according to the given | ||||||
|  |         product UPC, then redirect to the row view page. | ||||||
|  |         """ | ||||||
|  |         batch = self.get_instance() | ||||||
|  |         row = None | ||||||
|  |         upc = self.request.GET.get('upc', '').strip() | ||||||
|  |         upc = re.sub(r'\D', '', upc) | ||||||
|  |         if upc: | ||||||
|  | 
 | ||||||
|  |             # try to locate general product by UPC; add to batch either way | ||||||
|  |             provided = GPC(upc, calc_check_digit=False) | ||||||
|  |             checked = GPC(upc, calc_check_digit='upc') | ||||||
|  |             product = api.get_product_by_upc(self.Session(), provided) | ||||||
|  |             if not product: | ||||||
|  |                 product = api.get_product_by_upc(self.Session(), checked) | ||||||
|  |             row = model.InventoryBatchRow() | ||||||
|  |             if product: | ||||||
|  |                 row.product = product | ||||||
|  |                 row.upc = product.upc | ||||||
|  |             else: | ||||||
|  |                 row.upc = provided # TODO: why not 'checked' instead? how to choose? | ||||||
|  |                 row.description = "(unknown product)" | ||||||
|  |             self.handler.add_row(batch, row) | ||||||
|  | 
 | ||||||
|  |         self.Session.flush() | ||||||
|  |         return self.redirect(self.mobile_row_route_url('view', uuid=row.uuid)) | ||||||
|  | 
 | ||||||
|  |     def template_kwargs_view_row(self, **kwargs): | ||||||
|  |         row = kwargs['instance'] | ||||||
|  |         kwargs['product_image_url'] = pod.get_image_url(self.rattail_config, row.upc) | ||||||
|  |         return kwargs | ||||||
|  | 
 | ||||||
|  |     def get_batch_kwargs(self, batch, mobile=False): | ||||||
|  |         kwargs = super(InventoryBatchView, self).get_batch_kwargs(batch, mobile=False) | ||||||
|  |         kwargs['mode'] = batch.mode | ||||||
|  |         kwargs['complete'] = False | ||||||
|  |         return kwargs | ||||||
|  | 
 | ||||||
|  |     def get_mobile_row_data(self, batch): | ||||||
|  |         # we want newest on top, for inventory batch rows | ||||||
|  |         return self.get_row_data(batch)\ | ||||||
|  |                    .order_by(self.model_row_class.sequence.desc()) | ||||||
|  | 
 | ||||||
|  |     # TODO: ugh, the hackiness.  needs a refactor fo sho | ||||||
|  |     def mobile_view_row(self): | ||||||
|  |         """ | ||||||
|  |         Mobile view for inventory batch rows.  Note that this also handles | ||||||
|  |         updating a row...ugh. | ||||||
|  |         """ | ||||||
|  |         self.viewing = True | ||||||
|  |         row = self.get_row_instance() | ||||||
|  |         form = self.make_mobile_row_form(row) | ||||||
|  |         context = { | ||||||
|  |             'row': row, | ||||||
|  |             'instance': row, | ||||||
|  |             'instance_title': self.get_row_instance_title(row), | ||||||
|  |             'parent_model_title': self.get_model_title(), | ||||||
|  |             'product_image_url': pod.get_image_url(self.rattail_config, row.upc), | ||||||
|  |             'form': form, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if self.request.has_perm('{}.edit'.format(self.get_row_permission_prefix())): | ||||||
|  |             update_form = forms.SimpleForm(self.request, schema=InventoryForm) | ||||||
|  |             if update_form.validate(): | ||||||
|  |                 row = update_form.data['row'] | ||||||
|  |                 cases = update_form.data['cases'] | ||||||
|  |                 units = update_form.data['units'] | ||||||
|  |                 if cases: | ||||||
|  |                     row.cases = cases | ||||||
|  |                     row.units = None | ||||||
|  |                 elif units: | ||||||
|  |                     row.cases = None | ||||||
|  |                     row.units = units | ||||||
|  |                 self.handler.refresh_row(row) | ||||||
|  |                 return self.redirect(self.request.route_url('mobile.{}.view'.format(self.get_route_prefix()), uuid=row.batch_uuid)) | ||||||
|  | 
 | ||||||
|  |         return self.render_to_response('view_row', context, mobile=True) | ||||||
|  | 
 | ||||||
|     def _preconfigure_row_grid(self, g): |     def _preconfigure_row_grid(self, g): | ||||||
|         super(InventoryBatchView, self)._preconfigure_row_grid(g) |         super(InventoryBatchView, self)._preconfigure_row_grid(g) | ||||||
|         g.upc.set(label="UPC") |         g.upc.set(label="UPC") | ||||||
|  | @ -139,7 +231,9 @@ class InventoryBatchView(BatchMasterView): | ||||||
|         if row is None: |         if row is None: | ||||||
|             return '' |             return '' | ||||||
|         description = row.product.full_description if row.product else row.description |         description = row.product.full_description if row.product else row.description | ||||||
|         title = "({}) {}".format(row.upc.pretty(), description) |         unit_uom = 'LB' if row.product and row.product.weighed else 'EA' | ||||||
|  |         qty = "{} {}".format(pretty_quantity(row.cases or row.units), 'CS' if row.cases else unit_uom) | ||||||
|  |         title = "({}) {} - {}".format(row.upc.pretty(), description, qty) | ||||||
|         url = self.request.route_url('mobile.batch.inventory.rows.view', uuid=row.uuid) |         url = self.request.route_url('mobile.batch.inventory.rows.view', uuid=row.uuid) | ||||||
|         return tags.link_to(title, url) |         return tags.link_to(title, url) | ||||||
| 
 | 
 | ||||||
|  | @ -164,6 +258,21 @@ class InventoryBatchView(BatchMasterView): | ||||||
|                 fs.units, |                 fs.units, | ||||||
|             ]) |             ]) | ||||||
| 
 | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def defaults(cls, config): | ||||||
|  |         model_key = cls.get_model_key() | ||||||
|  |         route_prefix = cls.get_route_prefix() | ||||||
|  |         url_prefix = cls.get_url_prefix() | ||||||
|  |         row_permission_prefix = cls.get_row_permission_prefix() | ||||||
|  | 
 | ||||||
|  |         cls._batch_defaults(config) | ||||||
|  |         cls._defaults(config) | ||||||
|  | 
 | ||||||
|  |         # mobile - make new row from UPC | ||||||
|  |         config.add_route('mobile.{}.row_from_upc'.format(route_prefix), '/mobile{}/{{{}}}/row-from-upc'.format(url_prefix, model_key)) | ||||||
|  |         config.add_view(cls, attr='mobile_row_from_upc', route_name='mobile.{}.row_from_upc'.format(route_prefix), | ||||||
|  |                         permission='{}.create'.format(row_permission_prefix)) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class InventoryBatchRenderer(fa.FieldRenderer): | class InventoryBatchRenderer(fa.FieldRenderer): | ||||||
| 
 | 
 | ||||||
|  | @ -178,5 +287,23 @@ class InventoryBatchRenderer(fa.FieldRenderer): | ||||||
|         return tags.link_to(title, url) |         return tags.link_to(title, url) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class ValidBatchRow(forms.validators.ModelValidator): | ||||||
|  |     model_class = model.InventoryBatchRow | ||||||
|  | 
 | ||||||
|  |     def _to_python(self, value, state): | ||||||
|  |         row = super(ValidBatchRow, self)._to_python(value, state) | ||||||
|  |         if row.batch.executed: | ||||||
|  |             raise fe.Invalid("Batch has already been executed", value, state) | ||||||
|  |         return row | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class InventoryForm(forms.Schema): | ||||||
|  |     allow_extra_fields = True | ||||||
|  |     filter_extra_fields = True | ||||||
|  |     row = ValidBatchRow() | ||||||
|  |     cases = fe.validators.Number() | ||||||
|  |     units = fe.validators.Number() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def includeme(config): | def includeme(config): | ||||||
|     InventoryBatchView.defaults(config) |     InventoryBatchView.defaults(config) | ||||||
|  |  | ||||||
|  | @ -312,6 +312,21 @@ class MasterView(View): | ||||||
|                 return self.redirect_after_create(obj) |                 return self.redirect_after_create(obj) | ||||||
|         return self.render_to_response('create', {'form': form}) |         return self.render_to_response('create', {'form': form}) | ||||||
| 
 | 
 | ||||||
|  |     def mobile_create(self): | ||||||
|  |         """ | ||||||
|  |         Mobile view for creating a new primary object | ||||||
|  |         """ | ||||||
|  |         self.creating = True | ||||||
|  |         form = self.make_mobile_form(self.get_model_class()) | ||||||
|  |         if self.request.method == 'POST': | ||||||
|  |             if form.validate(): | ||||||
|  |                 # let save_create_form() return alternate object if necessary | ||||||
|  |                 obj = self.save_create_form(form) or form.fieldset.model | ||||||
|  |                 self.after_create(obj) | ||||||
|  |                 self.flash_after_create(obj) | ||||||
|  |                 return self.redirect_after_create(obj, mobile=True) | ||||||
|  |         return self.render_to_response('create', {'form': form}, mobile=True) | ||||||
|  | 
 | ||||||
|     def flash_after_create(self, obj): |     def flash_after_create(self, obj): | ||||||
|         self.request.session.flash("{} has been created: {}".format( |         self.request.session.flash("{} has been created: {}".format( | ||||||
|             self.get_model_title(), self.get_instance_title(obj))) |             self.get_model_title(), self.get_instance_title(obj))) | ||||||
|  | @ -320,8 +335,8 @@ class MasterView(View): | ||||||
|         self.before_create(form) |         self.before_create(form) | ||||||
|         form.save() |         form.save() | ||||||
| 
 | 
 | ||||||
|     def redirect_after_create(self, instance): |     def redirect_after_create(self, instance, mobile=False): | ||||||
|         return self.redirect(self.get_action_url('view', instance)) |         return self.redirect(self.get_action_url('view', instance, mobile=mobile)) | ||||||
| 
 | 
 | ||||||
|     def view(self, instance=None): |     def view(self, instance=None): | ||||||
|         """ |         """ | ||||||
|  | @ -573,6 +588,13 @@ class MasterView(View): | ||||||
|         fieldset = self.make_fieldset(instance) |         fieldset = self.make_fieldset(instance) | ||||||
|         self.preconfigure_mobile_fieldset(fieldset) |         self.preconfigure_mobile_fieldset(fieldset) | ||||||
|         self.configure_mobile_fieldset(fieldset) |         self.configure_mobile_fieldset(fieldset) | ||||||
|  |         kwargs.setdefault('creating', self.creating) | ||||||
|  |         kwargs.setdefault('editing', self.editing) | ||||||
|  |         kwargs.setdefault('action_url', self.request.current_route_url(_query=None)) | ||||||
|  |         if self.creating: | ||||||
|  |             kwargs.setdefault('cancel_url', self.get_index_url(mobile=True)) | ||||||
|  |         else: | ||||||
|  |             kwargs.setdefault('cancel_url', self.get_action_url('view', instance, mobile=True)) | ||||||
|         factory = kwargs.pop('factory', forms.AlchemyForm) |         factory = kwargs.pop('factory', forms.AlchemyForm) | ||||||
|         kwargs.setdefault('session', self.Session()) |         kwargs.setdefault('session', self.Session()) | ||||||
|         form = factory(self.request, fieldset, **kwargs) |         form = factory(self.request, fieldset, **kwargs) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar