Add beginnings of mobile receiving views
Very incomplete, not much is supported yet, but this is a start..
This commit is contained in:
		
							parent
							
								
									6ed752d477
								
							
						
					
					
						commit
						3930ed9a16
					
				
					 5 changed files with 235 additions and 17 deletions
				
			
		
							
								
								
									
										78
									
								
								tailbone/static/js/jquery.ui.tailbone.mobile.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								tailbone/static/js/jquery.ui.tailbone.mobile.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,78 @@ | ||||||
|  | 
 | ||||||
|  | /****************************************** | ||||||
|  |  * jQuery Mobile plugins for Tailbone | ||||||
|  |  *****************************************/ | ||||||
|  | 
 | ||||||
|  | /****************************************** | ||||||
|  |  * mobile autocomplete | ||||||
|  |  *****************************************/ | ||||||
|  | 
 | ||||||
|  | (function($) { | ||||||
|  |      | ||||||
|  |     $.widget('tailbone.mobileautocomplete', { | ||||||
|  | 
 | ||||||
|  |         _create: function() { | ||||||
|  |             var that = this; | ||||||
|  | 
 | ||||||
|  |             // snag some element references
 | ||||||
|  |             this.search = this.element.find('.ui-input-search'); | ||||||
|  |             this.hidden_field = this.element.find('input[type="hidden"]'); | ||||||
|  |             this.text_field = this.element.find('input[type="text"]'); | ||||||
|  |             this.ul = this.element.find('ul'); | ||||||
|  |             this.button = this.element.find('button'); | ||||||
|  | 
 | ||||||
|  |             // establish our autocomplete URL
 | ||||||
|  |             this.url = this.options.url || this.element.data('url'); | ||||||
|  | 
 | ||||||
|  |             // NOTE: much of this code was copied from the jquery mobile demo site
 | ||||||
|  |             // https://demos.jquerymobile.com/1.4.5/listview-autocomplete-remote/
 | ||||||
|  |             this.ul.on('filterablebeforefilter', function(e, data) { | ||||||
|  | 
 | ||||||
|  |                 var $input = $( data.input ), | ||||||
|  |                     value = $input.val(), | ||||||
|  |                     html = ""; | ||||||
|  |                 that.ul.html( "" ); | ||||||
|  |                 if ( value && value.length > 2 ) { | ||||||
|  |                     that.ul.html( "<li><div class='ui-loader'><span class='ui-icon ui-icon-loading'></span></div></li>" ); | ||||||
|  |                     that.ul.listview( "refresh" ); | ||||||
|  |                     $.ajax({ | ||||||
|  |                         url: that.url, | ||||||
|  |                         data: { | ||||||
|  |                             term: $input.val() | ||||||
|  |                         } | ||||||
|  |                     }) | ||||||
|  |                         .then( function ( response ) { | ||||||
|  |                             $.each( response, function ( i, val ) { | ||||||
|  |                                 html += '<li data-uuid="' + val.value + '">' + val.label + "</li>"; | ||||||
|  |                             }); | ||||||
|  |                             that.ul.html( html ); | ||||||
|  |                             that.ul.listview( "refresh" ); | ||||||
|  |                             that.ul.trigger( "updatelayout"); | ||||||
|  |                         }); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             // when user clicks autocomplete result, hide search etc.
 | ||||||
|  |             this.ul.on('click', 'li', function() { | ||||||
|  |                 var $li = $(this); | ||||||
|  |                 that.search.hide(); | ||||||
|  |                 that.hidden_field.val($li.data('uuid')); | ||||||
|  |                 that.button.text($li.text()).show(); | ||||||
|  |                 that.ul.hide(); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             // when user clicks "change" button, show search etc.
 | ||||||
|  |             this.button.click(function() { | ||||||
|  |                 that.button.hide(); | ||||||
|  |                 that.ul.empty().show(); | ||||||
|  |                 that.hidden_field.val(''); | ||||||
|  |                 that.search.show(); | ||||||
|  |                 that.text_field.focus(); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     }); | ||||||
|  |      | ||||||
|  | })( jQuery ); | ||||||
|  | @ -29,13 +29,36 @@ $(document).on('pagecontainerchange', function(event, ui) { | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | $(document).on('pagecreate', function() { | ||||||
|  | 
 | ||||||
|  |     // setup any autocomplete fields
 | ||||||
|  |     $('.field.autocomplete').mobileautocomplete(); | ||||||
|  | 
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Automatically set focus to certain fields, on various pages | ||||||
|  |  */ | ||||||
|  | function setfocus() { | ||||||
|  |     var el = null; | ||||||
|  |     var queries = [ | ||||||
|  |         '#username', | ||||||
|  |         '#new-purchasing-batch-vendor-text', | ||||||
|  |     ]; | ||||||
|  |     $.each(queries, function(i, query) { | ||||||
|  |         el = $(query); | ||||||
|  |         if (el.is(':visible')) { | ||||||
|  |             el.focus(); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| $(document).on('pageshow', function() { | $(document).on('pageshow', function() { | ||||||
| 
 | 
 | ||||||
|     // on login page, auto-focus username
 |     setfocus(); | ||||||
|     el = $('#username'); |  | ||||||
|     if (el.is(':visible')) { |  | ||||||
|         el.focus(); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     // TODO: seems like this should be better somehow...
 |     // TODO: seems like this should be better somehow...
 | ||||||
|     // remove all flash messages after 2.5 seconds
 |     // remove all flash messages after 2.5 seconds
 | ||||||
|  | @ -44,8 +67,28 @@ $(document).on('pageshow', function() { | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| $(document).on('click', '#datasync-restart', function() { | // vendor validation for new purchasing batch
 | ||||||
|  | $(document).on('click', 'form[name="new-purchasing-batch"] input[type="submit"]', function() { | ||||||
|  |     var $form = $(this).parents('form'); | ||||||
|  |     if (! $form.find('[name="vendor"]').val()) { | ||||||
|  |         alert("Please select a vendor"); | ||||||
|  |         $form.find('[name="new-purchasing-batch-vendor-text"]').focus(); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
|     // disable datasync restart button when clicked
 | // submit new purchasing batch form on Purchase click
 | ||||||
|  | $(document).on('click', 'form[name="new-purchasing-batch"] [data-role="listview"] a', function() { | ||||||
|  |     var $form = $(this).parents('form'); | ||||||
|  |     var $field = $form.find('[name="purchase"]'); | ||||||
|  |     var uuid = $(this).parents('li').data('uuid'); | ||||||
|  |     $field.val(uuid); | ||||||
|  |     $form.submit(); | ||||||
|  |     return false; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // disable datasync restart button when clicked
 | ||||||
|  | $(document).on('click', '#datasync-restart', function() { | ||||||
|     $(this).button('disable'); |     $(this).button('disable'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -5,16 +5,22 @@ | ||||||
|     <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> |     <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> | ||||||
|     <title>${self.global_title()} » ${self.title()}</title> |     <title>${self.global_title()} » ${self.title()}</title> | ||||||
| 	<meta name="viewport" content="width=device-width, initial-scale=1" /> | 	<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||||
|  | 
 | ||||||
|     ${h.javascript_link('https://code.jquery.com/jquery-1.12.4.min.js')} |     ${h.javascript_link('https://code.jquery.com/jquery-1.12.4.min.js')} | ||||||
|     ${h.javascript_link('https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js')} |     ${h.javascript_link('https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js')} | ||||||
|     ${h.stylesheet_link('https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css')} |     ${h.javascript_link(request.static_url('tailbone:static/js/jquery.ui.tailbone.mobile.js'))} | ||||||
|     ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.mobile.js'))} |     ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.mobile.js'))} | ||||||
|  |     ${self.extra_javascript()} | ||||||
|  | 
 | ||||||
|  |     ${h.stylesheet_link('https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css')} | ||||||
|     ${h.stylesheet_link(request.static_url('tailbone:static/css/mobile.css'))} |     ${h.stylesheet_link(request.static_url('tailbone:static/css/mobile.css'))} | ||||||
|     % if not request.rattail_config.production(): |     % if not request.rattail_config.production(): | ||||||
|         <style type="text/css"> |     <style type="text/css"> | ||||||
|           .ui-page-theme-a { background-image: url(${request.static_url('tailbone:static/img/testing.png')}); } |       .ui-page-theme-a { background-image: url(${request.static_url('tailbone:static/img/testing.png')}); } | ||||||
|         </style> |     </style> | ||||||
|     % endif |     % endif | ||||||
|  |     ${self.extra_styles()} | ||||||
|  | 
 | ||||||
|   </head> |   </head> | ||||||
|   ${self.mobile_body()} |   ${self.mobile_body()} | ||||||
| </html> | </html> | ||||||
|  | @ -47,6 +53,10 @@ | ||||||
| 
 | 
 | ||||||
| <%def name="page_title()">${self.title()}</%def> | <%def name="page_title()">${self.title()}</%def> | ||||||
| 
 | 
 | ||||||
|  | <%def name="extra_javascript()"></%def> | ||||||
|  | 
 | ||||||
|  | <%def name="extra_styles()"></%def> | ||||||
|  | 
 | ||||||
| <%def name="mobile_header()"> | <%def name="mobile_header()"> | ||||||
|   <div data-role="header"> |   <div data-role="header"> | ||||||
|     ${self.mobile_header_link()} |     ${self.mobile_header_link()} | ||||||
|  |  | ||||||
							
								
								
									
										49
									
								
								tailbone/templates/purchases/batches/mobile_create.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								tailbone/templates/purchases/batches/mobile_create.mako
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | ||||||
|  | ## -*- coding: utf-8 -*- | ||||||
|  | <%inherit file="/mobile/base.mako" /> | ||||||
|  | 
 | ||||||
|  | <%def name="title()">New ${mode_title} Batch</%def> | ||||||
|  | 
 | ||||||
|  | ${h.form(request.current_route_url(), class_='ui-filterable', name='new-purchasing-batch')} | ||||||
|  | ${h.csrf_token(request)} | ||||||
|  | 
 | ||||||
|  | % if vendor is Undefined: | ||||||
|  | 
 | ||||||
|  |     <div class="field-wrapper vendor"> | ||||||
|  |       <div class="field autocomplete" data-url="${url('vendors.autocomplete')}"> | ||||||
|  |         ${h.hidden('vendor')} | ||||||
|  |         ${h.text('new-purchasing-batch-vendor-text', placeholder="Vendor name", autocomplete='off', **{'data-type': 'search'})} | ||||||
|  |         <ul data-role="listview" data-inset="true" data-filter="true" data-input="#new-purchasing-batch-vendor-text"></ul> | ||||||
|  |         <button type="button" style="display: none;">Change Vendor</button> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <br /> | ||||||
|  |     ${h.submit('submit', "Find purchase orders")} | ||||||
|  |     ## <button type="button">New receiving from scratch</button> | ||||||
|  | 
 | ||||||
|  | % else: ## vendor is known | ||||||
|  | 
 | ||||||
|  |     <div class="field-wrapper vendor"> | ||||||
|  |       <div class="field"> | ||||||
|  |         ${h.hidden('vendor', value=vendor.uuid)} | ||||||
|  |         ${vendor} | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     % if purchases: | ||||||
|  |         ${h.hidden('purchase')} | ||||||
|  |         <ul data-role="listview" data-inset="true"> | ||||||
|  |           % for uuid, purchase in purchases: | ||||||
|  |               <li data-uuid="${uuid}">${h.link_to(purchase, '#')}</li> | ||||||
|  |           % endfor | ||||||
|  |         </ul> | ||||||
|  |     % else: | ||||||
|  |         <p>(no eligible purchases found)</p> | ||||||
|  |     % endif | ||||||
|  | 
 | ||||||
|  |     ## ${h.link_to("Receive from scratch for {}".format(vendor), '#', class_='ui-btn ui-corner-all')} | ||||||
|  |     ${h.link_to("Start over", url('purchases.batch.mobile_create'), class_='ui-btn ui-corner-all')} | ||||||
|  | 
 | ||||||
|  | % endif | ||||||
|  | 
 | ||||||
|  | ${h.end_form()} | ||||||
|  | @ -240,14 +240,16 @@ class PurchaseBatchView(BatchMasterView): | ||||||
|             fs.department.set(readonly=True) |             fs.department.set(readonly=True) | ||||||
|             fs.purchase.set(readonly=True) |             fs.purchase.set(readonly=True) | ||||||
| 
 | 
 | ||||||
|     def eligible_purchases(self): |     def eligible_purchases(self, vendor_uuid=None, mode=None): | ||||||
|         uuid = self.request.GET.get('vendor_uuid') |         if not vendor_uuid: | ||||||
|         vendor = Session.query(model.Vendor).get(uuid) if uuid else None |             vendor_uuid = self.request.GET.get('vendor_uuid') | ||||||
|  |         vendor = Session.query(model.Vendor).get(vendor_uuid) if vendor_uuid else None | ||||||
|         if not vendor: |         if not vendor: | ||||||
|             return {'error': "Must specify a vendor."} |             return {'error': "Must specify a vendor."} | ||||||
| 
 | 
 | ||||||
|         mode = self.request.GET.get('mode') |         if mode is None: | ||||||
|         mode = int(mode) if mode and mode.isdigit() else None |             mode = self.request.GET.get('mode') | ||||||
|  |             mode = int(mode) if mode and mode.isdigit() else None | ||||||
|         if not mode or mode not in self.enum.PURCHASE_BATCH_MODE: |         if not mode or mode not in self.enum.PURCHASE_BATCH_MODE: | ||||||
|             return {'error': "Unknown mode: {}".format(mode)} |             return {'error': "Unknown mode: {}".format(mode)} | ||||||
| 
 | 
 | ||||||
|  | @ -888,6 +890,37 @@ class PurchaseBatchView(BatchMasterView): | ||||||
|         self.mobile = True |         self.mobile = True | ||||||
|         return self.render_to_response('mobile_index', {}) |         return self.render_to_response('mobile_index', {}) | ||||||
| 
 | 
 | ||||||
|  |     def mobile_create(self): | ||||||
|  |         """ | ||||||
|  |         View for creating a new purchasing batch via mobile | ||||||
|  |         """ | ||||||
|  |         # TODO: make this dynamic somehow, support other modes | ||||||
|  |         mode = self.enum.PURCHASE_BATCH_MODE_RECEIVING | ||||||
|  |         data = {'mode': mode} | ||||||
|  | 
 | ||||||
|  |         vendor = None | ||||||
|  |         if self.request.method == 'POST' and self.request.POST.get('vendor'): | ||||||
|  |             vendor = self.Session.query(model.Vendor).get(self.request.POST['vendor']) | ||||||
|  |             if vendor: | ||||||
|  |                 data['vendor'] = vendor | ||||||
|  |                 if self.request.POST.get('purchase'): | ||||||
|  |                     purchase = self.get_purchase(self.request.POST['purchase']) | ||||||
|  |                     if purchase: | ||||||
|  |                         # TODO: these kwargs need help! | ||||||
|  |                         batch = self.handler.make_batch(self.Session(), | ||||||
|  |                                                         mode=mode, vendor=vendor, | ||||||
|  |                                                         store=self.rattail_config.get_store(self.Session()), | ||||||
|  |                                                         buyer=self.request.user.employee, | ||||||
|  |                                                         created_by=self.request.user) | ||||||
|  |                         self.request.session.flash("Created new purchasing batch: {}".format(batch)) | ||||||
|  |                         return self.redirect(self.request.route_url('purchases.batch.mobile_create')) | ||||||
|  | 
 | ||||||
|  |         data['mode_title'] = self.enum.PURCHASE_BATCH_MODE[mode].capitalize() | ||||||
|  |         if vendor: | ||||||
|  |             purchases = self.eligible_purchases(vendor.uuid, mode=mode) | ||||||
|  |             data['purchases'] = [(p['key'], p['display']) for p in purchases['purchases']] | ||||||
|  |         return self.render_to_response('mobile_create', data) | ||||||
|  | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def defaults(cls, config): |     def defaults(cls, config): | ||||||
|         route_prefix = cls.get_route_prefix() |         route_prefix = cls.get_route_prefix() | ||||||
|  | @ -896,11 +929,16 @@ class PurchaseBatchView(BatchMasterView): | ||||||
|         model_key = cls.get_model_key() |         model_key = cls.get_model_key() | ||||||
|         model_title = cls.get_model_title() |         model_title = cls.get_model_title() | ||||||
| 
 | 
 | ||||||
|         # mobile |         # mobile index | ||||||
|         config.add_route('{}.mobile'.format(route_prefix), '/mobile{}'.format(url_prefix)) |         config.add_route('{}.mobile'.format(route_prefix), '/mobile{}'.format(url_prefix)) | ||||||
|         config.add_view(cls, attr='mobile_index', route_name='{}.mobile'.format(route_prefix), |         config.add_view(cls, attr='mobile_index', route_name='{}.mobile'.format(route_prefix), | ||||||
|                         permission='{}.list'.format(permission_prefix)) |                         permission='{}.list'.format(permission_prefix)) | ||||||
| 
 | 
 | ||||||
|  |         # mobile create | ||||||
|  |         config.add_route('{}.mobile_create'.format(route_prefix), '/mobile{}/new'.format(url_prefix)) | ||||||
|  |         config.add_view(cls, attr='mobile_create', route_name='{}.mobile_create'.format(route_prefix), | ||||||
|  |                         permission='{}.create'.format(permission_prefix)) | ||||||
|  | 
 | ||||||
|         # eligible purchases (AJAX) |         # eligible purchases (AJAX) | ||||||
|         config.add_route('{}.eligible_purchases'.format(route_prefix), '{}/eligible-purchases'.format(url_prefix)) |         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), |         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