From b09d41b8823fdf9c2bcf1bab38baa190ddb3a7cb Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Thu, 28 Nov 2019 17:15:46 +0100 Subject: [PATCH 1/2] [12.0][IMP] - Contract: Add a new field to compute the next period end date make the invoicing period independent from the invoice date --- contract/__manifest__.py | 2 +- .../migrations/12.0.5.0.0/post-migration.py | 29 +++ contract/models/contract_line.py | 115 ++++++++---- contract/tests/test_contract.py | 174 ++++++++++++------ contract/views/contract_line.xml | 3 +- 5 files changed, 224 insertions(+), 99 deletions(-) create mode 100644 contract/migrations/12.0.5.0.0/post-migration.py diff --git a/contract/__manifest__.py b/contract/__manifest__.py index 274ab73260..1d1bc4769b 100644 --- a/contract/__manifest__.py +++ b/contract/__manifest__.py @@ -9,7 +9,7 @@ { 'name': 'Recurring - Contracts Management', - 'version': '12.0.4.2.5', + 'version': '12.0.5.0.0', 'category': 'Contract Management', 'license': 'AGPL-3', 'author': "OpenERP SA, " diff --git a/contract/migrations/12.0.5.0.0/post-migration.py b/contract/migrations/12.0.5.0.0/post-migration.py new file mode 100644 index 0000000000..7d27ccad72 --- /dev/null +++ b/contract/migrations/12.0.5.0.0/post-migration.py @@ -0,0 +1,29 @@ +# Copyright 2019 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from dateutil.relativedelta import relativedelta +from odoo import api, SUPERUSER_ID + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + _logger.info("Init next_period_end_date field") + with api.Environment.manage(): + env = api.Environment(cr, SUPERUSER_ID, {}) + cl_model = env['contract.line'] + contract_lines = cl_model.search([]) + for cl in contract_lines: + if cl.date_end and cl.last_date_invoiced == cl.date_end: + continue # Finished lines + next_period_start_date = ( + cl.last_date_invoiced + relativedelta(days=1) + if cl.last_date_invoiced + else cl.date_start + ) + cl.next_period_end_date = cl_model._get_next_period_end_date( + next_period_start_date, + cl.recurring_rule_type, + cl.recurring_interval, + ) diff --git a/contract/models/contract_line.py b/contract/models/contract_line.py index 3178f24094..07b26bf843 100644 --- a/contract/models/contract_line.py +++ b/contract/models/contract_line.py @@ -41,6 +41,9 @@ class ContractLine(models.Model): last_date_invoiced = fields.Date( string='Last Date Invoiced', readonly=True, copy=False ) + next_period_end_date = fields.Date( + string='Next Period End Date', copy=False + ) termination_notice_date = fields.Date( string='Termination notice date', compute="_compute_termination_notice_date", @@ -356,7 +359,7 @@ def _check_overlap_predecessor(self): ) @api.model - def _compute_first_recurring_next_date( + def _get_recurring_next_date( self, date_start, recurring_invoicing_type, @@ -374,7 +377,7 @@ def _compute_first_recurring_next_date( ) @api.model - def compute_first_date_end( + def _get_date_end( self, date_start, auto_renew_rule_type, auto_renew_interval ): return ( @@ -385,6 +388,20 @@ def compute_first_date_end( - relativedelta(days=1) ) + @api.model + def _get_next_period_end_date( + self, next_period_start_date, recurring_rule_type, recurring_interval + ): + if recurring_rule_type == 'monthlylastday': + return next_period_start_date + self.get_relative_delta( + recurring_rule_type, recurring_interval - 1 + ) + return ( + next_period_start_date + + self.get_relative_delta(recurring_rule_type, recurring_interval) + - relativedelta(days=1) + ) + @api.onchange( 'date_start', 'is_auto_renew', @@ -396,7 +413,7 @@ def _onchange_is_auto_renew(self): auto_renew""" for rec in self.filtered('is_auto_renew'): if rec.date_start: - rec.date_end = self.compute_first_date_end( + rec.date_end = self._get_date_end( rec.date_start, rec.auto_renew_rule_type, rec.auto_renew_interval, @@ -407,15 +424,19 @@ def _onchange_is_auto_renew(self): 'recurring_invoicing_type', 'recurring_rule_type', 'recurring_interval', + 'last_date_invoiced', ) def _onchange_date_start(self): for rec in self.filtered('date_start'): - rec.recurring_next_date = self._compute_first_recurring_next_date( + rec.recurring_next_date = self._get_recurring_next_date( rec.date_start, rec.recurring_invoicing_type, rec.recurring_rule_type, rec.recurring_interval, ) + rec.next_period_end_date = self._get_next_period_end_date( + rec.date_start, rec.recurring_rule_type, rec.recurring_interval + ) @api.constrains('is_canceled', 'is_auto_renew') def _check_auto_renew_canceled_lines(self): @@ -487,6 +508,20 @@ def _check_start_end_dates(self): % line.name ) + @api.constrains('next_period_end_date', 'last_date_invoiced') + def _check_next_period_end_date(self): + for line in self.filtered('next_period_end_date'): + if line.next_period_end_date < line.date_start or ( + line.date_end and line.next_period_end_date > line.date_end + ): + raise ValidationError( + _( + "Next period end date must be between the " + "start and the end of the contract line: %s" + ) + % line.name + ) + @api.depends('recurring_next_date', 'date_start', 'date_end') def _compute_create_invoice_visibility(self): today = fields.Date.context_today(self) @@ -542,23 +577,9 @@ def _get_period_to_invoice( if last_date_invoiced else self.date_start ) - if self.recurring_rule_type == 'monthlylastday': - last_date_invoiced = recurring_next_date - else: - if self.recurring_invoicing_type == 'pre-paid': - last_date_invoiced = ( - recurring_next_date - + self.get_relative_delta( - self.recurring_rule_type, self.recurring_interval - ) - - relativedelta(days=1) - ) - else: - last_date_invoiced = recurring_next_date - relativedelta( - days=1 - ) + last_date_invoiced = self.next_period_end_date if stop_at_date_end: - if self.date_end and self.date_end < last_date_invoiced: + if not self.next_period_end_date and self.date_end: last_date_invoiced = self.date_end return first_date_invoiced, last_date_invoiced, recurring_next_date @@ -580,23 +601,32 @@ def _insert_markers(self, first_date_invoiced, last_date_invoiced): @api.multi def _update_recurring_next_date(self): for rec in self: - old_date = rec.recurring_next_date - new_date = old_date + self.get_relative_delta( - rec.recurring_rule_type, rec.recurring_interval - ) - if rec.recurring_rule_type == 'monthlylastday': - last_date_invoiced = old_date - elif rec.recurring_invoicing_type == 'post-paid': - last_date_invoiced = old_date - relativedelta(days=1) - elif rec.recurring_invoicing_type == 'pre-paid': - last_date_invoiced = new_date - relativedelta(days=1) - - if rec.date_end and last_date_invoiced >= rec.date_end: - rec.last_date_invoiced = rec.date_end - rec.recurring_next_date = False - else: - rec.last_date_invoiced = last_date_invoiced - rec.recurring_next_date = new_date + if rec.next_period_end_date: + last_date_invoiced = rec.next_period_end_date + next_period_start_date = last_date_invoiced + relativedelta( + days=1 + ) + next_period_end_date = self._get_next_period_end_date( + next_period_start_date, + rec.recurring_rule_type, + rec.recurring_interval, + ) + recurring_next_date = self._get_recurring_next_date( + next_period_start_date, + rec.recurring_invoicing_type, + rec.recurring_rule_type, + rec.recurring_interval, + ) + if rec.date_end and next_period_end_date > rec.date_end: + next_period_end_date = rec.date_end + if rec.date_end and last_date_invoiced >= rec.date_end: + rec.last_date_invoiced = rec.date_end + rec.recurring_next_date = False + rec.next_period_end_date = False + else: + rec.last_date_invoiced = last_date_invoiced + rec.recurring_next_date = recurring_next_date + rec.next_period_end_date = next_period_end_date @api.multi def _init_last_date_invoiced(self): @@ -651,7 +681,7 @@ def _delay(self, delay_delta): ) ) new_date_start = rec.date_start + delay_delta - rec.recurring_next_date = self._compute_first_recurring_next_date( + rec.recurring_next_date = self._get_recurring_next_date( new_date_start, rec.recurring_invoicing_type, rec.recurring_rule_type, @@ -684,6 +714,7 @@ def stop(self, date_end, manual_renew_needed=False, post_message=True): } if rec.last_date_invoiced == date_end: values['recurring_next_date'] = False + values['next_period_end_date'] = False rec.write(values) if post_message: msg = _( @@ -712,12 +743,15 @@ def _prepare_value_for_plan_successor( ): self.ensure_one() if not recurring_next_date: - recurring_next_date = self._compute_first_recurring_next_date( + recurring_next_date = self._get_recurring_next_date( date_start, self.recurring_invoicing_type, self.recurring_rule_type, self.recurring_interval, ) + next_period_end_date = self._get_next_period_end_date( + date_start, self.recurring_rule_type, self.recurring_interval + ) new_vals = self.read()[0] new_vals.pop("id", None) new_vals.pop("last_date_invoiced", None) @@ -725,6 +759,7 @@ def _prepare_value_for_plan_successor( values['date_start'] = date_start values['date_end'] = date_end values['recurring_next_date'] = recurring_next_date + values['next_period_end_date'] = next_period_end_date values['is_auto_renew'] = is_auto_renew values['predecessor_contract_line_id'] = self.id return values @@ -1023,7 +1058,7 @@ def action_stop_plan_successor(self): def _get_renewal_dates(self): self.ensure_one() date_start = self.date_end + relativedelta(days=1) - date_end = self.compute_first_date_end( + date_end = self._get_date_end( date_start, self.auto_renew_rule_type, self.auto_renew_interval ) return date_start, date_end diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py index 74adae5176..65199a8aea 100644 --- a/contract/tests/test_contract.py +++ b/contract/tests/test_contract.py @@ -108,9 +108,23 @@ def setUpClass(cls): cls.line_vals ) cls.acct_line.product_id.is_auto_renew = True + cls.acct_line._onchange_date_start() class TestContract(TestContractBase): + def _update_contract_line_date( + self, date_start, recurring_next_date, date_end + ): + self.acct_line.write( + { + 'date_start': date_start, + 'recurring_next_date': recurring_next_date, + 'next_period_end_date': recurring_next_date, + 'date_end': date_end, + } + ) + self.acct_line._onchange_date_start() + def _add_template_line(self, overrides=None): if overrides is None: overrides = {} @@ -140,7 +154,7 @@ def test_automatic_price(self): self.assertEqual(self.acct_line.price_unit, 10) def test_contract(self): - recurring_next_date = to_date('2018-02-15') + recurring_next_date = to_date('2018-02-01') self.assertAlmostEqual(self.acct_line.price_subtotal, 50.0) res = self.acct_line._onchange_product_id() self.assertIn('uom_id', res['domain']) @@ -157,29 +171,11 @@ def test_contract(self): self.assertAlmostEqual(self.inv_line.price_subtotal, 50.0) self.assertEqual(self.contract.user_id, self.invoice_monthly.user_id) - def test_contract_recurring_next_date(self): - recurring_next_date = to_date('2018-01-15') - self.assertEqual( - self.contract.recurring_next_date, recurring_next_date - ) - contract_line = self.acct_line.copy( - {'recurring_next_date': '2018-01-14'} - ) - recurring_next_date = to_date('2018-01-14') - self.assertEqual( - self.contract.recurring_next_date, recurring_next_date - ) - contract_line.cancel() - recurring_next_date = to_date('2018-01-15') - self.assertEqual( - self.contract.recurring_next_date, recurring_next_date - ) - def test_contract_daily(self): recurring_next_date = to_date('2018-02-23') last_date_invoiced = to_date('2018-02-22') - self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_rule_type = 'daily' + self._update_contract_line_date('2018-02-22', '2018-02-22', False) self.contract.pricelist_id = False self.contract.recurring_create_invoice() invoice_daily = self.contract._get_related_invoices() @@ -192,9 +188,9 @@ def test_contract_daily(self): def test_contract_weekly_post_paid(self): recurring_next_date = to_date('2018-03-01') last_date_invoiced = to_date('2018-02-21') - self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_rule_type = 'weekly' self.acct_line.recurring_invoicing_type = 'post-paid' + self._update_contract_line_date('2018-02-15', '2018-02-15', False) self.contract.recurring_create_invoice() invoices_weekly = self.contract._get_related_invoices() self.assertTrue(invoices_weekly) @@ -206,9 +202,9 @@ def test_contract_weekly_post_paid(self): def test_contract_weekly_pre_paid(self): recurring_next_date = to_date('2018-03-01') last_date_invoiced = to_date('2018-02-28') - self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_rule_type = 'weekly' self.acct_line.recurring_invoicing_type = 'pre-paid' + self._update_contract_line_date('2018-02-22', '2018-02-22', False) self.contract.recurring_create_invoice() invoices_weekly = self.contract._get_related_invoices() self.assertTrue(invoices_weekly) @@ -220,12 +216,12 @@ def test_contract_weekly_pre_paid(self): def test_contract_yearly_post_paid(self): recurring_next_date = to_date('2019-02-22') last_date_invoiced = to_date('2018-02-21') - self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_rule_type = 'yearly' self.acct_line.recurring_invoicing_type = 'post-paid' + self._update_contract_line_date('2017-02-22', '2018-02-22', False) self.contract.recurring_create_invoice() - invoices_weekly = self.contract._get_related_invoices() - self.assertTrue(invoices_weekly) + invoices = self.contract._get_related_invoices() + self.assertTrue(invoices) self.assertEqual( self.acct_line.recurring_next_date, recurring_next_date ) @@ -234,10 +230,11 @@ def test_contract_yearly_post_paid(self): def test_contract_yearly_pre_paid(self): recurring_next_date = to_date('2019-02-22') last_date_invoiced = to_date('2019-02-21') - self.acct_line.date_end = '2020-02-22' - self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_rule_type = 'yearly' self.acct_line.recurring_invoicing_type = 'pre-paid' + self._update_contract_line_date( + '2018-02-22', '2018-02-22', '2020-02-22' + ) self.contract.recurring_create_invoice() invoices_weekly = self.contract._get_related_invoices() self.assertTrue(invoices_weekly) @@ -248,10 +245,11 @@ def test_contract_yearly_pre_paid(self): def test_contract_monthly_lastday(self): recurring_next_date = to_date('2018-03-31') - last_date_invoiced = to_date('2018-02-22') - self.acct_line.recurring_next_date = '2018-02-22' + last_date_invoiced = to_date('2018-03-22') self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'monthlylastday' + self._update_contract_line_date('2018-03-01', '2018-03-01', False) + self.acct_line.next_period_end_date = '2018-03-22' self.contract.recurring_create_invoice() invoices_monthly_lastday = self.contract._get_related_invoices() self.assertTrue(invoices_monthly_lastday) @@ -510,6 +508,7 @@ def test_same_date_start_and_date_end(self): 'date_start': self.today, 'date_end': self.today, 'recurring_next_date': self.today, + 'next_period_end_date': self.today, } ) self.contract._compute_recurring_next_date() @@ -606,7 +605,7 @@ def error_message( for recurring_next_date, combination in combinations: self.assertEqual( recurring_next_date, - contract_line_env._compute_first_recurring_next_date( + contract_line_env._get_recurring_next_date( *combination ), error_message(*combination), @@ -715,8 +714,10 @@ def test_stop_plan_successor_contract_line_0(self): { 'date_start': self.today + relativedelta(months=5), 'recurring_next_date': self.today + relativedelta(months=5), + 'next_period_end_date': self.today + relativedelta(months=5), } ) + self.acct_line._onchange_date_start() self.acct_line.write( { 'successor_contract_line_id': successor_contract_line.id, @@ -1188,13 +1189,7 @@ def test_cancel_uncancel_with_predecessor(self): suspension_end = self.today + relativedelta(months=5) start_date = self.today end_date = self.today + relativedelta(months=4) - self.acct_line.write( - { - 'date_start': start_date, - 'recurring_next_date': start_date, - 'date_end': end_date, - } - ) + self._update_contract_line_date(start_date, start_date, end_date) self.acct_line.stop_plan_successor( suspension_start, suspension_end, True ) @@ -1321,6 +1316,7 @@ def test_cron_recurring_create_invoice(self): contracts = self.contract2 for i in range(10): contracts |= self.contract.copy() + contracts.mapped('contract_line_ids')._onchange_date_start() self.env['contract.contract'].cron_recurring_create_invoice() invoice_lines = self.env['account.invoice.line'].search( [('contract_line_id', 'in', @@ -1332,10 +1328,11 @@ def test_cron_recurring_create_invoice(self): ) def test_get_period_to_invoice_monthlylastday(self): - self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'monthlylastday' - self.acct_line.date_end = '2018-03-15' + self._update_contract_line_date( + '2018-01-05', '2018-01-05', '2018-03-15' + ) self.acct_line._onchange_date_start() first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( @@ -1363,11 +1360,11 @@ def test_get_period_to_invoice_monthlylastday(self): self.acct_line.manual_renew_needed = True def test_get_period_to_invoice_monthly_pre_paid_2(self): - self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'pre-paid' self.acct_line.recurring_rule_type = 'monthly' - self.acct_line.date_end = '2018-08-15' - self.acct_line._onchange_date_start() + self._update_contract_line_date( + '2018-01-05', '2018-01-05', '2018-08-15' + ) self.contract.recurring_create_invoice() first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( @@ -1377,6 +1374,7 @@ def test_get_period_to_invoice_monthly_pre_paid_2(self): self.assertEqual(first, to_date('2018-02-05')) self.assertEqual(last, to_date('2018-03-04')) self.acct_line.recurring_next_date = '2018-06-05' + self.acct_line.next_period_end_date = '2018-07-04' first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1386,11 +1384,11 @@ def test_get_period_to_invoice_monthly_pre_paid_2(self): self.assertEqual(last, to_date('2018-07-04')) def test_get_period_to_invoice_monthly_post_paid_2(self): - self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'monthly' - self.acct_line.date_end = '2018-08-15' - self.acct_line._onchange_date_start() + self._update_contract_line_date( + '2018-01-05', '2018-01-05', '2018-08-15' + ) self.contract.recurring_create_invoice() first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( @@ -1400,6 +1398,7 @@ def test_get_period_to_invoice_monthly_post_paid_2(self): self.assertEqual(first, to_date('2018-02-05')) self.assertEqual(last, to_date('2018-03-04')) self.acct_line.recurring_next_date = '2018-06-05' + self.acct_line.next_period_end_date = '2018-06-04' first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1409,11 +1408,11 @@ def test_get_period_to_invoice_monthly_post_paid_2(self): self.assertEqual(last, to_date('2018-06-04')) def test_get_period_to_invoice_monthly_post_paid(self): - self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'monthly' - self.acct_line.date_end = '2018-03-15' - self.acct_line._onchange_date_start() + self._update_contract_line_date( + '2018-01-05', '2018-01-05', '2018-03-15' + ) first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1439,11 +1438,11 @@ def test_get_period_to_invoice_monthly_post_paid(self): self.assertEqual(last, to_date('2018-03-15')) def test_get_period_to_invoice_monthly_pre_paid(self): - self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'pre-paid' self.acct_line.recurring_rule_type = 'monthly' - self.acct_line.date_end = '2018-03-15' - self.acct_line._onchange_date_start() + self._update_contract_line_date( + '2018-01-05', '2018-01-05', '2018-03-15' + ) first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1469,11 +1468,11 @@ def test_get_period_to_invoice_monthly_pre_paid(self): self.assertEqual(last, to_date('2018-03-15')) def test_get_period_to_invoice_yearly_post_paid(self): - self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'yearly' - self.acct_line.date_end = '2020-03-15' - self.acct_line._onchange_date_start() + self._update_contract_line_date( + '2018-01-05', '2018-01-05', '2020-03-15' + ) first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1499,11 +1498,11 @@ def test_get_period_to_invoice_yearly_post_paid(self): self.assertEqual(last, to_date('2020-03-15')) def test_get_period_to_invoice_yearly_pre_paid(self): - self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'pre-paid' self.acct_line.recurring_rule_type = 'yearly' - self.acct_line.date_end = '2020-03-15' - self.acct_line._onchange_date_start() + self._update_contract_line_date( + '2018-01-05', '2018-01-05', '2020-03-15' + ) first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1839,3 +1838,64 @@ def test_stop_at_last_date_invoiced(self): self.assertTrue(self.acct_line.recurring_next_date) self.acct_line.stop(self.acct_line.last_date_invoiced) self.assertFalse(self.acct_line.recurring_next_date) + + def test_contract_monthly_pre_paid_custom_first_period(self): + recurring_next_date = to_date('2019-04-01') + last_date_invoiced = to_date('2019-03-31') + self.acct_line.recurring_next_date = '2019-03-22' + self.acct_line.date_start = '2019-03-22' + self.acct_line.next_period_end_date = '2019-03-31' + self.acct_line.recurring_rule_type = 'monthly' + self.acct_line.recurring_invoicing_type = 'pre-paid' + self.contract.recurring_create_invoice() + invoices = self.contract._get_related_invoices() + self.assertTrue(invoices) + self.assertEqual( + self.acct_line.recurring_next_date, recurring_next_date + ) + self.assertEqual(self.acct_line.last_date_invoiced, last_date_invoiced) + + def test_contract_monthly_post_paid_custom_first_period(self): + recurring_next_date = to_date('2019-05-01') + last_date_invoiced = to_date('2019-03-31') + self.acct_line.recurring_next_date = '2019-04-22' + self.acct_line.date_start = '2019-03-22' + self.acct_line.next_period_end_date = '2019-03-31' + self.acct_line.recurring_rule_type = 'monthly' + self.acct_line.recurring_invoicing_type = 'post-paid' + self.contract.recurring_create_invoice() + invoices = self.contract._get_related_invoices() + self.assertTrue(invoices) + self.assertEqual( + self.acct_line.recurring_next_date, recurring_next_date + ) + self.assertEqual(self.acct_line.last_date_invoiced, last_date_invoiced) + + def test_contract_monthlylastday_custom_first_period(self): + recurring_next_date = to_date('2019-03-31') + last_date_invoiced = to_date('2019-03-28') + self.acct_line.recurring_next_date = '2019-04-22' + self.acct_line.date_start = '2019-03-22' + self.acct_line.next_period_end_date = '2019-03-28' + self.acct_line.recurring_rule_type = 'monthlylastday' + self.acct_line.recurring_invoicing_type = 'post-paid' + self.contract.recurring_create_invoice() + invoices = self.contract._get_related_invoices() + self.assertTrue(invoices) + self.assertEqual( + self.acct_line.recurring_next_date, recurring_next_date + ) + self.assertEqual(self.acct_line.last_date_invoiced, last_date_invoiced) + + def test_check_next_period_end_date(self): + self.acct_line.recurring_next_date = '2019-04-22' + self.acct_line.date_start = '2019-03-22' + self.acct_line.date_end = '2019-05-22' + with self.assertRaises(ValidationError): + self.acct_line.next_period_end_date = '2019-03-21' + with self.assertRaises(ValidationError): + self.acct_line.next_period_end_date = '2019-05-23' + with self.assertRaises(ValidationError): + self.acct_line.last_date_invoiced = '2019-04-22' + self.acct_line.next_period_end_date = False + self.acct_line.last_date_invoiced = '2019-04-22' diff --git a/contract/views/contract_line.xml b/contract/views/contract_line.xml index 2d14a81f8d..703f028040 100644 --- a/contract/views/contract_line.xml +++ b/contract/views/contract_line.xml @@ -15,14 +15,15 @@ + + - From 4bbcf03c806b136d23072bb02e27e9a4e11b7599 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Tue, 3 Dec 2019 17:23:53 +0100 Subject: [PATCH 2/2] make recurring_next_date computed --- contract/models/contract_line.py | 58 +++++++++- contract/tests/test_contract.py | 120 +++++++++++---------- product_contract/models/sale_order_line.py | 2 +- 3 files changed, 120 insertions(+), 60 deletions(-) diff --git a/contract/models/contract_line.py b/contract/models/contract_line.py index 07b26bf843..e60c687752 100644 --- a/contract/models/contract_line.py +++ b/contract/models/contract_line.py @@ -37,12 +37,21 @@ class ContractLine(models.Model): default=lambda self: fields.Date.context_today(self), ) date_end = fields.Date(string='Date End', index=True) - recurring_next_date = fields.Date(string='Date of Next Invoice') + recurring_next_date = fields.Date( + string='Date of Next Invoice', + compute='_compute_recurring_next_date', + inverse='_inverse_recurring_next_date', + store=True, + ) last_date_invoiced = fields.Date( string='Last Date Invoiced', readonly=True, copy=False ) + next_period_start_date = fields.Date( + string='Next Period Start Date', + compute='_compute_next_period_start_date', + ) next_period_end_date = fields.Date( - string='Next Period End Date', copy=False + string='Next Period End Date' ) termination_notice_date = fields.Date( string='Termination notice date', @@ -376,6 +385,49 @@ def _get_recurring_next_date( recurring_rule_type, recurring_interval ) + @api.depends('last_date_invoiced') + def _compute_next_period_start_date(self): + for rec in self: + if rec.last_date_invoiced: + rec.next_period_start_date = ( + rec.last_date_invoiced + relativedelta(days=1) + ) + else: + rec.next_period_start_date = rec.date_start + + @api.depends( + 'recurring_invoicing_type', + 'recurring_rule_type', + 'next_period_end_date', + 'date_start', + 'date_end', + 'last_date_invoiced', + ) + def _compute_recurring_next_date(self): + for rec in self: + if rec.next_period_end_date: + if rec.recurring_rule_type == 'monthlylastday': + rec.recurring_next_date = rec.next_period_end_date + else: + if rec.recurring_invoicing_type == 'pre-paid': + rec.recurring_next_date = rec.next_period_start_date + else: + rec.recurring_next_date = rec.next_period_end_date + relativedelta(days=1) + if rec.date_end and rec.recurring_next_date > rec.date_end: + rec.recurring_next_date = False + + @api.multi + def _inverse_recurring_next_date(self): + for rec in self: + # TODO: monthlylastday + if rec.recurring_next_date: + if rec.recurring_invoicing_type == 'pre-paid': + rec.next_period_end_date = rec.recurring_next_date + self.get_relative_delta( + rec.recurring_rule_type, rec.recurring_interval + ) - relativedelta(days=1) + else: + rec.next_period_end_date = rec.recurring_next_date - relativedelta(days=1) + @api.model def _get_date_end( self, date_start, auto_renew_rule_type, auto_renew_interval @@ -508,7 +560,7 @@ def _check_start_end_dates(self): % line.name ) - @api.constrains('next_period_end_date', 'last_date_invoiced') + @api.constrains('next_period_end_date', 'last_date_invoiced', 'date_end', 'date_start') def _check_next_period_end_date(self): for line in self.filtered('next_period_end_date'): if line.next_period_end_date < line.date_start or ( diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py index 65199a8aea..9148648b0a 100644 --- a/contract/tests/test_contract.py +++ b/contract/tests/test_contract.py @@ -108,23 +108,9 @@ def setUpClass(cls): cls.line_vals ) cls.acct_line.product_id.is_auto_renew = True - cls.acct_line._onchange_date_start() class TestContract(TestContractBase): - def _update_contract_line_date( - self, date_start, recurring_next_date, date_end - ): - self.acct_line.write( - { - 'date_start': date_start, - 'recurring_next_date': recurring_next_date, - 'next_period_end_date': recurring_next_date, - 'date_end': date_end, - } - ) - self.acct_line._onchange_date_start() - def _add_template_line(self, overrides=None): if overrides is None: overrides = {} @@ -154,7 +140,7 @@ def test_automatic_price(self): self.assertEqual(self.acct_line.price_unit, 10) def test_contract(self): - recurring_next_date = to_date('2018-02-01') + recurring_next_date = to_date('2018-02-15') self.assertAlmostEqual(self.acct_line.price_subtotal, 50.0) res = self.acct_line._onchange_product_id() self.assertIn('uom_id', res['domain']) @@ -171,11 +157,29 @@ def test_contract(self): self.assertAlmostEqual(self.inv_line.price_subtotal, 50.0) self.assertEqual(self.contract.user_id, self.invoice_monthly.user_id) + def test_contract_recurring_next_date(self): + recurring_next_date = to_date('2018-01-15') + self.assertEqual( + self.contract.recurring_next_date, recurring_next_date + ) + contract_line = self.acct_line.copy( + {'recurring_next_date': '2018-01-14'} + ) + recurring_next_date = to_date('2018-01-14') + self.assertEqual( + self.contract.recurring_next_date, recurring_next_date + ) + contract_line.cancel() + recurring_next_date = to_date('2018-01-15') + self.assertEqual( + self.contract.recurring_next_date, recurring_next_date + ) + def test_contract_daily(self): recurring_next_date = to_date('2018-02-23') last_date_invoiced = to_date('2018-02-22') + self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_rule_type = 'daily' - self._update_contract_line_date('2018-02-22', '2018-02-22', False) self.contract.pricelist_id = False self.contract.recurring_create_invoice() invoice_daily = self.contract._get_related_invoices() @@ -188,9 +192,9 @@ def test_contract_daily(self): def test_contract_weekly_post_paid(self): recurring_next_date = to_date('2018-03-01') last_date_invoiced = to_date('2018-02-21') + self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_rule_type = 'weekly' self.acct_line.recurring_invoicing_type = 'post-paid' - self._update_contract_line_date('2018-02-15', '2018-02-15', False) self.contract.recurring_create_invoice() invoices_weekly = self.contract._get_related_invoices() self.assertTrue(invoices_weekly) @@ -202,9 +206,9 @@ def test_contract_weekly_post_paid(self): def test_contract_weekly_pre_paid(self): recurring_next_date = to_date('2018-03-01') last_date_invoiced = to_date('2018-02-28') + self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_rule_type = 'weekly' self.acct_line.recurring_invoicing_type = 'pre-paid' - self._update_contract_line_date('2018-02-22', '2018-02-22', False) self.contract.recurring_create_invoice() invoices_weekly = self.contract._get_related_invoices() self.assertTrue(invoices_weekly) @@ -216,12 +220,12 @@ def test_contract_weekly_pre_paid(self): def test_contract_yearly_post_paid(self): recurring_next_date = to_date('2019-02-22') last_date_invoiced = to_date('2018-02-21') + self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_rule_type = 'yearly' self.acct_line.recurring_invoicing_type = 'post-paid' - self._update_contract_line_date('2017-02-22', '2018-02-22', False) self.contract.recurring_create_invoice() - invoices = self.contract._get_related_invoices() - self.assertTrue(invoices) + invoices_weekly = self.contract._get_related_invoices() + self.assertTrue(invoices_weekly) self.assertEqual( self.acct_line.recurring_next_date, recurring_next_date ) @@ -231,10 +235,9 @@ def test_contract_yearly_pre_paid(self): recurring_next_date = to_date('2019-02-22') last_date_invoiced = to_date('2019-02-21') self.acct_line.recurring_rule_type = 'yearly' + self.acct_line.date_end = '2020-02-22' + self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_invoicing_type = 'pre-paid' - self._update_contract_line_date( - '2018-02-22', '2018-02-22', '2020-02-22' - ) self.contract.recurring_create_invoice() invoices_weekly = self.contract._get_related_invoices() self.assertTrue(invoices_weekly) @@ -245,11 +248,10 @@ def test_contract_yearly_pre_paid(self): def test_contract_monthly_lastday(self): recurring_next_date = to_date('2018-03-31') - last_date_invoiced = to_date('2018-03-22') + last_date_invoiced = to_date('2018-02-22') + self.acct_line.recurring_next_date = '2018-02-22' self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'monthlylastday' - self._update_contract_line_date('2018-03-01', '2018-03-01', False) - self.acct_line.next_period_end_date = '2018-03-22' self.contract.recurring_create_invoice() invoices_monthly_lastday = self.contract._get_related_invoices() self.assertTrue(invoices_monthly_lastday) @@ -508,7 +510,6 @@ def test_same_date_start_and_date_end(self): 'date_start': self.today, 'date_end': self.today, 'recurring_next_date': self.today, - 'next_period_end_date': self.today, } ) self.contract._compute_recurring_next_date() @@ -714,10 +715,8 @@ def test_stop_plan_successor_contract_line_0(self): { 'date_start': self.today + relativedelta(months=5), 'recurring_next_date': self.today + relativedelta(months=5), - 'next_period_end_date': self.today + relativedelta(months=5), } ) - self.acct_line._onchange_date_start() self.acct_line.write( { 'successor_contract_line_id': successor_contract_line.id, @@ -1189,7 +1188,13 @@ def test_cancel_uncancel_with_predecessor(self): suspension_end = self.today + relativedelta(months=5) start_date = self.today end_date = self.today + relativedelta(months=4) - self._update_contract_line_date(start_date, start_date, end_date) + self.acct_line.write( + { + 'date_start': start_date, + 'recurring_next_date': start_date, + 'date_end': end_date, + } + ) self.acct_line.stop_plan_successor( suspension_start, suspension_end, True ) @@ -1316,7 +1321,6 @@ def test_cron_recurring_create_invoice(self): contracts = self.contract2 for i in range(10): contracts |= self.contract.copy() - contracts.mapped('contract_line_ids')._onchange_date_start() self.env['contract.contract'].cron_recurring_create_invoice() invoice_lines = self.env['account.invoice.line'].search( [('contract_line_id', 'in', @@ -1328,11 +1332,10 @@ def test_cron_recurring_create_invoice(self): ) def test_get_period_to_invoice_monthlylastday(self): + self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'monthlylastday' - self._update_contract_line_date( - '2018-01-05', '2018-01-05', '2018-03-15' - ) + self.acct_line.date_end = '2018-03-15' self.acct_line._onchange_date_start() first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( @@ -1360,11 +1363,11 @@ def test_get_period_to_invoice_monthlylastday(self): self.acct_line.manual_renew_needed = True def test_get_period_to_invoice_monthly_pre_paid_2(self): + self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'pre-paid' self.acct_line.recurring_rule_type = 'monthly' - self._update_contract_line_date( - '2018-01-05', '2018-01-05', '2018-08-15' - ) + self.acct_line.date_end = '2018-08-15' + self.acct_line._onchange_date_start() self.contract.recurring_create_invoice() first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( @@ -1374,7 +1377,6 @@ def test_get_period_to_invoice_monthly_pre_paid_2(self): self.assertEqual(first, to_date('2018-02-05')) self.assertEqual(last, to_date('2018-03-04')) self.acct_line.recurring_next_date = '2018-06-05' - self.acct_line.next_period_end_date = '2018-07-04' first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1384,11 +1386,11 @@ def test_get_period_to_invoice_monthly_pre_paid_2(self): self.assertEqual(last, to_date('2018-07-04')) def test_get_period_to_invoice_monthly_post_paid_2(self): + self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'monthly' - self._update_contract_line_date( - '2018-01-05', '2018-01-05', '2018-08-15' - ) + self.acct_line.date_end = '2018-08-15' + self.acct_line._onchange_date_start() self.contract.recurring_create_invoice() first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( @@ -1398,7 +1400,6 @@ def test_get_period_to_invoice_monthly_post_paid_2(self): self.assertEqual(first, to_date('2018-02-05')) self.assertEqual(last, to_date('2018-03-04')) self.acct_line.recurring_next_date = '2018-06-05' - self.acct_line.next_period_end_date = '2018-06-04' first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1408,11 +1409,11 @@ def test_get_period_to_invoice_monthly_post_paid_2(self): self.assertEqual(last, to_date('2018-06-04')) def test_get_period_to_invoice_monthly_post_paid(self): + self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'monthly' - self._update_contract_line_date( - '2018-01-05', '2018-01-05', '2018-03-15' - ) + self.acct_line.date_end = '2018-03-15' + self.acct_line._onchange_date_start() first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1438,11 +1439,11 @@ def test_get_period_to_invoice_monthly_post_paid(self): self.assertEqual(last, to_date('2018-03-15')) def test_get_period_to_invoice_monthly_pre_paid(self): + self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'pre-paid' self.acct_line.recurring_rule_type = 'monthly' - self._update_contract_line_date( - '2018-01-05', '2018-01-05', '2018-03-15' - ) + self.acct_line.date_end = '2018-03-15' + self.acct_line._onchange_date_start() first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1468,11 +1469,11 @@ def test_get_period_to_invoice_monthly_pre_paid(self): self.assertEqual(last, to_date('2018-03-15')) def test_get_period_to_invoice_yearly_post_paid(self): + self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'yearly' - self._update_contract_line_date( - '2018-01-05', '2018-01-05', '2020-03-15' - ) + self.acct_line.date_end = '2020-03-15' + self.acct_line._onchange_date_start() first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1498,11 +1499,11 @@ def test_get_period_to_invoice_yearly_post_paid(self): self.assertEqual(last, to_date('2020-03-15')) def test_get_period_to_invoice_yearly_pre_paid(self): + self.acct_line.date_start = '2018-01-05' self.acct_line.recurring_invoicing_type = 'pre-paid' self.acct_line.recurring_rule_type = 'yearly' - self._update_contract_line_date( - '2018-01-05', '2018-01-05', '2020-03-15' - ) + self.acct_line.date_end = '2020-03-15' + self.acct_line._onchange_date_start() first, last, recurring_next_date = \ self.acct_line._get_period_to_invoice( self.acct_line.last_date_invoiced, @@ -1533,6 +1534,12 @@ def test_unlink(self): def test_contract_line_state(self): lines = self.env['contract.line'] + new_line = self.acct_line.copy() + new_line.write({ + 'date_start': self.today + relativedelta(months=3), + 'recurring_next_date': self.today + relativedelta(months=3), + 'date_end': self.today + relativedelta(months=5), + }) # upcoming lines |= self.acct_line.copy( { @@ -1897,5 +1904,6 @@ def test_check_next_period_end_date(self): self.acct_line.next_period_end_date = '2019-05-23' with self.assertRaises(ValidationError): self.acct_line.last_date_invoiced = '2019-04-22' - self.acct_line.next_period_end_date = False + with self.assertRaises(ValidationError): + self.acct_line.next_period_end_date = False self.acct_line.last_date_invoiced = '2019-04-22' diff --git a/product_contract/models/sale_order_line.py b/product_contract/models/sale_order_line.py index 6f55f5c4b6..f3adb99fad 100644 --- a/product_contract/models/sale_order_line.py +++ b/product_contract/models/sale_order_line.py @@ -108,7 +108,7 @@ def _prepare_contract_line_values( self.ensure_one() recurring_next_date = self.env[ 'contract.line' - ]._compute_first_recurring_next_date( + ]._get_recurring_next_date( self.date_start or fields.Date.today(), self.recurring_invoicing_type, self.recurring_rule_type,