From d994fb64117f5f71e09b628fd5d9b5a96a0b3a83 Mon Sep 17 00:00:00 2001 From: Carlos Incaser Date: Wed, 23 Mar 2016 13:01:15 +0100 Subject: [PATCH] [IMP] Tests and filters added. Add _id/_ids in old property fields --- contract/README.rst | 19 ++++++++-- contract/__openerp__.py | 2 + contract/models/contract.py | 50 +++++++++++++------------ contract/tests/__init__.py | 6 +++ contract/tests/test_contract.py | 41 ++++++++++++++++++++ contract/views/account_invoice_view.xml | 19 ++++++++++ contract/views/contract.xml | 47 ++++++++++++++++++----- 7 files changed, 147 insertions(+), 37 deletions(-) create mode 100644 contract/tests/__init__.py create mode 100644 contract/tests/test_contract.py create mode 100644 contract/views/account_invoice_view.xml diff --git a/contract/README.rst b/contract/README.rst index 0abdc88f3..2f105c93c 100644 --- a/contract/README.rst +++ b/contract/README.rst @@ -6,29 +6,42 @@ Contract ======== -This module helps you to manage contracts with recurring invoices. + * This module recover contracts management with recurring invoicing functions. Usage ===== To use this module, you need to: +#. Go to Sales -> Contracts and select or create a new contract. +#. Check *Generate recurring invoices automatically*. +#. Fill fields and add new lines. +#. To view discount field set *Discount on lines* in user access rights. +#. A cron is created with daily interval, but if you are in debug mode can + click on *Create invoices* to force this action. + + .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/167/8.0 + :target: https://runbot.odoo-community.org/runbot/110/9.0 For further information, please visit: * https://www.odoo.com/forum/help-1 +Known issues / Roadmap +====================== + +* Recovery states and others functional fields in Contracts. + 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 `_. +`here `_. Credits ======= diff --git a/contract/__openerp__.py b/contract/__openerp__.py index 4ec334ca4..efb884580 100644 --- a/contract/__openerp__.py +++ b/contract/__openerp__.py @@ -6,6 +6,7 @@ 'name': 'Contracts Management recurring', 'version': '9.0.1.0.0', 'category': 'Other', + 'license': 'AGPL-3', 'author': "OpenERP SA,Odoo Community Association (OCA)", 'website': 'http://openerp.com', 'depends': ['base', 'account', 'analytic'], @@ -13,6 +14,7 @@ 'security/ir.model.access.csv', 'data/contract_cron.xml', 'views/contract.xml', + 'views/account_invoice_view.xml', ], 'installable': True, 'images': [], diff --git a/contract/models/contract.py b/contract/models/contract.py index ebd253c53..4b7ed069b 100644 --- a/contract/models/contract.py +++ b/contract/models/contract.py @@ -6,7 +6,7 @@ from dateutil.relativedelta import relativedelta import logging import time -from openerp import api, exceptions, fields, models +from openerp import api, fields, models from openerp.addons.decimal_precision import decimal_precision as dp from openerp.exceptions import ValidationError from openerp.tools.translate import _ @@ -15,7 +15,7 @@ _logger = logging.getLogger(__name__) class AccountAnalyticInvoiceLine(models.Model): - _name = "account.analytic.invoice.line" + _name = 'account.analytic.invoice.line' product_id = fields.Many2one( 'product.product', string='Product', required=True) @@ -65,7 +65,8 @@ class AccountAnalyticInvoiceLine(models.Model): vals = {} domain = {'uom_id': [ ('category_id', '=', self.product_id.uom_id.category_id.id)]} - if not self.uom_id or (self.product_id.uom_id.category_id.id != self.uom_id.category_id.id): + if not self.uom_id or (self.product_id.uom_id.category_id.id != + self.uom_id.category_id.id): vals['uom_id'] = self.product_id.uom_id product = self.product_id.with_context( @@ -131,32 +132,38 @@ class AccountAnalyticAccount(models.Model): default=_default_journal, domain="[('type', '=', 'sale'),('company_id', '=', company_id)]") + @api.multi def copy(self, default=None): # Reset next invoice date default['recurring_next_date'] = \ self._defaults['recurring_next_date']() return super(AccountAnalyticAccount, self).copy(default=default) + @api.onchange('partner_id') + def _onchange_partner_id(self): + self.pricelist_id = self.partner_id.property_product_pricelist.id + @api.onchange('recurring_invoices') def _onchange_recurring_invoices(self): if self.date_start and self.recurring_invoices: self.recurring_next_date = self.date_start @api.model - def _insert_markers(self, line, date_start, date_end, date_format): + def _insert_markers(self, line, date_start, next_date, date_format): line = line.replace('#START#', date_start.strftime(date_format)) + date_end = next_date - relativedelta(days=1) line = line.replace('#END#', date_end.strftime(date_format)) return line @api.model def _prepare_invoice_line(self, line, invoice_id): product = line.product_id - account_id = product.property_account_income_id.id or \ - product.categ_id.property_account_income_categ_id.id + account_id = (product.property_account_income_id.id or + product.categ_id.property_account_income_categ_id.id) contract = line.analytic_account_id fpos = contract.partner_id.property_account_position_id account_id = fpos.map_account(account_id) - tax_id = fpos.map_tax(product.taxes_id) + taxes = fpos.map_tax(product.taxes_id) name = line.name if 'old_date' in self.env.context and 'next_date' in self.env.context: lang_obj = self.env['res.lang'] @@ -173,10 +180,10 @@ class AccountAnalyticAccount(models.Model): 'account_analytic_id': contract.id, 'price_unit': line.price_unit, 'quantity': line.quantity, - 'uos_id': line.uom_id.id, + 'uom_id': line.uom_id.id, 'product_id': line.product_id.id, 'invoice_id': invoice_id, - 'invoice_line_tax_id': [(6, 0, tax_id)], + 'invoice_line_tax_ids': [(6, 0, taxes.ids)], 'discount': line.discount, } @@ -184,19 +191,17 @@ class AccountAnalyticAccount(models.Model): def _prepare_invoice(self, contract): if not contract.partner_id: raise ValidationError( - _('No Customer Defined!'), _("You must first select a Customer for Contract %s!") % contract.name) partner = contract.partner_id fpos = partner.property_account_position_id - journal_ids = self.env['account.journal'].search( + journal = contract.journal_id or self.env['account.journal'].search( [('type', '=', 'sale'), ('company_id', '=', contract.company_id.id)], limit=1) - if not journal_ids: + if not journal: raise ValidationError( - _('Error!'), - _('Please define a sale journal for the company "%s".') % + _("Please define a sale journal for the company '%s'.") % (contract.company_id.name or '',)) inv_data = { 'reference': contract.code, @@ -204,22 +209,19 @@ class AccountAnalyticAccount(models.Model): 'type': 'out_invoice', 'partner_id': partner.id, 'currency_id': partner.property_product_pricelist.currency_id.id, - 'journal_id': journal_ids.id, + 'journal_id': journal.id, 'date_invoice': contract.recurring_next_date, 'origin': contract.name, - 'fiscal_position': fpos and fpos.id, - 'payment_term': partner.property_payment_term_id.id, + 'fiscal_position_id': fpos.id, + 'payment_term_id': partner.property_payment_term_id.id, 'company_id': contract.company_id.id, - 'journal_id': contract.journal_id.id, 'contract_id': contract.id, } - # if contract.journal_id: - # inv_data['journal_id'] = contract.journal_id.id invoice = self.env['account.invoice'].create(inv_data) for line in contract.recurring_invoice_line_ids: invoice_line_vals = self._prepare_invoice_line(line, invoice.id) self.env['account.invoice.line'].create(invoice_line_vals) - # invoice.button_compute() + invoice.compute_taxes() return invoice @api.model @@ -235,11 +237,11 @@ class AccountAnalyticAccount(models.Model): interval = contract.recurring_interval old_date = next_date if contract.recurring_rule_type == 'daily': - new_date = next_date + relativedelta(days=interval - 1) + new_date = next_date + relativedelta(days=interval) elif contract.recurring_rule_type == 'weekly': - new_date = next_date + relativedelta(weeks=interval, days=-1) + new_date = next_date + relativedelta(weeks=interval) else: - new_date = next_date + relativedelta(months=interval, days=-1) + new_date = next_date + relativedelta(months=interval) ctx = self.env.context.copy() ctx.update({ 'old_date': old_date, diff --git a/contract/tests/__init__.py b/contract/tests/__init__.py new file mode 100644 index 000000000..2b43f40bb --- /dev/null +++ b/contract/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# (c) 2015 Antiun Ingeniería S.L. - Sergio Teruel +# (c) 2015 Antiun Ingeniería S.L. - Carlos Dauden +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import test_contract diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py new file mode 100644 index 000000000..f492d943c --- /dev/null +++ b/contract/tests/test_contract.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# © 2016 Incaser Informatica S.L. - Carlos Dauden +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html +from openerp.exceptions import ValidationError +from openerp.tests.common import TransactionCase + + +class TestContract(TransactionCase): + # Use case : Prepare some data for current test case + def setUp(self): + super(TestContract, self).setUp() + self.partner = self.env.ref('base.res_partner_2') + self.product = self.env.ref('product.product_product_2') + self.contract = self.env['account.analytic.account'].create({ + 'name': 'Test Contract', + 'partner_id': self.partner.id, + 'pricelist_id': self.partner.property_product_pricelist.id, + 'recurring_invoices': True, + }) + self.contract_line = self.env['account.analytic.invoice.line'].create({ + 'analytic_account_id': self.contract.id, + 'product_id': self.product.id, + 'name': 'Services from #START# to #END#', + 'quantity': 1, + 'uom_id': self.product.uom_id.id, + 'price_unit': 100, + 'discount': 50, + }) + + def test_check_discount(self): + with self.assertRaises(ValidationError): + self.contract_line.write({'discount': 120}) + + def test_create_invoice(self): + self.contract.recurring_create_invoice() + self.invoice = self.env['account.invoice'].search( + [('contract_id', '=', self.contract.id)]) + self.assertTrue(self.invoice) + + self.inv_line = self.invoice.invoice_line_ids[0] + self.assertAlmostEqual(self.inv_line.price_subtotal, 50.0) diff --git a/contract/views/account_invoice_view.xml b/contract/views/account_invoice_view.xml new file mode 100644 index 000000000..09752e9f7 --- /dev/null +++ b/contract/views/account_invoice_view.xml @@ -0,0 +1,19 @@ + + + + + + + account.invoice.select.contract + account.invoice + + + + + + + + + + + diff --git a/contract/views/contract.xml b/contract/views/contract.xml index 216828a22..3f9e9bf1e 100644 --- a/contract/views/contract.xml +++ b/contract/views/contract.xml @@ -40,13 +40,16 @@
- +