diff --git a/.gitignore b/.gitignore
index ea1abef..2e6159c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1 @@
-*~
-*.pyc
-dist/
tailbone_quickbooks.egg-info/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72cab08..99cac9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,39 +5,6 @@ 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.
-
-## [0.1.5] - 2023-06-01
-### Changed
-- Stop passing `newstyle` kwarg to `Form.validate()`.
-- Replace `setup.py` contents with `setup.cfg`.
-
-## [0.1.4] - 2023-02-21
-### Changed
-- Show invoice amounts as currency.
-
-## [0.1.3] - 2023-02-20
-### Changed
-- Refactor `Query.get()` => `Session.get()` per SQLAlchemy 1.4.
-- Catch/display error when exporting QB invoices.
-- Fix fieldname for invoice view.
-
-## [0.1.2] - 2023-01-26
-### Changed
-- Commit session when refreshing invoices.
-
## [0.1.1] - 2023-01-25
### Changed
- Only include "export invoice" logic if user has perm.
diff --git a/README.md b/README.md
deleted file mode 100644
index 8a0c37d..0000000
--- a/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-# 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
new file mode 100644
index 0000000..343f4bf
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,14 @@
+
+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
deleted file mode 100644
index c3a0bb4..0000000
--- a/pyproject.toml
+++ /dev/null
@@ -1,41 +0,0 @@
-
-[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.py b/setup.py
new file mode 100644
index 0000000..03eed1f
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8; -*-
+################################################################################
+#
+# Rattail -- Retail Software Framework
+# Copyright © 2010-2022 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
+"""
+
+import os
+from setuptools import setup, find_packages
+
+
+here = os.path.abspath(os.path.dirname(__file__))
+exec(open(os.path.join(here, 'tailbone_quickbooks', '_version.py')).read())
+README = open(os.path.join(here, 'README.rst')).read()
+
+
+requires = [
+ #
+ # Version numbers within comments below have specific meanings.
+ # Basically the 'low' value is a "soft low," and 'high' a "soft high."
+ # In other words:
+ #
+ # If either a 'low' or 'high' value exists, the primary point to be
+ # made about the value is that it represents the most current (stable)
+ # version available for the package (assuming typical public access
+ # methods) whenever this project was started and/or documented.
+ # Therefore:
+ #
+ # If a 'low' version is present, you should know that attempts to use
+ # versions of the package significantly older than the 'low' version
+ # may not yield happy results. (A "hard" high limit may or may not be
+ # indicated by a true version requirement.)
+ #
+ # Similarly, if a 'high' version is present, and especially if this
+ # project has laid dormant for a while, you may need to refactor a bit
+ # when attempting to support a more recent version of the package. (A
+ # "hard" low limit should be indicated by a true version requirement
+ # when a 'high' version is present.)
+ #
+ # In any case, developers and other users are encouraged to play
+ # outside the lines with regard to these soft limits. If bugs are
+ # encountered then they should be filed as such.
+ #
+ # package # low high
+
+ 'rattail-quickbooks', # 0.1.0
+ 'Tailbone', # 0.8.199
+]
+
+
+setup(
+ name = "tailbone-quickbooks",
+ version = __version__,
+ author = "Lance Edgar",
+ author_email = "lance@edbob.org",
+ url = "https://rattailproject.org/",
+ description = "Tailbone integration package for Quickbooks",
+ long_description = README,
+
+ 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',
+ ],
+
+ install_requires = requires,
+ packages = find_packages(),
+ include_package_data = True,
+)
diff --git a/tailbone_quickbooks/_version.py b/tailbone_quickbooks/_version.py
index 4905e5d..4984097 100644
--- a/tailbone_quickbooks/_version.py
+++ b/tailbone_quickbooks/_version.py
@@ -1,9 +1,3 @@
# -*- coding: utf-8; -*-
-try:
- from importlib.metadata import version
-except ImportError:
- from importlib_metadata import version
-
-
-__version__ = version('tailbone-quickbooks')
+__version__ = '0.1.1'
diff --git a/tailbone_quickbooks/templates/quickbooks/exportable-invoices/index.mako b/tailbone_quickbooks/templates/quickbooks/exportable-invoices/index.mako
index 0469f32..dc6ec4f 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)
}
- ${grid.vue_component}.methods.allChecked = function(checkedList) {
+ TailboneGrid.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
deleted file mode 100644
index 109bb9e..0000000
--- a/tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_js.mako
+++ /dev/null
@@ -1,86 +0,0 @@
-## -*- 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
deleted file mode 100644
index 32a6dbe..0000000
--- a/tailbone_quickbooks/templates/vendors/quickbooks_bank_accounts_widget.mako
+++ /dev/null
@@ -1,58 +0,0 @@
-## -*- coding: utf-8; -*-
-
-
-
-
- Add Account
-
-
-
-${grid.render_table_element(data_prop='quickbooksBankAccountsData')|n}
-
-
-
-
-
- Quickbooks Bank Account
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tailbone_quickbooks/views/quickbooks/invoices.py b/tailbone_quickbooks/views/quickbooks/invoices.py
index c8ab414..a70a8ec 100644
--- a/tailbone_quickbooks/views/quickbooks/invoices.py
+++ b/tailbone_quickbooks/views/quickbooks/invoices.py
@@ -54,8 +54,6 @@ class ExportableInvoiceView(MasterView):
model_class = QuickbooksExportableInvoice
route_prefix = 'quickbooks.exportable_invoices'
url_prefix = '/quickbooks/exportable-invoices'
- editable = False
- bulk_deletable = True
has_versions = True
labels = {
@@ -89,7 +87,7 @@ class ExportableInvoiceView(MasterView):
'supplies_amount',
'quickbooks_vendor_name',
'quickbooks_vendor_terms',
- 'quickbooks_bank_account',
+ 'quickbooks_vendor_bank_account',
'quickbooks_export_template',
'status_code',
'status_text',
@@ -149,10 +147,8 @@ class ExportableInvoiceView(MasterView):
g.set_sort_defaults('invoice_date', 'desc')
g.set_link('invoice_date')
- # currency fields
+ # invoice_total
g.set_type('invoice_total', 'currency')
- g.set_type('shipping_amount', 'currency')
- g.set_type('supplies_amount', 'currency')
# status_code
g.set_enum('status_code', model.QuickbooksExportableInvoice.STATUS)
@@ -173,8 +169,6 @@ class ExportableInvoiceView(MasterView):
g.check_all_handler = 'allChecked'
def grid_extra_class(self, invoice, i):
- if invoice.deleted:
- return 'warning'
if not self.exportable(invoice):
if invoice.status_code in (invoice.STATUS_DEPTS_IGNORED,
invoice.STATUS_EXPORTED,
@@ -207,22 +201,6 @@ class ExportableInvoiceView(MasterView):
"""
return invoice.status_code == invoice.STATUS_EXPORTABLE
- def deletable_instance(self, invoice):
- if invoice.deleted:
- return False
- if invoice.exported:
- return False
- return True
-
- def delete_instance(self, invoice):
- app = self.get_rattail_app()
- session = app.get_session(invoice)
- invoice.deleted = app.make_utc()
- # nb. when bulk-deleting, user is in different session
- invoice.deleted_by = session.merge(self.request.user)
- invoice.status_code = invoice.STATUS_DELETED
- session.flush()
-
def configure_form(self, f):
super(ExportableInvoiceView, self).configure_form(f)
model = self.model
@@ -234,10 +212,8 @@ class ExportableInvoiceView(MasterView):
# vendor
f.set_renderer('vendor', self.render_vendor)
- # currency fields
+ # invoice_total
f.set_type('invoice_total', 'currency')
- f.set_type('shipping_amount', 'currency')
- f.set_type('supplies_amount', 'currency')
# status
f.set_enum('status_code', model.QuickbooksExportableInvoice.STATUS)
@@ -342,7 +318,6 @@ class ExportableInvoiceView(MasterView):
progress.session.save()
else:
- session.commit()
if progress:
progress.session.load()
progress.session['complete'] = True
@@ -360,13 +335,13 @@ class ExportableInvoiceView(MasterView):
model = self.model
form = forms.Form(schema=ToggleInvoices(), request=self.request)
- if not form.validate():
+ if not form.validate(newstyle=True):
return {'error': "Form did not validate"}
uuids = form.validated['uuids'].split(',')
invoices = []
for uuid in uuids:
- invoice = self.Session.get(model.QuickbooksExportableInvoice, uuid)
+ invoice = self.Session.query(model.QuickbooksExportableInvoice).get(uuid)
if invoice and self.exportable(invoice):
invoices.append(invoice)
if not invoices:
@@ -388,13 +363,13 @@ class ExportableInvoiceView(MasterView):
model = self.model
form = forms.Form(schema=ToggleInvoices(), request=self.request)
- if not form.validate():
+ if not form.validate(newstyle=True):
return {'error': "Form did not validate"}
uuids = form.validated['uuids'].split(',')
invoices = []
for uuid in uuids:
- invoice = self.Session.get(model.QuickbooksExportableInvoice, uuid)
+ invoice = self.Session.query(model.QuickbooksExportableInvoice).get(uuid)
if invoice and self.exportable(invoice):
invoices.append(invoice)
if not invoices:
@@ -423,7 +398,7 @@ class ExportableInvoiceView(MasterView):
invoices = []
for uuid in selected:
- invoice = self.Session.get(model.QuickbooksExportableInvoice, uuid)
+ invoice = self.Session.query(model.QuickbooksExportableInvoice).get(uuid)
if invoice and invoice.status_code == invoice.STATUS_EXPORTABLE:
invoices.append(invoice)
else:
@@ -435,16 +410,7 @@ class ExportableInvoiceView(MasterView):
return self.redirect(self.get_index_url())
# perform actual export and capture result
- try:
- result = self.do_export(invoices)
-
- except Exception as error:
- log.warning("failed to export invoices: %s",
- [inv.uuid for inv in invoices],
- exc_info=True)
- self.request.session.flash(simple_error(error), 'error')
- return self.redirect(self.request.get_referrer(
- default=self.get_index_url()))
+ result = self.do_export(invoices)
# clear out current checkbox selection
self.set_selected(set())
diff --git a/tailbone_quickbooks/views/vendors.py b/tailbone_quickbooks/views/vendors.py
index 0ce3230..7c3e70b 100644
--- a/tailbone_quickbooks/views/vendors.py
+++ b/tailbone_quickbooks/views/vendors.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2024 Lance Edgar
+# Copyright © 2010-2022 Lance Edgar
#
# This file is part of Rattail.
#
@@ -24,14 +24,6 @@
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
@@ -52,139 +44,14 @@ 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 2a3f1e0..9dbdd44 100644
--- a/tasks.py
+++ b/tasks.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2024 Lance Edgar
+# Copyright © 2010-2022 Lance Edgar
#
# This file is part of Rattail.
#
@@ -24,18 +24,24 @@
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 package
- c.run('rm -rf dist')
+ # rebuild local tar.gz file for distribution
c.run('rm -rf tailbone_quickbooks.egg-info')
- c.run('python -m build --sdist')
+ c.run('python setup.py sdist --formats=gztar')
# upload to PyPI
- c.run('twine upload dist/*')
+ filename = 'tailbone-quickbooks-{}.tar.gz'.format(__version__)
+ c.run('twine upload dist/{}'.format(filename))