From 803635dbca5b720c392ed77caebd4349226e6de3 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Mon, 22 Feb 2016 18:36:40 +0100 Subject: [PATCH] [ADD] contract_recurring_invoicing_markers ======================================= Markers for contract recurring invoices ======================================= This module allows to include some markers on the lines of your recurring invoices definition inside the contract so that the generated invoice lines will contain a dynamic text depending on these markers. These markers are the supported ones: * #START#: Start date of the invoiced period. * #END# End date of the invoiced period. Usage ===== On a contract (*Sales > Sales > Contracts*), mark "Generate recurring invoices automatically" for enabling the creation of recurring invoices. In the "Invoice Lines" section, you can add lines with products. In the *Description* field, you can now add any of the markers mentioned before between the rest of the text. When you invoice this contract (automatically or manually), your invoice will contain the corresponding text that replaces the marker. Known issues / Roadmap ====================== * Add more markers, like #START_MONTH# or #END_MONTH#. --- .../README.rst | 79 +++++ .../__init__.py | 5 + .../__openerp__.py | 19 ++ .../models/__init__.py | 5 + .../models/account_analytic_account.py | 41 +++ .../static/description/icon.png | Bin 0 -> 6327 bytes .../static/description/icon.svg | 320 ++++++++++++++++++ .../tests/__init__.py | 5 + ...est_contract_recurring_invoicing_marker.py | 39 +++ .../views/account_analytic_account_view.xml | 20 ++ 10 files changed, 533 insertions(+) create mode 100644 contract_recurring_invoicing_marker/README.rst create mode 100644 contract_recurring_invoicing_marker/__init__.py create mode 100644 contract_recurring_invoicing_marker/__openerp__.py create mode 100644 contract_recurring_invoicing_marker/models/__init__.py create mode 100644 contract_recurring_invoicing_marker/models/account_analytic_account.py create mode 100644 contract_recurring_invoicing_marker/static/description/icon.png create mode 100644 contract_recurring_invoicing_marker/static/description/icon.svg create mode 100644 contract_recurring_invoicing_marker/tests/__init__.py create mode 100644 contract_recurring_invoicing_marker/tests/test_contract_recurring_invoicing_marker.py create mode 100644 contract_recurring_invoicing_marker/views/account_analytic_account_view.xml diff --git a/contract_recurring_invoicing_marker/README.rst b/contract_recurring_invoicing_marker/README.rst new file mode 100644 index 00000000..a26d6936 --- /dev/null +++ b/contract_recurring_invoicing_marker/README.rst @@ -0,0 +1,79 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +======================================= +Markers for contract recurring invoices +======================================= + +This module allows to include some markers on the lines of your recurring +invoices definition inside the contract so that the generated invoice lines +will contain a dynamic text depending on these markers. + +These markers are the supported ones: + +* #START#: Start date of the invoiced period. +* #END# End date of the invoiced period. + +Usage +===== + +On a contract (*Sales > Sales > Contracts*), mark "Generate recurring invoices +automatically" for enabling the creation of recurring invoices. + +In the "Invoice Lines" section, you can add lines with products. In the +*Description* field, you can now add any of the markers mentioned before +between the rest of the text. + +When you invoice this contract (automatically or manually), your invoice +will contain the corresponding text that replaces the marker. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/110/8.0 + +Known issues / Roadmap +====================== + +* Add more markers, like #START_MONTH# or #END_MONTH#. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed feedback `here `_. + +Credits +======= + +Contributors +------------ + +* Pedro M. Baeza + +Icon +---- + +* https://openclipart.org/detail/125071/pie-graph +* Subicon made by `Freepik _ from + www.flaticon.com + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/contract_recurring_invoicing_marker/__init__.py b/contract_recurring_invoicing_marker/__init__.py new file mode 100644 index 00000000..406086aa --- /dev/null +++ b/contract_recurring_invoicing_marker/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# (c) 2016 Serv. Tecnol. Avanzados - Pedro M. Baeza +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import models diff --git a/contract_recurring_invoicing_marker/__openerp__.py b/contract_recurring_invoicing_marker/__openerp__.py new file mode 100644 index 00000000..f2efc554 --- /dev/null +++ b/contract_recurring_invoicing_marker/__openerp__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# (c) 2016 Serv. Tecnol. Avanzados - Pedro M. Baeza +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +{ + 'name': 'Markers for contract recurring invoices', + 'version': '8.0.1.0.0', + 'category': 'Contract Management', + 'author': 'Serv. Tecnol. Avanzados - Pedro M. Baeza, ' + 'Odoo Community Association (OCA)', + 'website': 'http://www.serviciosbaeza.com', + 'depends': [ + 'account_analytic_analysis', + ], + 'data': [ + 'views/account_analytic_account_view.xml', + ], + 'installable': True, +} diff --git a/contract_recurring_invoicing_marker/models/__init__.py b/contract_recurring_invoicing_marker/models/__init__.py new file mode 100644 index 00000000..9bde9c9b --- /dev/null +++ b/contract_recurring_invoicing_marker/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# (c) 2016 Serv. Tecnol. Avanzados - Pedro M. Baeza +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import account_analytic_account diff --git a/contract_recurring_invoicing_marker/models/account_analytic_account.py b/contract_recurring_invoicing_marker/models/account_analytic_account.py new file mode 100644 index 00000000..c2986655 --- /dev/null +++ b/contract_recurring_invoicing_marker/models/account_analytic_account.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# (c) 2016 Serv. Tecnol. Avanzados - Pedro M. Baeza +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from openerp import api, fields, models +from dateutil.relativedelta import relativedelta + + +class AccountAnalyticAccount(models.Model): + _inherit = "account.analytic.account" + + @api.model + def _prepare_invoice(self, contract): + next_date = fields.Date.from_string( + contract.recurring_next_date or fields.Date.today()) + interval = contract.recurring_interval + old_date = next_date + if contract.recurring_rule_type == 'daily': + new_date = next_date + relativedelta(days=interval - 1) + elif contract.recurring_rule_type == 'weekly': + new_date = next_date + relativedelta(weeks=interval, days=-1) + else: + new_date = next_date + relativedelta(months=interval, days=-1) + obj = self.with_context(old_date=old_date, next_date=new_date) + return super(AccountAnalyticAccount, obj)._prepare_invoice(contract) + + @api.model + def _prepare_invoice_line(self, line, invoice_id): + res = super(AccountAnalyticAccount, self)._prepare_invoice_line( + line, invoice_id) + if 'old_date' in self.env.context and 'next_date' in self.env.context: + lang_obj = self.env['res.lang'] + contract = line.analytic_account_id + lang = lang_obj.search( + [('code', '=', contract.partner_id.lang)]) + date_format = lang.date_format + res['name'] = res['name'].replace( + '#START#', self.env.context['old_date'].strftime(date_format)) + res['name'] = res['name'].replace( + '#END#', self.env.context['next_date'].strftime(date_format)) + return res diff --git a/contract_recurring_invoicing_marker/static/description/icon.png b/contract_recurring_invoicing_marker/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d4d85b463492004db72160d3ecb6a3eaeac718eb GIT binary patch literal 6327 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + Pile of Golden Coins + 2010-04-09T03:27:45 + A pile of hypothetical golden coins, drawn in Inkscape. + https://openclipart.org/detail/43969/pile-of-golden-coins-by-j_alves + + + J_Alves + + + + + coin + currency + gold + money + thaler + + + + + + + + + + + diff --git a/contract_recurring_invoicing_marker/tests/__init__.py b/contract_recurring_invoicing_marker/tests/__init__.py new file mode 100644 index 00000000..6e03ad9f --- /dev/null +++ b/contract_recurring_invoicing_marker/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# (c) 2016 Serv. Tecnol. Avanzados - Pedro M. Baeza +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import test_contract_recurring_invoicing_marker diff --git a/contract_recurring_invoicing_marker/tests/test_contract_recurring_invoicing_marker.py b/contract_recurring_invoicing_marker/tests/test_contract_recurring_invoicing_marker.py new file mode 100644 index 00000000..607bb7db --- /dev/null +++ b/contract_recurring_invoicing_marker/tests/test_contract_recurring_invoicing_marker.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# (c) 2016 Serv. Tecnol. Avanzados - Pedro M. Baeza +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +import openerp.tests.common as common + + +class TestContractRecurringInvoicingMarker(common.TransactionCase): + + def setUp(self): + super(TestContractRecurringInvoicingMarker, self).setUp() + self.partner = self.env['res.partner'].create({ + 'name': 'Test', + 'lang': 'en_US', + }) + self.product = self.env.ref('product.product_product_consultant') + self.uom = self.env.ref('product.product_uom_hour') + self.contract = self.env['account.analytic.account'].create({ + 'name': 'Test contract', + 'partner_id': self.partner.id, + 'type': 'contract', + 'recurring_invoices': 1, + 'recurring_next_date': '2016-01-01', + 'recurring_rule_type': 'monthly', + 'recurring_interval': 1, + 'recurring_invoice_line_ids': [ + (0, 0, {'quantity': 2.0, + 'price_unit': 100.0, + 'name': '#START# - #END#', + 'product_id': self.product.id, + 'uom_id': self.uom.id})], + }) + + def test_invoice_with_marker(self): + self.contract.recurring_create_invoice() + invoice = self.env['account.invoice'].search( + [('partner_id', '=', self.partner.id)]) + self.assertEqual( + invoice.invoice_line[0].name, u'01/01/2016 - 01/31/2016') diff --git a/contract_recurring_invoicing_marker/views/account_analytic_account_view.xml b/contract_recurring_invoicing_marker/views/account_analytic_account_view.xml new file mode 100644 index 00000000..5bc87358 --- /dev/null +++ b/contract_recurring_invoicing_marker/views/account_analytic_account_view.xml @@ -0,0 +1,20 @@ + + + + + + Contract form (with markers) + account.analytic.account + + + + +

#START#: Start date of the invoiced period

+

#END#: End date of the invoiced period

+
+
+
+
+ +
+