diff --git a/.gitignore b/.gitignore index 2e6159c..ea1abef 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ +*~ +*.pyc +dist/ tailbone_quickbooks.egg-info/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 900982b..72cab08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to tailbone-quickbooks will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.2.0 (2024-06-11) + +### Feat + +- switch from setup.cfg to pyproject.toml + hatchling + +## [0.1.7] - 2024-06-03 +### Changed +- Add support for Vendor -> Quickbooks Bank Accounts field. + ## [0.1.6] - 2023-08-29 ### Changed - Mark exportable invoice as deleted, instead of actually deleting. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a0c37d --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ + +# tailbone-quickbooks + +Rattail is a retail software framework, released under the GNU General +Public License. + +This package contains software interfaces for +[Quickbooks](https://quickbooks.intuit.com/). + +Please see the [Rattail Project](https://rattailproject.org/) for more +information. diff --git a/README.rst b/README.rst deleted file mode 100644 index 343f4bf..0000000 --- a/README.rst +++ /dev/null @@ -1,14 +0,0 @@ - -tailbone-quickbooks -=================== - -Rattail is a retail software framework, released under the GNU General -Public License. - -This package contains software interfaces for `Quickbooks`_. - -.. _`Quickbooks`: https://quickbooks.intuit.com/ - -Please see the `Rattail Project`_ for more information. - -.. _`Rattail Project`: https://rattailproject.org/ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c3a0bb4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + + +[project] +name = "tailbone-quickbooks" +version = "0.2.0" +description = "Tailbone integration package for Quickbooks" +readme = "README.md" +authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] +license = {text = "GNU GPL v3+"} +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Topic :: Office/Business", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ + "rattail-quickbooks", + "Tailbone", +] + + +[project.urls] +Homepage = "https://rattailproject.org" +Repository = "https://kallithea.rattailproject.org/rattail-project/tailbone-quickbooks" +Changelog = "https://kallithea.rattailproject.org/rattail-project/tailbone-quickbooks/files/master/CHANGELOG.md" + + +[tool.commitizen] +version_provider = "pep621" +tag_format = "v$version" +update_changelog_on_bump = true diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 0ed94fe..0000000 --- a/setup.cfg +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8; -*- - -[metadata] -name = tailbone-quickbooks -version = attr: tailbone_quickbooks.__version__ -author = Lance Edgar -author_email = lance@edbob.org -url = https://rattailproject.org/ -description = Tailbone integration package for Quickbooks -long_description = file: README.rst -classifiers = - Development Status :: 3 - Alpha - Environment :: Web Environment - Intended Audience :: Developers - License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) - Natural Language :: English - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3 - Topic :: Office/Business - Topic :: Software Development :: Libraries :: Python Modules - - -[options] -install_requires = - rattail-quickbooks - Tailbone - -packages = find: -include_package_data = True diff --git a/setup.py b/setup.py deleted file mode 100644 index cf9b79d..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2023 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ -""" -tailbone-quickbooks setup script -""" - -from setuptools import setup - -setup() diff --git a/tailbone_quickbooks/_version.py b/tailbone_quickbooks/_version.py index 955f1d8..4905e5d 100644 --- a/tailbone_quickbooks/_version.py +++ b/tailbone_quickbooks/_version.py @@ -1,3 +1,9 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.6' +try: + from importlib.metadata import version +except ImportError: + from importlib_metadata import version + + +__version__ = version('tailbone-quickbooks') diff --git a/tailbone_quickbooks/templates/quickbooks/exportable-invoices/index.mako b/tailbone_quickbooks/templates/quickbooks/exportable-invoices/index.mako index dc6ec4f..0469f32 100644 --- a/tailbone_quickbooks/templates/quickbooks/exportable-invoices/index.mako +++ b/tailbone_quickbooks/templates/quickbooks/exportable-invoices/index.mako @@ -97,7 +97,7 @@ this.toggleRows(uuids, checked) } - TailboneGrid.methods.allChecked = function(checkedList) { + ${grid.vue_component}.methods.allChecked = function(checkedList) { // (de-)select all visible invoices when header checkbox is clicked let checked = !!checkedList.length this.toggleRows(this.allRowUUIDs(), checked) 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} + + + + diff --git a/tailbone_quickbooks/views/vendors.py b/tailbone_quickbooks/views/vendors.py index 7c3e70b..0ce3230 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,139 @@ 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(request=self.request)) + + # 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(self.request) + 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(request): + g = grids.Grid(request, + key='quickbooks_bank_accounts', + data=[], # 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(self.request) + + g.actions.append( + grids.GridAction(self.request, 'edit', icon='edit', + click_handler='quickbooksBankAccountEdit(props.row)')) + + g.actions.append( + grids.GridAction(self.request, '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) diff --git a/tasks.py b/tasks.py index 9dbdd44..2a3f1e0 100644 --- a/tasks.py +++ b/tasks.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,24 +24,18 @@ Tasks for tailbone-quickbooks """ -import os - from invoke import task -here = os.path.abspath(os.path.dirname(__file__)) -exec(open(os.path.join(here, 'tailbone_quickbooks', '_version.py')).read()) - - @task def release(c): """ Release a new version of tailbone-quickbooks """ - # rebuild local tar.gz file for distribution + # rebuild package + c.run('rm -rf dist') c.run('rm -rf tailbone_quickbooks.egg-info') - c.run('python setup.py sdist --formats=gztar') + c.run('python -m build --sdist') # upload to PyPI - filename = 'tailbone-quickbooks-{}.tar.gz'.format(__version__) - c.run('twine upload dist/{}'.format(filename)) + c.run('twine upload dist/*')