Initial views, and basic invoice export UI tools
This commit is contained in:
commit
7384bd1fea
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
tailbone_quickbooks.egg-info/
|
10
CHANGELOG.md
Normal file
10
CHANGELOG.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
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).
|
||||||
|
|
||||||
|
## [0.1.0] - ??
|
||||||
|
### Added
|
||||||
|
- Initial version.
|
3
MANIFEST.in
Normal file
3
MANIFEST.in
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
include *.md
|
||||||
|
include *.rst
|
||||||
|
recursive-include tailbone_quickbooks/templates *.mako
|
14
README.rst
Normal file
14
README.rst
Normal file
|
@ -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/
|
95
setup.py
Normal file
95
setup.py
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
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,
|
||||||
|
)
|
27
tailbone_quickbooks/__init__.py
Normal file
27
tailbone_quickbooks/__init__.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
tailbone-quickbooks package root
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ._version import __version__
|
3
tailbone_quickbooks/_version.py
Normal file
3
tailbone_quickbooks/_version.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
|
||||||
|
__version__ = '0.1.0'
|
|
@ -0,0 +1,88 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/master/index.mako" />
|
||||||
|
|
||||||
|
<%def name="grid_tools()">
|
||||||
|
${parent.grid_tools()}
|
||||||
|
${h.form(url('{}.export'.format(route_prefix)), **{'@submit': 'exportingInvoices = true'})}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
% if master.has_perm('export'):
|
||||||
|
<b-button type="is-primary"
|
||||||
|
native-type="submit"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="arrow-circle-right"
|
||||||
|
:disabled="exportingInvoices || !exportingInvoicesCount">
|
||||||
|
{{ exportingInvoices ? "Working, please wait..." : `Export ${'$'}{this.exportingInvoicesCount} Invoices` }}
|
||||||
|
</b-button>
|
||||||
|
% endif
|
||||||
|
${h.end_form()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_this_page_vars()">
|
||||||
|
${parent.modify_this_page_vars()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
${grid.component_studly}Data.exportingInvoices = false
|
||||||
|
${grid.component_studly}Data.exportingInvoicesCount = ${json.dumps(len(selected))|n}
|
||||||
|
|
||||||
|
${grid.component_studly}.methods.toggleRows = function(uuids, checked) {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
let url = checked ? '${url('{}.select'.format(route_prefix))}' : '${url('{}.deselect'.format(route_prefix))}'
|
||||||
|
let params = {
|
||||||
|
uuids: uuids.join(','),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submitForm(url, params, response => {
|
||||||
|
this.exportingInvoicesCount = response.data.selected_count
|
||||||
|
this.loading = false
|
||||||
|
}, response => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
${grid.component_studly}.methods.rowChecked = function(checkedList, row) {
|
||||||
|
// skip this logic if triggered by header ("all") checkbox
|
||||||
|
if (!row) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// are we toggling ON or OFF?
|
||||||
|
let checked = checkedList.includes(row)
|
||||||
|
|
||||||
|
// collect all currently "checked" uuids
|
||||||
|
let checkedUUIDs = []
|
||||||
|
for (row of checkedList) {
|
||||||
|
checkedUUIDs.push(row.uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// even though we are given only the one row which was associated with
|
||||||
|
// the event, it is possible the user did a shift+click operation. and
|
||||||
|
// since we cannot know for sure, we must assume so, which means we
|
||||||
|
// must send "all" relevant uuids as we are not given the specific
|
||||||
|
// range involved for shift+click
|
||||||
|
let uuids = []
|
||||||
|
if (checked) {
|
||||||
|
// if toggling ON then we must send all "checked" rows
|
||||||
|
uuids = checkedUUIDs
|
||||||
|
} else {
|
||||||
|
// if toggling OFF then we must send all "unchecked" rows
|
||||||
|
for (uuid of this.allRowUUIDs()) {
|
||||||
|
if (!checkedUUIDs.includes(uuid)) {
|
||||||
|
uuids.push(uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.toggleRows(uuids, checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
TailboneGrid.methods.allChecked = function(checkedList) {
|
||||||
|
// (de-)select all visible invoices when header checkbox is clicked
|
||||||
|
let checked = !!checkedList.length
|
||||||
|
this.toggleRows(this.allRowUUIDs(), checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -0,0 +1,29 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/master/view.mako" />
|
||||||
|
|
||||||
|
<%def name="render_form_buttons()">
|
||||||
|
% if master.has_perm('export'):
|
||||||
|
${h.form(master.get_action_url('refresh', instance), **{'@submit': 'refreshingInvoice = true'})}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
<b-button type="is-primary"
|
||||||
|
native-type="submit"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="redo"
|
||||||
|
:disabled="refreshingInvoice">
|
||||||
|
{{ refreshingInvoice ? "Working, please wait..." : "Refresh Data" }}
|
||||||
|
</b-button>
|
||||||
|
${h.end_form()}
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_this_page_vars()">
|
||||||
|
${parent.modify_this_page_vars()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
${form.component_studly}Data.refreshingInvoice = false
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -0,0 +1,11 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/master/view.mako" />
|
||||||
|
|
||||||
|
<%def name="context_menu_items()">
|
||||||
|
% if request.has_perm('quickbooks.exportable_invoices.list'):
|
||||||
|
<li>${h.link_to("Back to Exportable Invoices", url('quickbooks.exportable_invoices'))}</li>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
30
tailbone_quickbooks/views/__init__.py
Normal file
30
tailbone_quickbooks/views/__init__.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Views w/ Quickbooks integration
|
||||||
|
"""
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
config.include('tailbone_quickbooks.views.departments')
|
||||||
|
config.include('tailbone_quickbooks.views.stores')
|
||||||
|
config.include('tailbone_quickbooks.views.vendors')
|
55
tailbone_quickbooks/views/departments.py
Normal file
55
tailbone_quickbooks/views/departments.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Department views, w/ Quickbooks integration
|
||||||
|
"""
|
||||||
|
|
||||||
|
from tailbone.views import ViewSupplement
|
||||||
|
|
||||||
|
|
||||||
|
class DepartmentViewSupplement(ViewSupplement):
|
||||||
|
"""
|
||||||
|
Department view supplement for Quickbooks integration
|
||||||
|
"""
|
||||||
|
route_prefix = 'departments'
|
||||||
|
|
||||||
|
def get_grid_query(self, query):
|
||||||
|
model = self.model
|
||||||
|
return query.outerjoin(model.QuickbooksDepartment)
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
model = self.model
|
||||||
|
g.set_filter('quickbooks_expense_account', model.QuickbooksDepartment.quickbooks_expense_account)
|
||||||
|
g.set_filter('quickbooks_expense_class', model.QuickbooksDepartment.quickbooks_expense_class)
|
||||||
|
|
||||||
|
def configure_form(self, f):
|
||||||
|
f.append('quickbooks_expense_account')
|
||||||
|
f.append('quickbooks_expense_class')
|
||||||
|
|
||||||
|
def get_version_child_classes(self):
|
||||||
|
model = self.model
|
||||||
|
return [model.QuickbooksDepartment]
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
DepartmentViewSupplement.defaults(config)
|
0
tailbone_quickbooks/views/quickbooks/__init__.py
Normal file
0
tailbone_quickbooks/views/quickbooks/__init__.py
Normal file
506
tailbone_quickbooks/views/quickbooks/invoices.py
Normal file
506
tailbone_quickbooks/views/quickbooks/invoices.py
Normal file
|
@ -0,0 +1,506 @@
|
||||||
|
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Views for Quickbooks invoices
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from rattail_quickbooks.db.model import (QuickbooksExportableInvoice,
|
||||||
|
QuickbooksExportableInvoiceDistribution,
|
||||||
|
QuickbooksInvoiceExport)
|
||||||
|
from rattail.util import simple_error
|
||||||
|
|
||||||
|
import colander
|
||||||
|
from pyramid.httpexceptions import HTTPFound
|
||||||
|
|
||||||
|
from tailbone import forms
|
||||||
|
from tailbone.views import MasterView
|
||||||
|
from tailbone.views.exports import ExportMasterView
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ToggleInvoices(colander.MappingSchema):
|
||||||
|
uuids = colander.SchemaNode(colander.String())
|
||||||
|
|
||||||
|
|
||||||
|
class ExportableInvoiceView(MasterView):
|
||||||
|
"""
|
||||||
|
Master view for Quickbooks exportable invoices
|
||||||
|
"""
|
||||||
|
model_class = QuickbooksExportableInvoice
|
||||||
|
route_prefix = 'quickbooks.exportable_invoices'
|
||||||
|
url_prefix = '/quickbooks/exportable-invoices'
|
||||||
|
has_versions = True
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'store_id': "Store ID",
|
||||||
|
'vendor_id': "Vendor ID",
|
||||||
|
'txn_id': "Transaction ID",
|
||||||
|
'quickbooks_vendor_terms': "Vendor Terms",
|
||||||
|
'quickbooks_export_template': "Export Template",
|
||||||
|
'status_code': "Status",
|
||||||
|
}
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
'invoice_date',
|
||||||
|
'invoice_number',
|
||||||
|
'invoice_total',
|
||||||
|
'store',
|
||||||
|
'vendor',
|
||||||
|
'quickbooks_vendor_terms',
|
||||||
|
'quickbooks_export_template',
|
||||||
|
'status_code',
|
||||||
|
]
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
'store',
|
||||||
|
'vendor',
|
||||||
|
'txn_id',
|
||||||
|
'invoice_number',
|
||||||
|
'invoice_date',
|
||||||
|
'invoice_total',
|
||||||
|
'quickbooks_vendor_name',
|
||||||
|
'quickbooks_vendor_terms',
|
||||||
|
'quickbooks_vendor_bank_account',
|
||||||
|
'quickbooks_export_template',
|
||||||
|
'status_code',
|
||||||
|
'status_text',
|
||||||
|
'deleted',
|
||||||
|
'deleted_by',
|
||||||
|
'exported',
|
||||||
|
'exported_by',
|
||||||
|
]
|
||||||
|
|
||||||
|
has_rows = True
|
||||||
|
model_row_class = QuickbooksExportableInvoiceDistribution
|
||||||
|
rows_filterable = False
|
||||||
|
rows_pageable = False
|
||||||
|
rows_viewable = False
|
||||||
|
|
||||||
|
# TODO: this does not work right yet, e.g. clicking the View
|
||||||
|
# action link will still trigger row-click event
|
||||||
|
# clicking_row_checks_box = True
|
||||||
|
|
||||||
|
row_labels = {
|
||||||
|
'department_id': "Department ID",
|
||||||
|
'status_code': "Status",
|
||||||
|
}
|
||||||
|
|
||||||
|
row_grid_columns = [
|
||||||
|
'department_id',
|
||||||
|
'department',
|
||||||
|
'quickbooks_expense_account',
|
||||||
|
'quickbooks_expense_class',
|
||||||
|
'source_amount',
|
||||||
|
'calculated_percent',
|
||||||
|
'calculated_amount',
|
||||||
|
'status_code',
|
||||||
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
super(ExportableInvoiceView, self).configure_grid(g)
|
||||||
|
model = self.model
|
||||||
|
|
||||||
|
# store
|
||||||
|
g.set_joiner('store', lambda q: q.outerjoin(model.Store))
|
||||||
|
g.set_filter('store', model.Store.name)
|
||||||
|
g.set_sorter('store', model.Store.name)
|
||||||
|
|
||||||
|
# vendor
|
||||||
|
g.set_joiner('vendor', lambda q: q.outerjoin(model.Vendor))
|
||||||
|
g.set_filter('vendor', model.Vendor.name)
|
||||||
|
g.set_sorter('vendor', model.Vendor.name)
|
||||||
|
g.set_link('vendor')
|
||||||
|
|
||||||
|
# invoice_number
|
||||||
|
g.filters['invoice_number'].default_active = True
|
||||||
|
g.filters['invoice_number'].default_verb = 'contains'
|
||||||
|
g.set_link('invoice_number')
|
||||||
|
|
||||||
|
# invoice_date
|
||||||
|
g.set_sort_defaults('invoice_date', 'desc')
|
||||||
|
g.set_link('invoice_date')
|
||||||
|
|
||||||
|
# invoice_total
|
||||||
|
g.set_type('invoice_total', 'currency')
|
||||||
|
|
||||||
|
# status_code
|
||||||
|
g.set_enum('status_code', model.QuickbooksExportableInvoice.STATUS)
|
||||||
|
g.set_renderer('status_code', self.make_status_renderer(
|
||||||
|
model.QuickbooksExportableInvoice.STATUS))
|
||||||
|
|
||||||
|
# exported
|
||||||
|
g.filters['exported'].default_active = True
|
||||||
|
g.filters['exported'].default_verb = 'is_null'
|
||||||
|
|
||||||
|
# deleted
|
||||||
|
g.filters['deleted'].default_active = True
|
||||||
|
g.filters['deleted'].default_verb = 'is_null'
|
||||||
|
|
||||||
|
if self.has_perm('export'):
|
||||||
|
g.checkboxes = True
|
||||||
|
g.check_handler = 'rowChecked'
|
||||||
|
g.check_all_handler = 'allChecked'
|
||||||
|
|
||||||
|
def grid_extra_class(self, invoice, i):
|
||||||
|
if not self.exportable(invoice):
|
||||||
|
if invoice.status_code in (invoice.STATUS_DEPTS_IGNORED,
|
||||||
|
invoice.STATUS_EXPORTED,
|
||||||
|
invoice.STATUS_DELETED):
|
||||||
|
return 'notice'
|
||||||
|
return 'warning'
|
||||||
|
|
||||||
|
def checkbox(self, invoice):
|
||||||
|
return self.exportable(invoice)
|
||||||
|
|
||||||
|
def checked(self, invoice):
|
||||||
|
return invoice.uuid in self.get_selected()
|
||||||
|
|
||||||
|
def template_kwargs_index(self, **kwargs):
|
||||||
|
kwargs = super(ExportableInvoiceView, self).template_kwargs_index(**kwargs)
|
||||||
|
kwargs['selected'] = self.get_selected()
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_selected(self):
|
||||||
|
route_prefix = self.get_route_prefix()
|
||||||
|
return self.request.session.get('{}.selected'.format(route_prefix), set())
|
||||||
|
|
||||||
|
def set_selected(self, selected):
|
||||||
|
route_prefix = self.get_route_prefix()
|
||||||
|
self.request.session['{}.selected'.format(route_prefix)] = selected
|
||||||
|
|
||||||
|
def exportable(self, invoice):
|
||||||
|
"""
|
||||||
|
Return boolean indicating whether the given invoice is exportable.
|
||||||
|
"""
|
||||||
|
return invoice.status_code == invoice.STATUS_EXPORTABLE
|
||||||
|
|
||||||
|
def configure_form(self, f):
|
||||||
|
super(ExportableInvoiceView, self).configure_form(f)
|
||||||
|
model = self.model
|
||||||
|
invoice = f.model_instance
|
||||||
|
|
||||||
|
# store
|
||||||
|
f.set_renderer('store', self.render_store)
|
||||||
|
|
||||||
|
# vendor
|
||||||
|
f.set_renderer('vendor', self.render_vendor)
|
||||||
|
|
||||||
|
# invoice_total
|
||||||
|
f.set_type('invoice_total', 'currency')
|
||||||
|
|
||||||
|
# status
|
||||||
|
f.set_enum('status_code', model.QuickbooksExportableInvoice.STATUS)
|
||||||
|
|
||||||
|
# exported
|
||||||
|
if self.creating or not invoice.exported:
|
||||||
|
f.remove('exported', 'exported_by')
|
||||||
|
|
||||||
|
# deleted
|
||||||
|
if self.creating or not invoice.deleted:
|
||||||
|
f.remove('deleted', 'deleted_by')
|
||||||
|
|
||||||
|
def get_row_data(self, invoice):
|
||||||
|
model = self.model
|
||||||
|
return self.Session.query(model.QuickbooksExportableInvoiceDistribution)\
|
||||||
|
.filter(model.QuickbooksExportableInvoiceDistribution.invoice == invoice)
|
||||||
|
|
||||||
|
def get_parent(self, dist):
|
||||||
|
return dist.invoice
|
||||||
|
|
||||||
|
def configure_row_grid(self, g):
|
||||||
|
super(ExportableInvoiceView, self).configure_row_grid(g)
|
||||||
|
model = self.model
|
||||||
|
|
||||||
|
# department_id
|
||||||
|
g.set_sort_defaults('department_id')
|
||||||
|
|
||||||
|
# amounts etc.
|
||||||
|
g.set_type('source_amount', 'currency')
|
||||||
|
g.set_type('calculated_percent', 'percent')
|
||||||
|
g.set_type('calculated_amount', 'currency')
|
||||||
|
|
||||||
|
# status
|
||||||
|
g.set_enum('status_code', model.QuickbooksExportableInvoiceDistribution.STATUS)
|
||||||
|
|
||||||
|
def row_grid_extra_class(self, dist, i):
|
||||||
|
if dist.status_code in (dist.STATUS_DEPT_IGNORED,
|
||||||
|
dist.STATUS_EXPORTED):
|
||||||
|
return 'notice'
|
||||||
|
elif dist.status_code != dist.STATUS_EXPORTABLE:
|
||||||
|
return 'warning'
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
"""
|
||||||
|
View to refresh data for a single invoice.
|
||||||
|
"""
|
||||||
|
invoice = self.get_instance()
|
||||||
|
self.refresh_invoice(invoice)
|
||||||
|
return self.redirect(self.get_action_url('view', invoice))
|
||||||
|
|
||||||
|
def refresh_invoice(self, invoice):
|
||||||
|
"""
|
||||||
|
Logic to actually refresh data for the given invoice.
|
||||||
|
Implement as needed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def select(self):
|
||||||
|
"""
|
||||||
|
Mark one or more invoices as selected, within the current user's session.
|
||||||
|
"""
|
||||||
|
model = self.model
|
||||||
|
|
||||||
|
form = forms.Form(schema=ToggleInvoices(), request=self.request)
|
||||||
|
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.query(model.QuickbooksExportableInvoice).get(uuid)
|
||||||
|
if invoice and self.exportable(invoice):
|
||||||
|
invoices.append(invoice)
|
||||||
|
if not invoices:
|
||||||
|
return {'error': "Must specify one or more valid invoice UUIDs."}
|
||||||
|
|
||||||
|
selected = self.get_selected()
|
||||||
|
for invoice in invoices:
|
||||||
|
selected.add(invoice.uuid)
|
||||||
|
self.set_selected(selected)
|
||||||
|
return {
|
||||||
|
'ok': True,
|
||||||
|
'selected_count': len(selected),
|
||||||
|
}
|
||||||
|
|
||||||
|
def deselect(self):
|
||||||
|
"""
|
||||||
|
Mark one or more invoices as *not* selected, within the current user's session.
|
||||||
|
"""
|
||||||
|
model = self.model
|
||||||
|
|
||||||
|
form = forms.Form(schema=ToggleInvoices(), request=self.request)
|
||||||
|
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.query(model.QuickbooksExportableInvoice).get(uuid)
|
||||||
|
if invoice and self.exportable(invoice):
|
||||||
|
invoices.append(invoice)
|
||||||
|
if not invoices:
|
||||||
|
return {'error': "Must specify one or more valid invoice UUIDs."}
|
||||||
|
|
||||||
|
selected = self.get_selected()
|
||||||
|
for invoice in invoices:
|
||||||
|
selected.discard(invoice.uuid)
|
||||||
|
self.set_selected(selected)
|
||||||
|
return {
|
||||||
|
'ok': True,
|
||||||
|
'selected_count': len(selected),
|
||||||
|
}
|
||||||
|
|
||||||
|
def export(self):
|
||||||
|
"""
|
||||||
|
Export all currently-selected invoices.
|
||||||
|
"""
|
||||||
|
model = self.model
|
||||||
|
|
||||||
|
selected = self.get_selected()
|
||||||
|
if not selected:
|
||||||
|
self.request.session.flash("You must first select one or more "
|
||||||
|
"invoices.", 'error')
|
||||||
|
return self.redirect(self.get_index_url())
|
||||||
|
|
||||||
|
invoices = []
|
||||||
|
for uuid in selected:
|
||||||
|
invoice = self.Session.query(model.QuickbooksExportableInvoice).get(uuid)
|
||||||
|
if invoice and invoice.status_code == invoice.STATUS_EXPORTABLE:
|
||||||
|
invoices.append(invoice)
|
||||||
|
else:
|
||||||
|
log.warning("invoice not found or wrong status: %s", uuid)
|
||||||
|
|
||||||
|
if not invoices:
|
||||||
|
self.request.session.flash("Hm, was unable to determine any invoices "
|
||||||
|
"to export.", 'error')
|
||||||
|
return self.redirect(self.get_index_url())
|
||||||
|
|
||||||
|
# perform actual export and capture result
|
||||||
|
result = self.do_export(invoices)
|
||||||
|
|
||||||
|
# clear out current checkbox selection
|
||||||
|
self.set_selected(set())
|
||||||
|
|
||||||
|
# result may be a redirect, e.g. to new export record. if so
|
||||||
|
# then just return as-is
|
||||||
|
if isinstance(result, HTTPFound):
|
||||||
|
return result
|
||||||
|
|
||||||
|
# otherwise go back to invoice list
|
||||||
|
return self.redirect(self.get_index_url())
|
||||||
|
|
||||||
|
def do_export(self, invoices):
|
||||||
|
export = self.make_invoice_export(invoices)
|
||||||
|
self.update_export_status(invoices)
|
||||||
|
url = self.request.route_url('quickbooks.invoice_exports.view',
|
||||||
|
uuid=export.uuid)
|
||||||
|
return self.redirect(url)
|
||||||
|
|
||||||
|
def make_invoice_export_filename(self, invoices):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def make_invoice_export(self, invoices):
|
||||||
|
model = self.model
|
||||||
|
|
||||||
|
export = model.QuickbooksInvoiceExport()
|
||||||
|
export.created_by = self.request.user
|
||||||
|
export.record_count = len(invoices)
|
||||||
|
export.filename = self.make_invoice_export_filename(invoices)
|
||||||
|
self.Session.add(export)
|
||||||
|
self.Session.flush()
|
||||||
|
|
||||||
|
path = export.filepath(self.rattail_config, filename=export.filename,
|
||||||
|
makedirs=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.write_invoice_export_file(export, path, invoices)
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
log.warning("failed to write invoice export file for %s", export.uuid,
|
||||||
|
exc_info=True)
|
||||||
|
self.request.session.flash("Failed to write export file: {}".format(
|
||||||
|
simple_error(error)), 'error')
|
||||||
|
|
||||||
|
return export
|
||||||
|
|
||||||
|
def write_invoice_export_file(self, export, path, invoices, progress=None):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def update_export_status(self, invoices):
|
||||||
|
app = self.get_rattail_app()
|
||||||
|
now = app.make_utc()
|
||||||
|
for invoice in invoices:
|
||||||
|
invoice.exported = now
|
||||||
|
invoice.exported_by = self.request.user
|
||||||
|
invoice.status_code = invoice.STATUS_EXPORTED
|
||||||
|
for dist in invoice.distributions:
|
||||||
|
if dist.status_code == dist.STATUS_EXPORTABLE:
|
||||||
|
dist.status_code = dist.STATUS_EXPORTED
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def defaults(cls, config):
|
||||||
|
cls._invoice_defaults(config)
|
||||||
|
cls._defaults(config)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _invoice_defaults(cls, config):
|
||||||
|
route_prefix = cls.get_route_prefix()
|
||||||
|
url_prefix = cls.get_url_prefix()
|
||||||
|
instance_url_prefix = cls.get_instance_url_prefix()
|
||||||
|
permission_prefix = cls.get_permission_prefix()
|
||||||
|
model_title_plural = cls.get_model_title_plural()
|
||||||
|
|
||||||
|
# nb. must fix permission group title
|
||||||
|
config.add_tailbone_permission_group(permission_prefix,
|
||||||
|
model_title_plural, overwrite=False)
|
||||||
|
|
||||||
|
# select
|
||||||
|
config.add_route('{}.select'.format(route_prefix),
|
||||||
|
'{}/select'.format(url_prefix))
|
||||||
|
config.add_view(cls, attr='select',
|
||||||
|
route_name='{}.select'.format(route_prefix),
|
||||||
|
request_method='POST',
|
||||||
|
permission='{}.export'.format(permission_prefix),
|
||||||
|
renderer='json')
|
||||||
|
|
||||||
|
# deselect
|
||||||
|
config.add_route('{}.deselect'.format(route_prefix),
|
||||||
|
'{}/deselect'.format(url_prefix))
|
||||||
|
config.add_view(cls, attr='deselect',
|
||||||
|
route_name='{}.deselect'.format(route_prefix),
|
||||||
|
request_method='POST',
|
||||||
|
permission='{}.export'.format(permission_prefix),
|
||||||
|
renderer='json')
|
||||||
|
|
||||||
|
# refresh
|
||||||
|
config.add_route('{}.refresh'.format(route_prefix),
|
||||||
|
'{}/refresh'.format(instance_url_prefix))
|
||||||
|
config.add_view(cls, attr='refresh',
|
||||||
|
route_name='{}.refresh'.format(route_prefix),
|
||||||
|
request_method='POST',
|
||||||
|
permission='{}.export'.format(permission_prefix))
|
||||||
|
|
||||||
|
# export
|
||||||
|
config.add_tailbone_permission(permission_prefix,
|
||||||
|
'{}.export'.format(permission_prefix),
|
||||||
|
"Export Invoices")
|
||||||
|
config.add_route('{}.export'.format(route_prefix),
|
||||||
|
'{}/export'.format(url_prefix))
|
||||||
|
config.add_view(cls, attr='export',
|
||||||
|
route_name='{}.export'.format(route_prefix),
|
||||||
|
request_method='POST',
|
||||||
|
permission='{}.export'.format(permission_prefix))
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceExportView(ExportMasterView):
|
||||||
|
"""
|
||||||
|
Master view for Quickbooks invoice exports.
|
||||||
|
"""
|
||||||
|
model_class = QuickbooksInvoiceExport
|
||||||
|
route_prefix = 'quickbooks.invoice_exports'
|
||||||
|
url_prefix = '/quickbooks/exports/invoice'
|
||||||
|
downloadable = True
|
||||||
|
delete_export_files = True
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
'id',
|
||||||
|
'created',
|
||||||
|
'created_by',
|
||||||
|
'filename',
|
||||||
|
'record_count',
|
||||||
|
]
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
'id',
|
||||||
|
'created',
|
||||||
|
'created_by',
|
||||||
|
'record_count',
|
||||||
|
'filename',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
ExportableInvoiceView = kwargs.get('ExportableInvoiceView', base['ExportableInvoiceView'])
|
||||||
|
ExportableInvoiceView.defaults(config)
|
||||||
|
|
||||||
|
InvoiceExportView = kwargs.get('InvoiceExportView', base['InvoiceExportView'])
|
||||||
|
InvoiceExportView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
53
tailbone_quickbooks/views/stores.py
Normal file
53
tailbone_quickbooks/views/stores.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Store views, w/ Quickbooks integration
|
||||||
|
"""
|
||||||
|
|
||||||
|
from tailbone.views import ViewSupplement
|
||||||
|
|
||||||
|
|
||||||
|
class StoreViewSupplement(ViewSupplement):
|
||||||
|
"""
|
||||||
|
Store view supplement for Quickbooks integration
|
||||||
|
"""
|
||||||
|
route_prefix = 'stores'
|
||||||
|
|
||||||
|
def get_grid_query(self, query):
|
||||||
|
model = self.model
|
||||||
|
return query.outerjoin(model.QuickbooksStore)
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
model = self.model
|
||||||
|
g.set_filter('quickbooks_location', model.QuickbooksStore.quickbooks_location)
|
||||||
|
|
||||||
|
def configure_form(self, f):
|
||||||
|
f.append('quickbooks_location')
|
||||||
|
|
||||||
|
def get_version_child_classes(self):
|
||||||
|
model = self.model
|
||||||
|
return [model.QuickbooksStore]
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
StoreViewSupplement.defaults(config)
|
57
tailbone_quickbooks/views/vendors.py
Normal file
57
tailbone_quickbooks/views/vendors.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Vendor views, w/ Quickbooks integration
|
||||||
|
"""
|
||||||
|
|
||||||
|
from tailbone.views import ViewSupplement
|
||||||
|
|
||||||
|
|
||||||
|
class VendorViewSupplement(ViewSupplement):
|
||||||
|
"""
|
||||||
|
Vendor view supplement for Quickbooks integration
|
||||||
|
"""
|
||||||
|
route_prefix = 'vendors'
|
||||||
|
|
||||||
|
def get_grid_query(self, query):
|
||||||
|
model = self.model
|
||||||
|
return query.outerjoin(model.QuickbooksVendor)
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
model = self.model
|
||||||
|
g.set_filter('quickbooks_name', model.QuickbooksVendor.quickbooks_name)
|
||||||
|
g.set_filter('quickbooks_bank_account', model.QuickbooksVendor.quickbooks_bank_account)
|
||||||
|
g.set_filter('quickbooks_terms', model.QuickbooksVendor.quickbooks_terms)
|
||||||
|
|
||||||
|
def configure_form(self, f):
|
||||||
|
f.append('quickbooks_name')
|
||||||
|
f.append('quickbooks_bank_account')
|
||||||
|
f.append('quickbooks_terms')
|
||||||
|
|
||||||
|
def get_version_child_classes(self):
|
||||||
|
model = self.model
|
||||||
|
return [model.QuickbooksVendor]
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
VendorViewSupplement.defaults(config)
|
47
tasks.py
Normal file
47
tasks.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
c.run('rm -rf tailbone_quickbooks.egg-info')
|
||||||
|
c.run('python setup.py sdist --formats=gztar')
|
||||||
|
|
||||||
|
# upload to PyPI
|
||||||
|
filename = 'tailbone-quickbooks-{}.tar.gz'.format(__version__)
|
||||||
|
c.run('twine upload dist/{}'.format(filename))
|
Loading…
Reference in a new issue