Add support for Vendor -> Quickbooks Bank Accounts field
this feels a bit hacky yet, but had to introduce some new mechanisms to allow for extra template stuff while avoiding adding a new vendors/edit (etc.) template in this project, since we are using a view supplement..ugh
This commit is contained in:
		
							parent
							
								
									ddeb4545a6
								
							
						
					
					
						commit
						7e08dd0f89
					
				
					 3 changed files with 277 additions and 1 deletions
				
			
		
							
								
								
									
										86
									
								
								tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_js.mako
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_js.mako
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | ||||||
|  | ## -*- coding: utf-8; -*- | ||||||
|  | 
 | ||||||
|  | <script type="text/javascript"> | ||||||
|  | 
 | ||||||
|  |   ${form.component_studly}Data.quickbooksBankAccountShowDialog = false | ||||||
|  |   ${form.component_studly}Data.quickbooksBankAccountEditing = null | ||||||
|  |   ${form.component_studly}Data.quickbooksBankAccountStore = null | ||||||
|  |   ${form.component_studly}Data.quickbooksBankAccountNumber = null | ||||||
|  |   ${form.component_studly}Data.quickbooksBankAccountStoreOptions = ${json.dumps(store_options)|n} | ||||||
|  | 
 | ||||||
|  |   ${form.component_studly}.methods.quickbooksBankAccountCreate = function() { | ||||||
|  |       this.quickbooksBankAccountEditing = null | ||||||
|  |       this.quickbooksBankAccountStore = null | ||||||
|  |       this.quickbooksBankAccountNumber = null | ||||||
|  |       this.quickbooksBankAccountShowDialog = true | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ${form.component_studly}.methods.quickbooksBankAccountEdit = function(row) { | ||||||
|  |       this.quickbooksBankAccountEditing = row | ||||||
|  |       this.quickbooksBankAccountStore = row.store_uuid | ||||||
|  |       this.quickbooksBankAccountNumber = row.account_number | ||||||
|  |       this.quickbooksBankAccountShowDialog = true | ||||||
|  |       this.$nextTick(() => { | ||||||
|  |           this.$refs.quickbooksBankAccountNumber.focus() | ||||||
|  |       }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ${form.component_studly}.computed.quickbooksBankAccountSaveDisabled = function(row) { | ||||||
|  |       if (!this.quickbooksBankAccountStore) { | ||||||
|  |           return true | ||||||
|  |       } | ||||||
|  |       if (!this.quickbooksBankAccountNumber) { | ||||||
|  |           return true | ||||||
|  |       } | ||||||
|  |       return false | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ${form.component_studly}.computed.quickbooksBankAccountsFinal = function() { | ||||||
|  |       return JSON.stringify(this.quickbooksBankAccountsData) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ${form.component_studly}.methods.quickbooksBankAccountSave = function() { | ||||||
|  |       if (this.quickbooksBankAccountEditing) { | ||||||
|  |           this.quickbooksBankAccountEditing.store_uuid = this.quickbooksBankAccountStore | ||||||
|  |           this.quickbooksBankAccountEditing.account_number = this.quickbooksBankAccountNumber | ||||||
|  |           this.quickbooksBankAccountShowDialog = false | ||||||
|  |       } else { | ||||||
|  |           if (this.quickbooksBankAccountIsStoreDefined(this.quickbooksBankAccountStore)) { | ||||||
|  |               alert("An account number is already defined for that store!") | ||||||
|  |           } else { | ||||||
|  |               this.quickbooksBankAccountsData.push({ | ||||||
|  |                   store_uuid: this.quickbooksBankAccountStore, | ||||||
|  |                   store: this.quickbooksBankAccountGetStoreDisplay(this.quickbooksBankAccountStore), | ||||||
|  |                   account_number: this.quickbooksBankAccountNumber, | ||||||
|  |               }) | ||||||
|  |               this.quickbooksBankAccountShowDialog = false | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ${form.component_studly}.methods.quickbooksBankAccountIsStoreDefined = function(uuid) { | ||||||
|  |       for (let account of this.quickbooksBankAccountsData) { | ||||||
|  |           if (account.store_uuid == uuid) { | ||||||
|  |               return true | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |       return false | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ${form.component_studly}.methods.quickbooksBankAccountGetStoreDisplay = function(uuid) { | ||||||
|  |       for (let store of this.quickbooksBankAccountStoreOptions) { | ||||||
|  |           if (store.uuid == uuid) { | ||||||
|  |               return store.display | ||||||
|  |           } | ||||||
|  |       } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   ${form.component_studly}.methods.quickbooksBankAccountDelete = function(row) { | ||||||
|  |       if (confirm("Really delete this account number?")) { | ||||||
|  |           let i = this.quickbooksBankAccountsData.indexOf(row) | ||||||
|  |           this.quickbooksBankAccountsData.splice(i, 1) | ||||||
|  |           this.quickbooksBankAccountShowDialog = false | ||||||
|  |       } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
							
								
								
									
										58
									
								
								tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_widget.mako
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_widget.mako
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | ||||||
|  | ## -*- coding: utf-8; -*- | ||||||
|  | 
 | ||||||
|  | <div style="display: flex;"> | ||||||
|  |   <span style="flex-grow: 1;"></span> | ||||||
|  |   <b-button type="is-primary" | ||||||
|  |             icon-pack="fas" | ||||||
|  |             icon-left="plus" | ||||||
|  |             @click="quickbooksBankAccountCreate()"> | ||||||
|  |     Add Account | ||||||
|  |   </b-button> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | ${grid.render_table_element(data_prop='quickbooksBankAccountsData')|n} | ||||||
|  | 
 | ||||||
|  | <b-modal has-modal-card | ||||||
|  |          :active.sync="quickbooksBankAccountShowDialog"> | ||||||
|  |   <div class="modal-card"> | ||||||
|  | 
 | ||||||
|  |     <header class="modal-card-head"> | ||||||
|  |       <p class="modal-card-title">Quickbooks Bank Account</p> | ||||||
|  |     </header> | ||||||
|  | 
 | ||||||
|  |     <section class="modal-card-body"> | ||||||
|  | 
 | ||||||
|  |       <b-field label="Store" | ||||||
|  |                :type="{'is-danger': !quickbooksBankAccountStore}"> | ||||||
|  |         <b-select v-model="quickbooksBankAccountStore" | ||||||
|  |                   :disabled="quickbooksBankAccountEditing"> | ||||||
|  |           <option v-for="store in quickbooksBankAccountStoreOptions" | ||||||
|  |                   :key="store.uuid" | ||||||
|  |                   :value="store.uuid"> | ||||||
|  |             {{ store.display }} | ||||||
|  |           </option> | ||||||
|  |         </b-select> | ||||||
|  |       </b-field> | ||||||
|  | 
 | ||||||
|  |       <b-field label="Account Number" | ||||||
|  |                :type="{'is-danger': !quickbooksBankAccountNumber}"> | ||||||
|  |         <b-input v-model="quickbooksBankAccountNumber" | ||||||
|  |                  ref="quickbooksBankAccountNumber" /> | ||||||
|  |       </b-field> | ||||||
|  | 
 | ||||||
|  |     </section> | ||||||
|  | 
 | ||||||
|  |     <footer class="modal-card-foot"> | ||||||
|  |       <b-button type="is-primary" | ||||||
|  |                 @click="quickbooksBankAccountSave()" | ||||||
|  |                 :disabled="quickbooksBankAccountSaveDisabled" | ||||||
|  |                 icon-pack="fas" | ||||||
|  |                 icon-left="save"> | ||||||
|  |         Update | ||||||
|  |       </b-button> | ||||||
|  |       <b-button @click="quickbooksBankAccountShowDialog = false"> | ||||||
|  |         Cancel | ||||||
|  |       </b-button> | ||||||
|  |     </footer> | ||||||
|  |   </div> | ||||||
|  | </b-modal> | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| ################################################################################ | ################################################################################ | ||||||
| # | # | ||||||
| #  Rattail -- Retail Software Framework | #  Rattail -- Retail Software Framework | ||||||
| #  Copyright © 2010-2022 Lance Edgar | #  Copyright © 2010-2024 Lance Edgar | ||||||
| # | # | ||||||
| #  This file is part of Rattail. | #  This file is part of Rattail. | ||||||
| # | # | ||||||
|  | @ -24,6 +24,14 @@ | ||||||
| Vendor views, w/ Quickbooks integration | Vendor views, w/ Quickbooks integration | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
|  | import json | ||||||
|  | 
 | ||||||
|  | import colander | ||||||
|  | from deform import widget as dfwidget | ||||||
|  | from pyramid.renderers import render | ||||||
|  | from webhelpers2.html import HTML, tags | ||||||
|  | 
 | ||||||
|  | from tailbone import grids | ||||||
| from tailbone.views import ViewSupplement | from tailbone.views import ViewSupplement | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -44,14 +52,138 @@ class VendorViewSupplement(ViewSupplement): | ||||||
|         g.set_filter('quickbooks_terms', model.QuickbooksVendor.quickbooks_terms) |         g.set_filter('quickbooks_terms', model.QuickbooksVendor.quickbooks_terms) | ||||||
| 
 | 
 | ||||||
|     def configure_form(self, f): |     def configure_form(self, f): | ||||||
|  | 
 | ||||||
|  |         # quickbooks_name | ||||||
|         f.append('quickbooks_name') |         f.append('quickbooks_name') | ||||||
|  | 
 | ||||||
|  |         # quickbooks_bank_account | ||||||
|         f.append('quickbooks_bank_account') |         f.append('quickbooks_bank_account') | ||||||
|  | 
 | ||||||
|  |         # quickbooks_bank_accounts | ||||||
|  |         f.append('quickbooks_bank_accounts_') | ||||||
|  |         f.set_renderer('quickbooks_bank_accounts_', self.render_quickbooks_bank_accounts) | ||||||
|  |         f.set_node('quickbooks_bank_accounts_', BankAccounts()) | ||||||
|  |         f.set_widget('quickbooks_bank_accounts_', BankAccountsWidget()) | ||||||
|  | 
 | ||||||
|  |         # quickbooks_terms | ||||||
|         f.append('quickbooks_terms') |         f.append('quickbooks_terms') | ||||||
| 
 | 
 | ||||||
|  |     def render_quickbooks_bank_accounts(self, vendor, field): | ||||||
|  |         accounts = getattr(vendor, 'quickbooks_bank_accounts') | ||||||
|  |         if accounts: | ||||||
|  |             g = make_accounts_grid() | ||||||
|  |             return HTML.literal(g.render_table_element(data_prop='quickbooksBankAccountsData')) | ||||||
|  | 
 | ||||||
|  |     def objectify(self, vendor, form, data): | ||||||
|  |         model = self.model | ||||||
|  |         old_accounts = vendor.quickbooks_bank_accounts | ||||||
|  |         new_accounts = data['quickbooks_bank_accounts_'] | ||||||
|  | 
 | ||||||
|  |         for new_account in new_accounts: | ||||||
|  |             old_account = old_accounts.get(new_account['store_uuid']) | ||||||
|  |             if old_account: | ||||||
|  |                 if old_account.account_number != new_account['account_number']: | ||||||
|  |                     old_account.account_number = new_account['account_number'] | ||||||
|  |             else: | ||||||
|  |                 account = model.QuickbooksVendorBankAccount() | ||||||
|  |                 account.store_uuid = new_account['store_uuid'] | ||||||
|  |                 account.account_number = new_account['account_number'] | ||||||
|  |                 vendor.quickbooks_bank_accounts[account.store_uuid] = account | ||||||
|  |                 self.Session.add(account) | ||||||
|  | 
 | ||||||
|  |         final_store_uuids = set([a['store_uuid'] for a in new_accounts]) | ||||||
|  |         for old_account in list(vendor.quickbooks_bank_accounts.values()): | ||||||
|  |             if old_account.store_uuid not in final_store_uuids: | ||||||
|  |                 self.Session.delete(old_account) | ||||||
|  | 
 | ||||||
|  |         return vendor | ||||||
|  | 
 | ||||||
|  |     def template_kwargs(self, **kwargs): | ||||||
|  |         app = self.get_rattail_app() | ||||||
|  |         form = kwargs.get('form') | ||||||
|  |         if form: | ||||||
|  | 
 | ||||||
|  |             # quickbooks bank accounts | ||||||
|  |             vendor = kwargs['instance'] | ||||||
|  |             accounts = [] | ||||||
|  |             for account in vendor.quickbooks_bank_accounts.values(): | ||||||
|  |                 store = account.store | ||||||
|  |                 accounts.append({ | ||||||
|  |                     'uuid': account.uuid, | ||||||
|  |                     'store': f'{store.id} - {store.name}', | ||||||
|  |                     'store_uuid': store.uuid, | ||||||
|  |                     'store_id': store.id, | ||||||
|  |                     'store_name': store.name, | ||||||
|  |                     'account_number': account.account_number, | ||||||
|  |                 }) | ||||||
|  |             accounts.sort(key=lambda a: a['store_id']) | ||||||
|  |             # nb. this is needed for widget *and* readonly template | ||||||
|  |             form.set_json_data('quickbooksBankAccountsData', accounts) | ||||||
|  |             # TODO: these are needed by widget | ||||||
|  |             stores = [] | ||||||
|  |             for store in app.get_active_stores(self.Session()): | ||||||
|  |                 stores.append({ | ||||||
|  |                     'uuid': store.uuid, | ||||||
|  |                     'display': f'{store.id} - {store.name}', | ||||||
|  |                 }) | ||||||
|  |             form.include_template('/vendors/quickbooks_bank_accounts_js.mako', { | ||||||
|  |                 'store_options': stores, | ||||||
|  |             }) | ||||||
|  | 
 | ||||||
|  |         return kwargs | ||||||
|  | 
 | ||||||
|     def get_version_child_classes(self): |     def get_version_child_classes(self): | ||||||
|         model = self.model |         model = self.model | ||||||
|         return [model.QuickbooksVendor] |         return [model.QuickbooksVendor] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def make_accounts_grid(): | ||||||
|  |     g = grids.Grid('quickbooks_bank_accounts', | ||||||
|  |                    [],         # empty data | ||||||
|  |                    columns=[ | ||||||
|  |                        'store', | ||||||
|  |                        'account_number', | ||||||
|  |                    ]) | ||||||
|  |     return g | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BankAccount(colander.MappingSchema): | ||||||
|  | 
 | ||||||
|  |     store_uuid = colander.SchemaNode(colander.String()) | ||||||
|  | 
 | ||||||
|  |     account_number = colander.SchemaNode(colander.String()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BankAccounts(colander.SequenceSchema): | ||||||
|  | 
 | ||||||
|  |     account = BankAccount() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BankAccountsWidget(dfwidget.Widget): | ||||||
|  | 
 | ||||||
|  |     def serialize(self, field, cstruct, **kw): | ||||||
|  |         g = make_accounts_grid() | ||||||
|  | 
 | ||||||
|  |         g.main_actions.append( | ||||||
|  |             grids.GridAction('edit', icon='edit', | ||||||
|  |                              click_handler='quickbooksBankAccountEdit(props.row)')) | ||||||
|  | 
 | ||||||
|  |         g.main_actions.append( | ||||||
|  |             grids.GridAction('delete', icon='trash', | ||||||
|  |                              click_handler='quickbooksBankAccountDelete(props.row)')) | ||||||
|  | 
 | ||||||
|  |         widget = render('/vendors/quickbooks_bank_accounts_widget.mako', { | ||||||
|  |             'grid': g, | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         return HTML.tag('div', c=[ | ||||||
|  |             HTML.literal(widget), | ||||||
|  |             tags.hidden(field.name, **{':value': "quickbooksBankAccountsFinal"}), | ||||||
|  |         ]) | ||||||
|  | 
 | ||||||
|  |     def deserialize(self, field, pstruct): | ||||||
|  |         return json.loads(pstruct) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def includeme(config): | def includeme(config): | ||||||
|     VendorViewSupplement.defaults(config) |     VendorViewSupplement.defaults(config) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar