From 58e967f887232afee8fce01685e279c9e1db23c1 Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Sat, 22 Dec 2018 11:09:50 +0100 Subject: [PATCH] [FIX] contract. Optimize insertion of dates in invoice and lines. --- contract/models/account_analytic_account.py | 127 +++++++++++------- contract/tests/test_contract.py | 15 +++ .../models/account_analytic_account.py | 35 +---- .../tests/test_contract_sale.py | 8 ++ 4 files changed, 110 insertions(+), 75 deletions(-) diff --git a/contract/models/account_analytic_account.py b/contract/models/account_analytic_account.py index 361be72a..df26b7b9 100644 --- a/contract/models/account_analytic_account.py +++ b/contract/models/account_analytic_account.py @@ -4,7 +4,9 @@ # Copyright 2015 Pedro M. Baeza # Copyright 2016-2017 Carlos Dauden # Copyright 2016-2017 LasLabs Inc. -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# Copyright 2018 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +# pylint: disable=no-member from dateutil.relativedelta import relativedelta @@ -15,9 +17,10 @@ from odoo.tools.translate import _ class AccountAnalyticAccount(models.Model): _name = 'account.analytic.account' - _inherit = ['account.analytic.account', - 'account.analytic.contract', - ] + _inherit = [ + 'account.analytic.account', + 'account.analytic.contract', + ] contract_template_id = fields.Many2one( string='Contract Template', @@ -163,18 +166,18 @@ class AccountAnalyticAccount(models.Model): return relativedelta(years=interval) @api.model - def _insert_markers(self, line, date_start, next_date, date_format): - contract = line.analytic_account_id - if contract.recurring_invoicing_type == 'pre-paid': - date_from = date_start - date_to = next_date - relativedelta(days=1) - else: - date_from = (date_start - - self.get_relative_delta(contract.recurring_rule_type, - contract.recurring_interval) + - relativedelta(days=1)) - date_to = date_start - name = line.name + def _insert_markers(self, name): + """Replace markers in contract or line with dates from context. + + This can be used either for the generation of invoice values + or for invoice line values. + """ + self.ensure_one() + context = self.env.context if 'date_format' in self.env.context \ + else self.get_invoice_context() + date_format = context.get('date_format', '%m/%d/%Y') + date_from = context['date_from'] + date_to = context['date_to'] name = name.replace('#START#', date_from.strftime(date_format)) name = name.replace('#END#', date_to.strftime(date_format)) return name @@ -190,20 +193,21 @@ class AccountAnalyticAccount(models.Model): }) # Add analytic tags to invoice line invoice_line.analytic_tag_ids |= line.analytic_tag_ids - # Get other invoice line values from product onchange - invoice_line._onchange_product_id() + # Get some invoice line values from relevant parts of + # onchange_product_id (for performance reasons inlined here). + invoice = invoice_line.invoice_id + account = invoice_line.get_invoice_line_account( + invoice.type, + invoice_line.product_id, + invoice.fiscal_position_id, + invoice.company_id) + if account: + invoice_line.account_id = account.id + invoice_line._set_taxes() invoice_line_vals = invoice_line._convert_to_write(invoice_line._cache) # Insert markers - name = line.name contract = line.analytic_account_id - if 'old_date' in self.env.context and 'next_date' in self.env.context: - lang_obj = self.env['res.lang'] - code = contract.partner_id.lang or self.company_id.partner_id.lang - lang = lang_obj.search([('code', '=', code)]) - date_format = lang.date_format or '%m/%d/%Y' - name = self._insert_markers( - line, self.env.context['old_date'], - self.env.context['next_date'], date_format) + name = self._insert_markers(line.name) invoice_line_vals.update({ 'name': name, 'account_analytic_id': contract.id, @@ -259,6 +263,51 @@ class AccountAnalyticAccount(models.Model): invoice.compute_taxes() return invoice + @api.multi + def get_invoice_context(self): + """Compute context for invoice creation.""" + self.ensure_one() + ctx = self.env.context.copy() + ref_date = self.recurring_next_date or fields.Date.today() + date_start = fields.Date.from_string(ref_date) + next_date = date_start + self.get_relative_delta( + self.recurring_rule_type, self.recurring_interval) + lang_obj = self.env['res.lang'] + code = self.partner_id.lang or self.company_id.partner_id.lang + lang = lang_obj.search([('code', '=', code)]) + date_format = lang.date_format or '%m/%d/%Y' + if self.recurring_invoicing_type == 'pre-paid': + date_from = date_start + date_to = next_date - relativedelta(days=1) + else: + date_from = ( + date_start - + self.get_relative_delta( + self.recurring_rule_type, self.recurring_interval) + + relativedelta(days=1)) + date_to = date_start + ctx.update({ + 'next_date': next_date, + 'date_format': date_format, + 'date_from': date_from, + 'date_to': date_to, + # Force company for correct evaluation of domain access rules + 'force_company': self.company_id.id}) + return ctx + + @api.multi + def check_dates_valid(self): + """Check start and end dates.""" + self.ensure_one() + ref_date = self.recurring_next_date or fields.Date.today() + if (self.date_start > ref_date or + self.date_end and self.date_end < ref_date): + if self.env.context.get('cron'): + return False # Don't fail on cron jobs + raise ValidationError( + _("You must review start and end dates!\n%s") % self.name) + return True + @api.multi def recurring_create_invoice(self, limit=None): """Create invoices from contracts @@ -272,29 +321,13 @@ class AccountAnalyticAccount(models.Model): for contract in self: if limit and len(invoices) >= limit: break - ref_date = contract.recurring_next_date or fields.Date.today() - if (contract.date_start > ref_date or - contract.date_end and contract.date_end < ref_date): - if self.env.context.get('cron'): - continue # Don't fail on cron jobs - raise ValidationError( - _("You must review start and end dates!\n%s") % - contract.name - ) - old_date = fields.Date.from_string(ref_date) - new_date = old_date + self.get_relative_delta( - contract.recurring_rule_type, contract.recurring_interval) - ctx = self.env.context.copy() - ctx.update({ - 'old_date': old_date, - 'next_date': new_date, - # Force company for correct evaluation of domain access rules - 'force_company': contract.company_id.id, - }) + if not contract.check_dates_valid(): + continue # Re-read contract with correct company + ctx = contract.get_invoice_context() invoices |= contract.with_context(ctx)._create_invoice() contract.write({ - 'recurring_next_date': fields.Date.to_string(new_date) + 'recurring_next_date': fields.Date.to_string(ctx['next_date']) }) return invoices diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py index 2ab19a91..399dcc2c 100644 --- a/contract/tests/test_contract.py +++ b/contract/tests/test_contract.py @@ -181,6 +181,21 @@ class TestContract(TestContractBase): with self.assertRaises(ValidationError): self.contract.date_end = '2015-12-31' + def test_check_cron_ended_contract(self): + self.contract.recurring_next_date = '2016-02-29' + self.contract.recurring_rule_type = 'yearly' + self.contract.date_end = '2016-02-28' + invoices = self.contract.with_context( + cron=True).recurring_create_invoice() + self.assertFalse(invoices) + + def test_check_no_cron_ended_contract(self): + self.contract.recurring_next_date = '2016-02-29' + self.contract.recurring_rule_type = 'yearly' + self.contract.date_end = '2016-02-28' + with self.assertRaises(ValidationError): + self.contract.recurring_create_invoice() + def test_check_recurring_next_date_start_date(self): with self.assertRaises(ValidationError): self.contract.write({ diff --git a/contract_sale_generation/models/account_analytic_account.py b/contract_sale_generation/models/account_analytic_account.py index c07d6b0d..79405fd7 100644 --- a/contract_sale_generation/models/account_analytic_account.py +++ b/contract_sale_generation/models/account_analytic_account.py @@ -6,7 +6,8 @@ # Copyright 2016-2017 LasLabs Inc. # Copyright 2017 Pesol () # Copyright 2017 Angel Moya -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# Copyright 2018 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import api, models, fields from odoo.exceptions import ValidationError @@ -29,16 +30,7 @@ class AccountAnalyticAccount(models.Model): sale_line.product_id_change() sale_line_vals = sale_line._convert_to_write(sale_line._cache) # Insert markers - name = line.name - contract = line.analytic_account_id - if 'old_date' in self.env.context and 'next_date' in self.env.context: - lang_obj = self.env['res.lang'] - lang = lang_obj.search( - [('code', '=', contract.partner_id.lang)]) - date_format = lang.date_format or '%m/%d/%Y' - name = self._insert_markers( - line, self.env.context['old_date'], - self.env.context['next_date'], date_format) + name = self._insert_markers(line.name) sale_line_vals.update({ 'name': name, 'discount': line.discount, @@ -106,26 +98,13 @@ class AccountAnalyticAccount(models.Model): """ sales = self.env['sale.order'] for contract in self: - ref_date = contract.recurring_next_date or fields.Date.today() - if (contract.date_start > ref_date or - contract.date_end and contract.date_end < ref_date): - raise ValidationError( - _("You must review start and end dates!\n%s") % - contract.name) - old_date = fields.Date.from_string(ref_date) - new_date = old_date + self.get_relative_delta( - contract.recurring_rule_type, contract.recurring_interval) - ctx = self.env.context.copy() - ctx.update({ - 'old_date': old_date, - 'next_date': new_date, - # Force company for correct evaluate domain access rules - 'force_company': contract.company_id.id, - }) + if not contract.check_dates_valid(): + continue # Re-read contract with correct company + ctx = contract.get_invoice_context() sales |= contract.with_context(ctx)._create_sale() contract.write({ - 'recurring_next_date': new_date.strftime('%Y-%m-%d') + 'recurring_next_date': fields.Date.to_string(ctx['next_date']) }) return sales diff --git a/contract_sale_generation/tests/test_contract_sale.py b/contract_sale_generation/tests/test_contract_sale.py index b64286b5..035ca8b9 100644 --- a/contract_sale_generation/tests/test_contract_sale.py +++ b/contract_sale_generation/tests/test_contract_sale.py @@ -98,3 +98,11 @@ class TestContractSale(TransactionCase): } del self.template_vals['name'] self.assertDictEqual(res, self.template_vals) + + def test_check_cron_ended_contract(self): + self.contract.recurring_next_date = '2016-02-29' + self.contract.recurring_rule_type = 'yearly' + self.contract.date_end = '2016-02-28' + sale_orders = self.contract.with_context( + cron=True).recurring_create_sale() + self.assertFalse(sale_orders)