diff --git a/tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_js.mako b/tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_js.mako
new file mode 100644
index 0000000..109bb9e
--- /dev/null
+++ b/tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_js.mako
@@ -0,0 +1,86 @@
+## -*- coding: utf-8; -*-
+
+
diff --git a/tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_widget.mako b/tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_widget.mako
new file mode 100644
index 0000000..32a6dbe
--- /dev/null
+++ b/tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_widget.mako
@@ -0,0 +1,58 @@
+## -*- coding: utf-8; -*-
+
+
+
+
+ Add Account
+
+
+
+${grid.render_table_element(data_prop='quickbooksBankAccountsData')|n}
+
+
+
+
+
+ Quickbooks Bank Account
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tailbone_quickbooks/views/vendors.py b/tailbone_quickbooks/views/vendors.py
index 7c3e70b..c73196d 100644
--- a/tailbone_quickbooks/views/vendors.py
+++ b/tailbone_quickbooks/views/vendors.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2022 Lance Edgar
+# Copyright © 2010-2024 Lance Edgar
#
# This file is part of Rattail.
#
@@ -24,6 +24,14 @@
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
@@ -44,14 +52,138 @@ class VendorViewSupplement(ViewSupplement):
g.set_filter('quickbooks_terms', model.QuickbooksVendor.quickbooks_terms)
def configure_form(self, f):
+
+ # quickbooks_name
f.append('quickbooks_name')
+
+ # 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')
+ 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):
model = self.model
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):
VendorViewSupplement.defaults(config)