From 36d4d0e9490aa50eb832b12d6f99a567b1282877 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Thu, 20 Dec 2018 21:50:30 +0100 Subject: [PATCH] [FIX+IMP] contract: several things [FIX] - as date_start is required, constraints on it become useless [FIX] - for finished contract _get_lines_to_invoice should return False [FIX] - default value for active field in contract line [FIX] - fix flake8 [IMP] - check invoice line vals before assignment [FIX] - Fix unit tests. [FIX] - do not copy last_date_invoiced on plan_successor [FIX] - renew only recurring_invoices contract [FIX] - filter by termination_notice for contract line to renew [IMP] - Improve inprogress search filter [IMP] - Link invoice line to contract lines [IMP] - Add index on contract line [IMP] - Add is_suspended flag and _search_state [IMP] - Add is_suspended flag to stop contract line [MV] - move onchange method to contract_product module [IMP] - Replace is_suspended flag by manual_renew_needed Add a computed field for the first date of the termination notice period Adapt state compute and search method [IMP] - Improve unit tests --- contract/__manifest__.py | 1 - .../migrations/12.0.2.0.0/pre-migration.py | 4 +- contract/models/__init__.py | 1 + contract/models/abstract_contract_line.py | 20 - contract/models/account_invoice.py | 5 +- contract/models/account_invoice_line.py | 12 + contract/models/contract.py | 94 ++- contract/models/contract_line.py | 254 +++++-- contract/tests/test_contract.py | 702 +++++++++++++----- contract/views/account_invoice_view.xml | 44 -- contract/views/contract.xml | 26 +- contract/views/contract_line.xml | 8 + contract/wizards/contract_line_wizard.py | 12 +- contract/wizards/contract_line_wizard.xml | 1 + 14 files changed, 843 insertions(+), 341 deletions(-) create mode 100644 contract/models/account_invoice_line.py diff --git a/contract/__manifest__.py b/contract/__manifest__.py index 10d14ddcd..dcf9aac94 100644 --- a/contract/__manifest__.py +++ b/contract/__manifest__.py @@ -33,7 +33,6 @@ 'views/contract.xml', 'views/contract_template_line.xml', 'views/contract_template.xml', - 'views/account_invoice_view.xml', 'views/contract_line.xml', 'views/res_partner_view.xml', ], diff --git a/contract/migrations/12.0.2.0.0/pre-migration.py b/contract/migrations/12.0.2.0.0/pre-migration.py index e8f428940..0d53bb05a 100644 --- a/contract/migrations/12.0.2.0.0/pre-migration.py +++ b/contract/migrations/12.0.2.0.0/pre-migration.py @@ -19,6 +19,6 @@ def migrate(cr, version): lambda c: not c.create_invoice_visibility ) cr.execute( - "UPDATE account_analytic_account set recurring_next_date=null where id in (%)" - % ','.join(finished_contract.ids) + "UPDATE account_analytic_account set recurring_next_date=null " + "where id in (%)" % ','.join(finished_contract.ids) ) diff --git a/contract/models/__init__.py b/contract/models/__init__.py index 0bbc06e94..28dc99228 100644 --- a/contract/models/__init__.py +++ b/contract/models/__init__.py @@ -7,4 +7,5 @@ from . import contract from . import contract_template_line from . import contract_line from . import account_invoice +from . import account_invoice_line from . import res_partner diff --git a/contract/models/abstract_contract_line.py b/contract/models/abstract_contract_line.py index 92dc584b0..b4ab198f2 100644 --- a/contract/models/abstract_contract_line.py +++ b/contract/models/abstract_contract_line.py @@ -205,23 +205,3 @@ class AccountAbstractAnalyticContractLine(models.AbstractModel): vals['price_unit'] = product.price self.update(vals) return {'domain': domain} - - @api.onchange('product_id') - def _onchange_product_id_recurring_info(self): - for rec in self: - rec.date_start = fields.Date.today() - if rec.product_id.is_contract: - rec.recurring_rule_type = rec.product_id.recurring_rule_type - rec.recurring_invoicing_type = ( - rec.product_id.recurring_invoicing_type - ) - rec.recurring_interval = rec.product_id.recurring_interval - rec.is_auto_renew = rec.product_id.is_auto_renew - rec.auto_renew_interval = rec.product_id.auto_renew_interval - rec.auto_renew_rule_type = rec.product_id.auto_renew_rule_type - rec.termination_notice_interval = ( - rec.product_id.termination_notice_interval - ) - rec.termination_notice_rule_type = ( - rec.product_id.termination_notice_rule_type - ) diff --git a/contract/models/account_invoice.py b/contract/models/account_invoice.py index b651ea2ad..7d9f4ceb3 100644 --- a/contract/models/account_invoice.py +++ b/contract/models/account_invoice.py @@ -7,6 +7,7 @@ from odoo import fields, models class AccountInvoice(models.Model): _inherit = 'account.invoice' - contract_id = fields.Many2one( - 'account.analytic.account', string='Contract' + # We keep this field for migration purpose + old_contract_id = fields.Many2one( + 'account.analytic.account', oldname="contract_id" ) diff --git a/contract/models/account_invoice_line.py b/contract/models/account_invoice_line.py new file mode 100644 index 000000000..30e1829c8 --- /dev/null +++ b/contract/models/account_invoice_line.py @@ -0,0 +1,12 @@ +# Copyright 2018 ACSONE SA/NV. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class AccountInvoiceLine(models.Model): + _inherit = 'account.invoice.line' + + contract_line_id = fields.Many2one( + 'account.analytic.invoice.line', string='Contract Line', index=True + ) diff --git a/contract/models/contract.py b/contract/models/contract.py index 1fd747ee6..393e121e9 100644 --- a/contract/models/contract.py +++ b/contract/models/contract.py @@ -48,9 +48,48 @@ class AccountAnalyticAccount(models.Model): compute='_compute_date_end', string='Date End', store=True ) payment_term_id = fields.Many2one( - comodel_name='account.payment.term', - string='Payment Terms', + comodel_name='account.payment.term', string='Payment Terms' ) + invoice_count = fields.Integer(compute="_compute_invoice_count") + + @api.multi + def _get_related_invoices(self): + self.ensure_one() + + invoices = ( + self.env['account.invoice.line'] + .search( + [ + ( + 'contract_line_id', + 'in', + self.recurring_invoice_line_ids.ids, + ) + ] + ) + .mapped('invoice_id') + ) + invoices |= self.env['account.invoice'].search( + [('old_contract_id', '=', self.id)] + ) + return invoices + + @api.multi + def _compute_invoice_count(self): + for rec in self: + rec.invoice_count = len(rec._get_related_invoices()) + + @api.multi + def action_show_invoices(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': 'Invoices', + 'res_model': 'account.invoice', + 'view_type': 'form', + 'view_mode': 'tree,kanban,form,calendar,pivot,graph,activity', + 'domain': [('id', 'in', self._get_related_invoices().ids)], + } @api.depends('recurring_invoice_line_ids.date_end') def _compute_date_end(self): @@ -118,7 +157,7 @@ class AccountAnalyticAccount(models.Model): for contract in self.filtered('recurring_invoices'): if not contract.partner_id: raise ValidationError( - _("You must supply a customer for the contract '%s'") + _("You must supply a partner for the contract '%s'") % contract.name ) @@ -141,17 +180,6 @@ class AccountAnalyticAccount(models.Model): @api.multi def _prepare_invoice(self, date_invoice, journal=None): self.ensure_one() - if not self.partner_id: - if self.contract_type == 'purchase': - raise ValidationError( - _("You must first select a Supplier for Contract %s!") - % self.name - ) - else: - raise ValidationError( - _("You must first select a Customer for Contract %s!") - % self.name - ) if not journal: journal = ( self.journal_id @@ -180,15 +208,12 @@ class AccountAnalyticAccount(models.Model): return { 'reference': self.code, 'type': invoice_type, - 'partner_id': self.partner_id.address_get(['invoice'])[ - 'invoice' - ], + 'partner_id': self.partner_id.address_get(['invoice'])['invoice'], 'currency_id': currency.id, 'date_invoice': date_invoice, 'journal_id': journal.id, 'origin': self.name, 'company_id': self.company_id.id, - 'contract_id': self.id, 'user_id': self.partner_id.user_id.id, } @@ -240,11 +265,13 @@ class AccountAnalyticAccount(models.Model): price_unit = invoice_line.price_unit invoice_line.invoice_id = new_invoice invoice_line._onchange_product_id() - invoice_line.update({ - 'name': name, - 'account_analytic_id': account_analytic_id, - 'price_unit': price_unit, - }) + invoice_line.update( + { + 'name': name, + 'account_analytic_id': account_analytic_id, + 'price_unit': price_unit, + } + ) return new_invoice._convert_to_write(new_invoice._cache) @api.model @@ -268,7 +295,8 @@ class AccountAnalyticAccount(models.Model): final_invoices_values = [] for invoice_values in invoices_values: final_invoices_values.append( - self._finalize_invoice_values(invoice_values)) + self._finalize_invoice_values(invoice_values) + ) invoices = self.env['account.invoice'].create(final_invoices_values) self._finalize_invoice_creation(invoices) return invoices @@ -302,8 +330,10 @@ class AccountAnalyticAccount(models.Model): """ self.ensure_one() return self.recurring_invoice_line_ids.filtered( - lambda l: not l.is_canceled and l.recurring_next_date - and l.recurring_next_date <= date_ref) + lambda l: not l.is_canceled + and l.recurring_next_date + and l.recurring_next_date <= date_ref + ) @api.multi def _prepare_recurring_invoices_values(self, date_ref=False): @@ -317,15 +347,23 @@ class AccountAnalyticAccount(models.Model): for contract in self: if not date_ref: date_ref = contract.recurring_next_date + if not date_ref: + # this use case is possible when recurring_create_invoice is + # called for a finished contract + continue contract_lines = contract._get_lines_to_invoice(date_ref) if not contract_lines: continue invoice_values = contract._prepare_invoice(date_ref) for line in contract_lines: invoice_values.setdefault('invoice_line_ids', []) - invoice_values['invoice_line_ids'].append( - (0, 0, line._prepare_invoice_line(invoice_id=False)) + invoice_line_values = line._prepare_invoice_line( + invoice_id=False ) + if invoice_line_values: + invoice_values['invoice_line_ids'].append( + (0, 0, invoice_line_values) + ) invoices_values.append(invoice_values) contract_lines._update_recurring_next_date() return invoices_values diff --git a/contract/models/contract_line.py b/contract/models/contract_line.py index c87abf911..d5ac6c429 100644 --- a/contract/models/contract_line.py +++ b/contract/models/contract_line.py @@ -1,4 +1,5 @@ # Copyright 2017 LasLabs Inc. +# Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import timedelta @@ -32,6 +33,12 @@ class AccountAnalyticInvoiceLine(models.Model): last_date_invoiced = fields.Date( string='Last Date Invoiced', readonly=True, copy=False ) + termination_notice_date = fields.Date( + string='Termination notice date', + compute="_compute_termination_notice_date", + store=True, + copy=False, + ) create_invoice_visibility = fields.Boolean( compute='_compute_create_invoice_visibility' ) @@ -40,6 +47,7 @@ class AccountAnalyticInvoiceLine(models.Model): string="Successor Contract Line", required=False, readonly=True, + index=True, copy=False, help="In case of restart after suspension, this field contain the new " "contract line created.", @@ -49,9 +57,17 @@ class AccountAnalyticInvoiceLine(models.Model): string="Predecessor Contract Line", required=False, readonly=True, + index=True, copy=False, help="Contract Line origin of this one.", ) + manual_renew_needed = fields.Boolean( + string="Manual renew needed", + default=False, + help="This flag is used to make a difference between a definitive stop" + "and temporary one for which a user is not able to plan a" + "successor in advance", + ) is_plan_successor_allowed = fields.Boolean( string="Plan successor allowed?", compute='_compute_allowed' ) @@ -72,37 +88,186 @@ class AccountAnalyticInvoiceLine(models.Model): selection=[ ('upcoming', 'Upcoming'), ('in-progress', 'In-progress'), + ('to-renew', 'To renew'), ('upcoming-close', 'Upcoming Close'), ('closed', 'Closed'), ('canceled', 'Canceled'), ], compute="_compute_state", + search='_search_state', ) active = fields.Boolean( string="Active", related="contract_id.active", store=True, readonly=True, + default=True, ) @api.multi + @api.depends( + 'date_end', + 'termination_notice_rule_type', + 'termination_notice_interval', + ) + def _compute_termination_notice_date(self): + for rec in self: + if rec.date_end: + rec.termination_notice_date = ( + rec.date_end + - self.get_relative_delta( + rec.termination_notice_rule_type, + rec.termination_notice_interval, + ) + ) + + @api.multi + @api.depends('is_canceled', 'date_start', 'date_end', 'is_auto_renew') def _compute_state(self): today = fields.Date.context_today(self) for rec in self: - if rec.date_start: - if rec.is_canceled: - rec.state = 'canceled' - elif today < rec.date_start: - rec.state = 'upcoming' - elif not rec.date_end or ( - today <= rec.date_end and rec.is_auto_renew + if rec.is_canceled: + rec.state = 'canceled' + continue + + if rec.date_start and rec.date_start > today: + # Before period + rec.state = 'upcoming' + continue + if ( + rec.date_start + and rec.date_start <= today + and (not rec.date_end or rec.date_end >= today) + ): + # In period + if ( + rec.termination_notice_date + and rec.termination_notice_date < today + and not rec.is_auto_renew + and not rec.manual_renew_needed ): - rec.state = 'in-progress' - elif today <= rec.date_end and not rec.is_auto_renew: rec.state = 'upcoming-close' + else: + rec.state = 'in-progress' + continue + if rec.date_end and rec.date_end < today: + # After + if ( + rec.manual_renew_needed + and not rec.successor_contract_line_id + or rec.is_auto_renew + ): + rec.state = 'to-renew' else: rec.state = 'closed' + @api.model + def _get_state_domain(self, state): + today = fields.Date.context_today(self) + if state == 'upcoming': + return [ + "&", + ('date_start', '>', today), + ('is_canceled', '=', False), + ] + if state == 'in-progress': + return [ + "&", + "&", + "&", + ('date_start', '<=', today), + ('is_canceled', '=', False), + "|", + ('date_end', '>=', today), + ('date_end', '=', False), + "|", + "&", + ('is_auto_renew', '=', True), + ('is_auto_renew', '=', False), + ('termination_notice_date', '>', today), + ] + if state == 'to-renew': + return [ + "&", + "&", + ('is_canceled', '=', False), + ('date_end', '<', today), + "|", + "&", + ('manual_renew_needed', '=', True), + ('successor_contract_line_id', '=', False), + ('is_auto_renew', '=', True), + ] + if state == 'upcoming-close': + return [ + "&", + "&", + "&", + "&", + "&", + ('date_start', '<=', today), + ('is_auto_renew', '=', False), + ('manual_renew_needed', '=', False), + ('is_canceled', '=', False), + ('termination_notice_date', '<', today), + ('date_end', '>=', today), + ] + if state == 'closed': + return [ + "&", + "&", + "&", + ('is_canceled', '=', False), + ('date_end', '<', today), + ('is_auto_renew', '=', False), + "|", + "&", + ('manual_renew_needed', '=', True), + ('successor_contract_line_id', '!=', False), + ('manual_renew_needed', '=', False), + ] + if state == 'canceled': + return [('is_canceled', '=', True)] + + @api.model + def _search_state(self, operator, value): + states = [ + 'upcoming', + 'in-progress', + 'to-renew', + 'upcoming-close', + 'closed', + 'canceled', + ] + if operator == '!=' and not value: + return [] + if operator == '=' and not value: + return [('id', '=', False)] + if operator == '=': + return self._get_state_domain(value) + if operator == '!=': + domain = [] + for state in states: + if state != value: + if domain: + domain.insert(0, '|') + domain.extend(self._get_state_domain(state)) + return domain + if operator == 'in': + domain = [] + if not value: + return [('id', '=', False)] + for state in value: + if domain: + domain.insert(0, '|') + domain.extend(self._get_state_domain(state)) + return domain + + if operator == 'not in': + return self._search_state( + 'in', [state for state in states if state not in value] + ) + @api.depends( 'date_start', 'date_end', @@ -282,15 +447,6 @@ class AccountAnalyticInvoiceLine(models.Model): % rec.name ) - @api.constrains('date_start') - def _check_date_start_recurring_invoices(self): - for line in self.filtered('contract_id.recurring_invoices'): - if not line.date_start: - raise ValidationError( - _("You must supply a start date for contract line '%s'") - % line.name - ) - @api.constrains('date_start', 'date_end') def _check_start_end_dates(self): for line in self.filtered('date_end'): @@ -324,6 +480,7 @@ class AccountAnalyticInvoiceLine(models.Model): 'quantity': self.quantity, 'uom_id': self.uom_id.id, 'discount': self.discount, + 'contract_line_id': self.id, } if invoice_id: invoice_line_vals['invoice_id'] = invoice_id.id @@ -393,31 +550,18 @@ class AccountAnalyticInvoiceLine(models.Model): new_date = old_date + self.get_relative_delta( rec.recurring_rule_type, rec.recurring_interval ) - if rec.recurring_rule_type == 'monthlylastday': - rec.last_date_invoiced = ( - old_date - if rec.date_end and old_date < rec.date_end - else rec.date_end - ) + last_date_invoiced = old_date elif rec.recurring_invoicing_type == 'post-paid': - rec.last_date_invoiced = ( - old_date - relativedelta(days=1) - if rec.date_end and old_date < rec.date_end - else rec.date_end - ) + last_date_invoiced = old_date - relativedelta(days=1) elif rec.recurring_invoicing_type == 'pre-paid': - rec.last_date_invoiced = ( - new_date - relativedelta(days=1) - if rec.date_end and new_date < rec.date_end - else rec.date_end - ) - if ( - rec.last_date_invoiced - and rec.last_date_invoiced == rec.date_end - ): + 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 @api.multi @@ -481,7 +625,7 @@ class AccountAnalyticInvoiceLine(models.Model): rec.date_start = new_date_start @api.multi - def stop(self, date_end, post_message=True): + def stop(self, date_end, manual_renew_needed=False, post_message=True): """ Put date_end on contract line We don't consider contract lines that end's before the new end date @@ -508,9 +652,20 @@ class AccountAnalyticInvoiceLine(models.Model): ) ) rec.contract_id.message_post(body=msg) - rec.write({'date_end': date_end, 'is_auto_renew': False}) + rec.write( + { + 'date_end': date_end, + 'is_auto_renew': False, + "manual_renew_needed": manual_renew_needed, + } + ) else: - rec.write({'is_auto_renew': False}) + rec.write( + { + 'is_auto_renew': False, + "manual_renew_needed": manual_renew_needed, + } + ) return True @api.multi @@ -527,6 +682,7 @@ class AccountAnalyticInvoiceLine(models.Model): ) new_vals = self.read()[0] new_vals.pop("id", None) + new_vals.pop("last_date_invoiced", None) values = self._convert_to_write(new_vals) values['date_start'] = date_start values['date_end'] = date_end @@ -644,7 +800,9 @@ class AccountAnalyticInvoiceLine(models.Model): + relativedelta(days=1) ) rec.stop( - date_start - relativedelta(days=1), post_message=False + date_start - relativedelta(days=1), + manual_renew_needed=True, + post_message=False, ) contract_line |= rec.plan_successor( new_date_start, @@ -664,7 +822,9 @@ class AccountAnalyticInvoiceLine(models.Model): new_date_end = rec.date_end rec.stop( - date_start - relativedelta(days=1), post_message=False + date_start - relativedelta(days=1), + manual_renew_needed=True, + post_message=False, ) contract_line |= rec.plan_successor( new_date_start, @@ -839,7 +999,7 @@ class AccountAnalyticInvoiceLine(models.Model): res = self.env['account.analytic.invoice.line'] for rec in self: is_auto_renew = rec.is_auto_renew - rec.stop(rec.date_end, post_message=False) + rec.is_auto_renew = False date_start, date_end = rec._get_renewal_dates() new_line = rec.plan_successor( date_start, date_end, is_auto_renew, post_message=False @@ -863,13 +1023,11 @@ class AccountAnalyticInvoiceLine(models.Model): @api.model def _contract_line_to_renew_domain(self): - date_ref = fields.Date.context_today(self) + self.get_relative_delta( - self.termination_notice_rule_type, self.termination_notice_interval - ) return [ ('is_auto_renew', '=', True), - ('date_end', '<=', date_ref), ('is_canceled', '=', False), + ('contract_id.recurring_invoices', '=', True), + ('termination_notice_date', '<=', fields.Date.context_today(self)), ] @api.model diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py index b1482d032..f7eb345b7 100644 --- a/contract/tests/test_contract.py +++ b/contract/tests/test_contract.py @@ -17,17 +17,19 @@ class TestContractBase(common.SavepointCase): @classmethod def setUpClass(cls): super(TestContractBase, cls).setUpClass() + cls.today = fields.Date.today() cls.partner = cls.env.ref('base.res_partner_2') - cls.product = cls.env.ref('product.product_product_2') - cls.product.taxes_id += cls.env['account.tax'].search( + cls.product_1 = cls.env.ref('product.product_product_1') + cls.product_2 = cls.env.ref('product.product_product_2') + cls.product_1.taxes_id += cls.env['account.tax'].search( [('type_tax_use', '=', 'sale')], limit=1 ) - cls.product.description_sale = 'Test description sale' + cls.product_1.description_sale = 'Test description sale' cls.line_template_vals = { - 'product_id': cls.product.id, + 'product_id': cls.product_1.id, 'name': 'Services from #START# to #END#', 'quantity': 1, - 'uom_id': cls.product.uom_id.id, + 'uom_id': cls.product_1.uom_id.id, 'price_unit': 100, 'discount': 50, 'recurring_rule_type': 'yearly', @@ -44,7 +46,7 @@ class TestContractBase(common.SavepointCase): cls.env['product.pricelist.item'].create( { 'pricelist_id': cls.partner.property_product_pricelist.id, - 'product_id': cls.product.id, + 'product_id': cls.product_1.id, 'compute_price': 'formula', 'base': 'list_price', } @@ -69,10 +71,10 @@ class TestContractBase(common.SavepointCase): 0, 0, { - 'product_id': cls.product.id, + 'product_id': cls.product_1.id, 'name': 'Services from #START# to #END#', 'quantity': 1, - 'uom_id': cls.product.uom_id.id, + 'uom_id': cls.product_1.uom_id.id, 'price_unit': 100, 'discount': 50, 'recurring_rule_type': 'monthly', @@ -86,18 +88,17 @@ class TestContractBase(common.SavepointCase): ) cls.line_vals = { 'contract_id': cls.contract.id, - 'product_id': cls.product.id, + 'product_id': cls.product_1.id, 'name': 'Services from #START# to #END#', 'quantity': 1, - 'uom_id': cls.product.uom_id.id, + 'uom_id': cls.product_1.uom_id.id, 'price_unit': 100, 'discount': 50, 'recurring_rule_type': 'monthly', 'recurring_interval': 1, 'date_start': '2018-01-01', - 'date_end': '2019-01-01', 'recurring_next_date': '2018-01-15', - 'is_auto_renew': True, + 'is_auto_renew': False, } cls.acct_line = cls.env['account.analytic.invoice.line'].create( cls.line_vals @@ -112,7 +113,6 @@ class TestContract(TestContractBase): vals = self.line_vals.copy() del vals['contract_id'] del vals['date_start'] - del vals['date_end'] vals['contract_id'] = self.template.id vals.update(overrides) return self.env['account.analytic.contract.line'].create(vals) @@ -123,7 +123,7 @@ class TestContract(TestContractBase): def test_automatic_price(self): self.acct_line.automatic_price = True - self.product.list_price = 1100 + self.product_1.list_price = 1100 self.assertEqual(self.acct_line.price_unit, 1100) # Try to write other price self.acct_line.price_unit = 10 @@ -145,9 +145,7 @@ class TestContract(TestContractBase): self.contract.partner_id = False self.contract.partner_id = self.partner.id self.contract.recurring_create_invoice() - self.invoice_monthly = self.env['account.invoice'].search( - [('contract_id', '=', self.contract.id)] - ) + self.invoice_monthly = self.contract._get_related_invoices() self.assertTrue(self.invoice_monthly) self.assertEqual( self.acct_line.recurring_next_date, recurring_next_date @@ -184,9 +182,7 @@ class TestContract(TestContractBase): self.acct_line.recurring_rule_type = 'daily' self.contract.pricelist_id = False self.contract.recurring_create_invoice() - invoice_daily = self.env['account.invoice'].search( - [('contract_id', '=', self.contract.id)] - ) + invoice_daily = self.contract._get_related_invoices() self.assertTrue(invoice_daily) self.assertEqual( self.acct_line.recurring_next_date, recurring_next_date @@ -200,9 +196,7 @@ class TestContract(TestContractBase): self.acct_line.recurring_rule_type = 'weekly' self.acct_line.recurring_invoicing_type = 'post-paid' self.contract.recurring_create_invoice() - invoices_weekly = self.env['account.invoice'].search( - [('contract_id', '=', self.contract.id)] - ) + invoices_weekly = self.contract._get_related_invoices() self.assertTrue(invoices_weekly) self.assertEqual( self.acct_line.recurring_next_date, recurring_next_date @@ -216,9 +210,7 @@ class TestContract(TestContractBase): self.acct_line.recurring_rule_type = 'weekly' self.acct_line.recurring_invoicing_type = 'pre-paid' self.contract.recurring_create_invoice() - invoices_weekly = self.env['account.invoice'].search( - [('contract_id', '=', self.contract.id)] - ) + invoices_weekly = self.contract._get_related_invoices() self.assertTrue(invoices_weekly) self.assertEqual( self.acct_line.recurring_next_date, recurring_next_date @@ -232,9 +224,7 @@ class TestContract(TestContractBase): self.acct_line.recurring_rule_type = 'yearly' self.acct_line.recurring_invoicing_type = 'post-paid' self.contract.recurring_create_invoice() - invoices_weekly = self.env['account.invoice'].search( - [('contract_id', '=', self.contract.id)] - ) + invoices_weekly = self.contract._get_related_invoices() self.assertTrue(invoices_weekly) self.assertEqual( self.acct_line.recurring_next_date, recurring_next_date @@ -249,9 +239,7 @@ class TestContract(TestContractBase): self.acct_line.recurring_rule_type = 'yearly' self.acct_line.recurring_invoicing_type = 'pre-paid' self.contract.recurring_create_invoice() - invoices_weekly = self.env['account.invoice'].search( - [('contract_id', '=', self.contract.id)] - ) + invoices_weekly = self.contract._get_related_invoices() self.assertTrue(invoices_weekly) self.assertEqual( self.acct_line.recurring_next_date, recurring_next_date @@ -265,9 +253,7 @@ class TestContract(TestContractBase): self.acct_line.recurring_invoicing_type = 'post-paid' self.acct_line.recurring_rule_type = 'monthlylastday' self.contract.recurring_create_invoice() - invoices_monthly_lastday = self.env['account.invoice'].search( - [('contract_id', '=', self.contract.id)] - ) + invoices_monthly_lastday = self.contract._get_related_invoices() self.assertTrue(invoices_monthly_lastday) self.assertEqual( self.acct_line.recurring_next_date, recurring_next_date @@ -304,13 +290,9 @@ class TestContract(TestContractBase): ) self.assertFalse(self.acct_line.recurring_next_date) self.assertFalse(self.acct_line.create_invoice_visibility) - invoices = self.env['account.invoice'].search( - [('contract_id', '=', self.contract.id)] - ) + invoices = self.contract._get_related_invoices() self.contract.recurring_create_invoice() - new_invoices = self.env['account.invoice'].search( - [('contract_id', '=', self.contract.id)] - ) + new_invoices = self.contract._get_related_invoices() self.assertEqual( invoices, new_invoices, @@ -347,13 +329,9 @@ class TestContract(TestContractBase): ) self.assertFalse(self.acct_line.recurring_next_date) self.assertFalse(self.acct_line.create_invoice_visibility) - invoices = self.env['account.invoice'].search( - [('contract_id', '=', self.contract.id)] - ) + invoices = self.contract._get_related_invoices() self.contract.recurring_create_invoice() - new_invoices = self.env['account.invoice'].search( - [('contract_id', '=', self.contract.id)] - ) + new_invoices = self.contract._get_related_invoices() self.assertEqual( invoices, new_invoices, @@ -417,13 +395,10 @@ class TestContract(TestContractBase): self.contract.write({'recurring_invoices': True}) self.acct_line.write({'recurring_next_date': False}) - def test_check_date_start_recurring_invoices(self): - with self.assertRaises(ValidationError): - self.contract.write({'recurring_invoices': True}) - self.acct_line.write({'date_start': False}) - def test_onchange_contract_template_id(self): """It should change the contract values to match the template.""" + self.contract.contract_template_id = False + self.contract._onchange_contract_template_id() self.contract.contract_template_id = self.template self.contract._onchange_contract_template_id() res = { @@ -432,10 +407,10 @@ class TestContract(TestContractBase): 0, 0, { - 'product_id': self.product.id, + 'product_id': self.product_1.id, 'name': 'Services from #START# to #END#', 'quantity': 1, - 'uom_id': self.product.uom_id.id, + 'uom_id': self.product_1.uom_id.id, 'price_unit': 100, 'discount': 50, 'recurring_rule_type': 'yearly', @@ -479,6 +454,15 @@ class TestContract(TestContractBase): self.assertEqual( self.contract.journal_id.company_id, self.contract.company_id ) + self.contract.type = 'purchase' + self.contract._onchange_contract_type() + self.assertFalse( + any( + self.contract.recurring_invoice_line_ids.mapped( + 'automatic_price' + ) + ) + ) def test_contract_onchange_product_id_domain_blank(self): """It should return a blank UoM domain when no product.""" @@ -492,7 +476,7 @@ class TestContract(TestContractBase): res = line._onchange_product_id() self.assertEqual( res['domain']['uom_id'][0], - ('category_id', '=', self.product.uom_id.category_id.id), + ('category_id', '=', self.product_1.uom_id.category_id.id), ) def test_contract_onchange_product_id_uom(self): @@ -529,27 +513,20 @@ class TestContract(TestContractBase): def test_same_date_start_and_date_end(self): """It should create one invoice with same start and end date.""" - account_invoice_model = self.env['account.invoice'] self.acct_line.write( { - 'date_start': fields.Date.today(), - 'date_end': fields.Date.today(), - 'recurring_next_date': fields.Date.today(), + 'date_start': self.today, + 'date_end': self.today, + 'recurring_next_date': self.today, } ) self.contract._compute_recurring_next_date() - init_count = account_invoice_model.search_count( - [('contract_id', '=', self.contract.id)] - ) + init_count = len(self.contract._get_related_invoices()) self.contract.recurring_create_invoice() - last_count = account_invoice_model.search_count( - [('contract_id', '=', self.contract.id)] - ) + last_count = len(self.contract._get_related_invoices()) self.assertEqual(last_count, init_count + 1) self.contract.recurring_create_invoice() - last_count = account_invoice_model.search_count( - [('contract_id', '=', self.contract.id)] - ) + last_count = len(self.contract._get_related_invoices()) self.assertEqual(last_count, init_count + 1) def test_act_show_contract(self): @@ -657,48 +634,49 @@ class TestContract(TestContractBase): def test_date_end(self): """recurring next date for a contract is the min for all lines""" - self.assertEqual(self.acct_line.date_end, to_date('2019-01-01')) self.acct_line.date_end = '2018-01-01' - self.assertEqual(self.acct_line.date_end, to_date('2018-01-01')) self.acct_line.copy() self.acct_line.write({'date_end': False, 'is_auto_renew': False}) self.assertFalse(self.contract.date_end) - def test_last_date_invoiced_prepaid(self): - self.contract.recurring_create_invoice() - self - def test_stop_contract_line(self): """It should put end to the contract line""" self.acct_line.write( { - 'date_start': fields.Date.today(), - 'recurring_next_date': fields.Date.today(), - 'date_end': fields.Date.today() + relativedelta(months=7), + 'date_start': self.today - relativedelta(months=7), + 'recurring_next_date': self.today - relativedelta(months=7), + 'date_end': self.today - relativedelta(months=5), + 'is_auto_renew': False, + } + ) + with self.assertRaises(ValidationError): + self.acct_line.stop(self.today) + self.acct_line.write( + { + 'date_start': self.today, + 'recurring_next_date': self.today, + 'date_end': self.today + relativedelta(months=7), 'is_auto_renew': True, } ) - self.acct_line.stop(fields.Date.today() + relativedelta(months=5)) + self.acct_line.stop(self.today + relativedelta(months=5)) self.assertEqual( - self.acct_line.date_end, - fields.Date.today() + relativedelta(months=5), + self.acct_line.date_end, self.today + relativedelta(months=5) ) def test_stop_upcoming_contract_line(self): """It should put end to the contract line""" self.acct_line.write( { - 'date_start': fields.Date.today() + relativedelta(months=3), - 'recurring_next_date': fields.Date.today() - + relativedelta(months=3), - 'date_end': fields.Date.today() + relativedelta(months=7), + 'date_start': self.today + relativedelta(months=3), + 'recurring_next_date': self.today + relativedelta(months=3), + 'date_end': self.today + relativedelta(months=7), 'is_auto_renew': True, } ) - self.acct_line.stop(fields.Date.today()) + self.acct_line.stop(self.today) self.assertEqual( - self.acct_line.date_end, - fields.Date.today() + relativedelta(months=7), + self.acct_line.date_end, self.today + relativedelta(months=7) ) self.assertTrue(self.acct_line.is_canceled) @@ -706,56 +684,74 @@ class TestContract(TestContractBase): """Past contract line are ignored on stop""" self.acct_line.write( { - 'date_end': fields.Date.today() + relativedelta(months=5), + 'date_end': self.today + relativedelta(months=5), 'is_auto_renew': True, } ) - self.acct_line.stop(fields.Date.today() + relativedelta(months=7)) + self.acct_line.stop(self.today + relativedelta(months=7)) self.assertEqual( - self.acct_line.date_end, - fields.Date.today() + relativedelta(months=5), + self.acct_line.date_end, self.today + relativedelta(months=5) ) def test_stop_contract_line_without_date_end(self): """Past contract line are ignored on stop""" self.acct_line.write({'date_end': False, 'is_auto_renew': False}) - self.acct_line.stop(fields.Date.today() + relativedelta(months=7)) + self.acct_line.stop(self.today + relativedelta(months=7)) self.assertEqual( - self.acct_line.date_end, - fields.Date.today() + relativedelta(months=7), + self.acct_line.date_end, self.today + relativedelta(months=7) ) - def test_stop_plan_successor_wizard(self): + def test_stop_wizard(self): self.acct_line.write( { - 'date_start': fields.Date.today(), - 'recurring_next_date': fields.Date.today(), - 'date_end': fields.Date.today() + relativedelta(months=5), + 'date_start': self.today, + 'recurring_next_date': self.today, + 'date_end': self.today + relativedelta(months=5), 'is_auto_renew': True, } ) wizard = self.env['account.analytic.invoice.line.wizard'].create( { - 'date_end': fields.Date.today() + relativedelta(months=7), + 'date_end': self.today + relativedelta(months=3), 'contract_line_id': self.acct_line.id, } ) wizard.stop() self.assertEqual( - self.acct_line.date_end, - fields.Date.today() + relativedelta(months=7), + self.acct_line.date_end, self.today + relativedelta(months=3) ) self.assertFalse(self.acct_line.is_auto_renew) + def test_stop_plan_successor_contract_line_0(self): + successor_contract_line = self.acct_line.copy( + { + 'date_start': self.today + relativedelta(months=5), + 'recurring_next_date': self.today + relativedelta(months=5), + } + ) + self.acct_line.write( + { + 'successor_contract_line_id': successor_contract_line.id, + 'is_auto_renew': False, + 'date_end': self.today, + } + ) + suspension_start = self.today + relativedelta(months=5) + suspension_end = self.today + relativedelta(months=6) + with self.assertRaises(ValidationError): + self.acct_line.stop_plan_successor( + suspension_start, suspension_end, True + ) + def test_stop_plan_successor_contract_line_1(self): """ * contract line end's before the suspension period: -> apply stop """ - suspension_start = fields.Date.today() + relativedelta(months=5) - suspension_end = fields.Date.today() + relativedelta(months=6) - start_date = fields.Date.today() - end_date = fields.Date.today() + relativedelta(months=4) + suspension_start = self.today + relativedelta(months=5) + suspension_end = self.today + relativedelta(months=6) + start_date = self.today + end_date = self.today + relativedelta(months=4) self.acct_line.write( { 'date_start': start_date, @@ -781,10 +777,10 @@ class TestContract(TestContractBase): - date_end: suspension.date_end + (contract_line.date_end - suspension.date_start) """ - suspension_start = fields.Date.today() + relativedelta(months=3) - suspension_end = fields.Date.today() + relativedelta(months=5) - start_date = fields.Date.today() - end_date = fields.Date.today() + relativedelta(months=4) + suspension_start = self.today + relativedelta(months=3) + 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, @@ -811,6 +807,7 @@ class TestContract(TestContractBase): new_line.date_start, suspension_end + relativedelta(days=1) ) self.assertEqual(new_line.date_end, new_date_end) + self.assertTrue(self.acct_line.manual_renew_needed) def test_stop_plan_successor_contract_line_3(self): """ @@ -821,10 +818,10 @@ class TestContract(TestContractBase): - date_end: suspension.date_end + (suspension.date_end - suspension.date_start) """ - suspension_start = fields.Date.today() + relativedelta(months=3) - suspension_end = fields.Date.today() + relativedelta(months=5) - start_date = fields.Date.today() - end_date = fields.Date.today() + relativedelta(months=6) + suspension_start = self.today + relativedelta(months=3) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today + end_date = self.today + relativedelta(months=6) self.acct_line.write( { 'date_start': start_date, @@ -851,6 +848,7 @@ class TestContract(TestContractBase): new_line.date_start, suspension_end + relativedelta(days=1) ) self.assertEqual(new_line.date_end, new_date_end) + self.assertTrue(self.acct_line.manual_renew_needed) def test_stop_plan_successor_contract_line_3_without_end_date(self): """ @@ -861,9 +859,9 @@ class TestContract(TestContractBase): - date_end: suspension.date_end + (suspension.date_end - suspension.date_start) """ - suspension_start = fields.Date.today() + relativedelta(months=3) - suspension_end = fields.Date.today() + relativedelta(months=5) - start_date = fields.Date.today() + suspension_start = self.today + relativedelta(months=3) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today end_date = False self.acct_line.write( { @@ -887,6 +885,7 @@ class TestContract(TestContractBase): new_line.date_start, suspension_end + relativedelta(days=1) ) self.assertFalse(new_line.date_end) + self.assertTrue(self.acct_line.manual_renew_needed) def test_stop_plan_successor_contract_line_4(self): """ @@ -894,10 +893,10 @@ class TestContract(TestContractBase): -> apply delay - delay: suspension.date_end - contract_line.end_date """ - suspension_start = fields.Date.today() + relativedelta(months=2) - suspension_end = fields.Date.today() + relativedelta(months=5) - start_date = fields.Date.today() + relativedelta(months=3) - end_date = fields.Date.today() + relativedelta(months=4) + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today + relativedelta(months=3) + end_date = self.today + relativedelta(months=4) self.acct_line.write( { 'date_start': start_date, @@ -927,10 +926,10 @@ class TestContract(TestContractBase): -> apply delay - delay: suspension.date_end - contract_line.date_start """ - suspension_start = fields.Date.today() + relativedelta(months=2) - suspension_end = fields.Date.today() + relativedelta(months=5) - start_date = fields.Date.today() + relativedelta(months=3) - end_date = fields.Date.today() + relativedelta(months=6) + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today + relativedelta(months=3) + end_date = self.today + relativedelta(months=6) self.acct_line.write( { 'date_start': start_date, @@ -960,9 +959,9 @@ class TestContract(TestContractBase): -> apply delay - delay: suspension.date_end - contract_line.date_start """ - suspension_start = fields.Date.today() + relativedelta(months=2) - suspension_end = fields.Date.today() + relativedelta(months=5) - start_date = fields.Date.today() + relativedelta(months=3) + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=5) + start_date = self.today + relativedelta(months=3) end_date = False self.acct_line.write( { @@ -991,10 +990,10 @@ class TestContract(TestContractBase): -> apply delay - delay: suspension.date_end - suspension.start_date """ - suspension_start = fields.Date.today() + relativedelta(months=2) - suspension_end = fields.Date.today() + relativedelta(months=3) - start_date = fields.Date.today() + relativedelta(months=4) - end_date = fields.Date.today() + relativedelta(months=6) + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=3) + start_date = self.today + relativedelta(months=4) + end_date = self.today + relativedelta(months=6) self.acct_line.write( { 'date_start': start_date, @@ -1026,9 +1025,9 @@ class TestContract(TestContractBase): -> apply delay - delay: suspension.date_end - suspension.start_date """ - suspension_start = fields.Date.today() + relativedelta(months=2) - suspension_end = fields.Date.today() + relativedelta(months=3) - start_date = fields.Date.today() + relativedelta(months=4) + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=3) + start_date = self.today + relativedelta(months=4) end_date = False self.acct_line.write( { @@ -1054,10 +1053,10 @@ class TestContract(TestContractBase): self.assertFalse(new_line) def test_stop_plan_successor_wizard(self): - suspension_start = fields.Date.today() + relativedelta(months=2) - suspension_end = fields.Date.today() + relativedelta(months=3) - start_date = fields.Date.today() + relativedelta(months=4) - end_date = fields.Date.today() + relativedelta(months=6) + suspension_start = self.today + relativedelta(months=2) + suspension_end = self.today + relativedelta(months=3) + start_date = self.today + relativedelta(months=4) + end_date = self.today + relativedelta(months=6) self.acct_line.write( { 'date_start': start_date, @@ -1092,15 +1091,15 @@ class TestContract(TestContractBase): def test_plan_successor_contract_line(self): self.acct_line.write( { - 'date_start': fields.Date.today(), - 'recurring_next_date': fields.Date.today(), - 'date_end': fields.Date.today() + relativedelta(months=3), + 'date_start': self.today, + 'recurring_next_date': self.today, + 'date_end': self.today + relativedelta(months=3), 'is_auto_renew': False, } ) self.acct_line.plan_successor( - fields.Date.today() + relativedelta(months=5), - fields.Date.today() + relativedelta(months=7), + self.today + relativedelta(months=5), + self.today + relativedelta(months=7), True, ) new_line = self.env['account.analytic.invoice.line'].search( @@ -1110,49 +1109,47 @@ class TestContract(TestContractBase): self.assertTrue(new_line.is_auto_renew) self.assertTrue(new_line, "should create a new contract line") self.assertEqual( - new_line.date_start, fields.Date.today() + relativedelta(months=5) + new_line.date_start, self.today + relativedelta(months=5) ) self.assertEqual( - new_line.date_end, fields.Date.today() + relativedelta(months=7) + new_line.date_end, self.today + relativedelta(months=7) ) def test_overlap(self): self.acct_line.write( { - 'date_start': fields.Date.today(), - 'recurring_next_date': fields.Date.today(), - 'date_end': fields.Date.today() + relativedelta(months=3), + 'date_start': self.today, + 'recurring_next_date': self.today, + 'date_end': self.today + relativedelta(months=3), 'is_auto_renew': False, } ) self.acct_line.plan_successor( - fields.Date.today() + relativedelta(months=5), - fields.Date.today() + relativedelta(months=7), + self.today + relativedelta(months=5), + self.today + relativedelta(months=7), True, ) new_line = self.env['account.analytic.invoice.line'].search( [('predecessor_contract_line_id', '=', self.acct_line.id)] ) with self.assertRaises(ValidationError): - new_line.date_start = fields.Date.today() + relativedelta(months=2) + new_line.date_start = self.today + relativedelta(months=2) with self.assertRaises(ValidationError): - self.acct_line.date_end = fields.Date.today() + relativedelta( - months=6 - ) + self.acct_line.date_end = self.today + relativedelta(months=6) def test_plan_successor_wizard(self): self.acct_line.write( { - 'date_start': fields.Date.today(), - 'recurring_next_date': fields.Date.today(), - 'date_end': fields.Date.today() + relativedelta(months=2), + 'date_start': self.today, + 'recurring_next_date': self.today, + 'date_end': self.today + relativedelta(months=2), 'is_auto_renew': False, } ) wizard = self.env['account.analytic.invoice.line.wizard'].create( { - 'date_start': fields.Date.today() + relativedelta(months=3), - 'date_end': fields.Date.today() + relativedelta(months=5), + 'date_start': self.today + relativedelta(months=3), + 'date_end': self.today + relativedelta(months=5), 'is_auto_renew': True, 'contract_line_id': self.acct_line.id, } @@ -1165,23 +1162,35 @@ class TestContract(TestContractBase): self.assertTrue(new_line.is_auto_renew) self.assertTrue(new_line, "should create a new contract line") self.assertEqual( - new_line.date_start, fields.Date.today() + relativedelta(months=3) + new_line.date_start, self.today + relativedelta(months=3) ) self.assertEqual( - new_line.date_end, fields.Date.today() + relativedelta(months=5) + new_line.date_end, self.today + relativedelta(months=5) ) def test_cancel(self): self.acct_line.cancel() self.assertTrue(self.acct_line.is_canceled) - self.acct_line.uncancel(fields.Date.today()) + self.acct_line.uncancel(self.today) + self.assertFalse(self.acct_line.is_canceled) + + def test_uncancel_wizard(self): + self.acct_line.cancel() + self.assertTrue(self.acct_line.is_canceled) + wizard = self.env['account.analytic.invoice.line.wizard'].create( + { + 'recurring_next_date': self.today, + 'contract_line_id': self.acct_line.id, + } + ) + wizard.uncancel() self.assertFalse(self.acct_line.is_canceled) def test_cancel_uncancel_with_predecessor(self): - suspension_start = fields.Date.today() + relativedelta(months=3) - suspension_end = fields.Date.today() + relativedelta(months=5) - start_date = fields.Date.today() - end_date = fields.Date.today() + relativedelta(months=4) + suspension_start = self.today + relativedelta(months=3) + 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, @@ -1212,10 +1221,10 @@ class TestContract(TestContractBase): ) def test_cancel_uncancel_with_predecessor_has_successor(self): - suspension_start = fields.Date.today() + relativedelta(months=6) - suspension_end = fields.Date.today() + relativedelta(months=7) - start_date = fields.Date.today() - end_date = fields.Date.today() + relativedelta(months=8) + suspension_start = self.today + relativedelta(months=6) + suspension_end = self.today + relativedelta(months=7) + start_date = self.today + end_date = self.today + relativedelta(months=8) self.acct_line.write( { 'date_start': start_date, @@ -1230,8 +1239,8 @@ class TestContract(TestContractBase): [('predecessor_contract_line_id', '=', self.acct_line.id)] ) new_line.cancel() - suspension_start = fields.Date.today() + relativedelta(months=4) - suspension_end = fields.Date.today() + relativedelta(months=5) + suspension_start = self.today + relativedelta(months=4) + suspension_end = self.today + relativedelta(months=5) self.acct_line.stop_plan_successor( suspension_start, suspension_end, True ) @@ -1256,18 +1265,18 @@ class TestContract(TestContractBase): ) def test_search_contract_line_to_renew(self): - self.acct_line.write({'date_end': fields.Date.today()}) + self.acct_line.write({'date_end': self.today, 'is_auto_renew': True}) line_1 = self.acct_line.copy( - {'date_end': fields.Date.today() + relativedelta(months=1)} + {'date_end': self.today + relativedelta(months=1)} ) line_2 = self.acct_line.copy( - {'date_end': fields.Date.today() - relativedelta(months=1)} + {'date_end': self.today - relativedelta(months=1)} ) line_3 = self.acct_line.copy( - {'date_end': fields.Date.today() - relativedelta(months=2)} + {'date_end': self.today - relativedelta(months=2)} ) - self.acct_line.copy( - {'date_end': fields.Date.today() + relativedelta(months=2)} + line_4 = self.acct_line.copy( + {'date_end': self.today + relativedelta(months=2)} ) to_renew = self.acct_line.search( self.acct_line._contract_line_to_renew_domain() @@ -1275,15 +1284,37 @@ class TestContract(TestContractBase): self.assertEqual( set(to_renew), set((self.acct_line, line_1, line_2, line_3)) ) + self.acct_line.cron_renew_contract_line() + self.assertTrue(self.acct_line.successor_contract_line_id) + self.assertTrue(line_1.successor_contract_line_id) + self.assertTrue(line_2.successor_contract_line_id) + self.assertTrue(line_3.successor_contract_line_id) + self.assertFalse(line_4.successor_contract_line_id) def test_renew(self): + date_start = self.today - relativedelta(months=9) + date_end = ( + date_start + relativedelta(months=12) - relativedelta(days=1) + ) + self.acct_line.write( + { + 'is_auto_renew': True, + 'date_start': date_start, + 'recurring_next_date': date_start, + 'date_end': self.today, + } + ) self.acct_line._onchange_is_auto_renew() - self.assertEqual(self.acct_line.date_end, to_date('2018-12-31')) + self.assertEqual(self.acct_line.date_end, date_end) new_line = self.acct_line.renew() self.assertFalse(self.acct_line.is_auto_renew) self.assertTrue(new_line.is_auto_renew) - self.assertEqual(new_line.date_start, to_date('2019-01-01')) - self.assertEqual(new_line.date_end, to_date('2019-12-31')) + self.assertEqual( + new_line.date_start, date_start + relativedelta(months=12) + ) + self.assertEqual( + new_line.date_end, date_end + relativedelta(months=12) + ) def test_cron_recurring_create_invoice(self): self.acct_line.date_start = '2018-01-01' @@ -1297,8 +1328,10 @@ class TestContract(TestContractBase): invoice_lines = self.env['account.invoice.line'].search( [('account_analytic_id', 'in', contracts.ids)] ) - self.assertEqual(len(contracts.mapped('recurring_invoice_line_ids')), - len(invoice_lines)) + self.assertEqual( + len(contracts.mapped('recurring_invoice_line_ids')), + len(invoice_lines), + ) def test_get_invoiced_period_monthlylastday(self): self.acct_line.date_start = '2018-01-05' @@ -1317,6 +1350,7 @@ class TestContract(TestContractBase): first, last = self.acct_line._get_invoiced_period() self.assertEqual(first, to_date('2018-03-01')) self.assertEqual(last, to_date('2018-03-15')) + self.acct_line.manual_renew_needed = True def test_get_invoiced_period_monthly_pre_paid_2(self): self.acct_line.date_start = '2018-01-05' @@ -1423,3 +1457,305 @@ class TestContract(TestContractBase): def test_unlink(self): with self.assertRaises(ValidationError): self.acct_line.unlink() + + def test_contract_line_state(self): + lines = self.env['account.analytic.invoice.line'] + # upcoming + lines |= self.acct_line.copy( + { + 'date_start': self.today + relativedelta(months=3), + 'recurring_next_date': self.today + relativedelta(months=3), + 'date_end': self.today + relativedelta(months=5), + } + ) + # in-progress + lines |= self.acct_line.copy( + { + 'date_start': self.today, + 'recurring_next_date': self.today, + 'date_end': self.today + relativedelta(months=5), + } + ) + # in-progress + lines |= self.acct_line.copy( + { + 'date_start': self.today, + 'recurring_next_date': self.today, + 'date_end': self.today + relativedelta(months=5), + 'manual_renew_needed': True, + } + ) + # to-renew + lines |= self.acct_line.copy( + { + 'date_start': self.today - relativedelta(months=5), + 'recurring_next_date': self.today - relativedelta(months=5), + 'date_end': self.today - relativedelta(months=2), + 'manual_renew_needed': True, + } + ) + # upcoming-close + lines |= self.acct_line.copy( + { + 'date_start': self.today - relativedelta(months=5), + 'recurring_next_date': self.today - relativedelta(months=5), + 'date_end': self.today + relativedelta(days=20), + 'is_auto_renew': False, + } + ) + # closed + lines |= self.acct_line.copy( + { + 'date_start': self.today - relativedelta(months=5), + 'recurring_next_date': self.today - relativedelta(months=5), + 'date_end': self.today - relativedelta(months=2), + 'is_auto_renew': False, + } + ) + # canceled + lines |= self.acct_line.copy( + { + 'date_start': self.today - relativedelta(months=5), + 'recurring_next_date': self.today - relativedelta(months=5), + 'date_end': self.today - relativedelta(months=2), + 'is_canceled': True, + } + ) + states = [ + 'upcoming', + 'in-progress', + 'to-renew', + 'upcoming-close', + 'closed', + 'canceled', + ] + self.assertEqual(set(lines.mapped('state')), set(states)) + for state in states: + lines = self.env['account.analytic.invoice.line'].search( + [('state', '=', state)] + ) + self.assertEqual(len(set(lines.mapped('state'))), 1, state) + self.assertEqual(lines.mapped('state')[0], state, state) + + for state in states: + lines = self.env['account.analytic.invoice.line'].search( + [('state', '!=', state)] + ) + self.assertFalse(state in lines.mapped('state')) + + lines = self.env['account.analytic.invoice.line'].search( + [('state', 'in', states)] + ) + self.assertEqual(set(lines.mapped('state')), set(states)) + lines = self.env['account.analytic.invoice.line'].search( + [('state', 'in', [])] + ) + self.assertFalse(lines.mapped('state')) + with self.assertRaises(TypeError): + self.env['account.analytic.invoice.line'].search( + [('state', 'in', 'upcoming')] + ) + lines = self.env['account.analytic.invoice.line'].search( + [('state', 'not in', [])] + ) + self.assertEqual(set(lines.mapped('state')), set(states)) + lines = self.env['account.analytic.invoice.line'].search( + [('state', 'not in', states)] + ) + self.assertFalse(lines.mapped('state')) + lines = self.env['account.analytic.invoice.line'].search( + [('state', 'not in', ['upcoming', 'in-progress'])] + ) + self.assertEqual( + set(lines.mapped('state')), + set(['to-renew', 'upcoming-close', 'closed', 'canceled']), + ) + + def test_check_auto_renew_contract_line_with_successor(self): + """ + A contract line with a successor can't be set to auto-renew + """ + successor_contract_line = self.acct_line.copy() + with self.assertRaises(ValidationError): + self.acct_line.write( + { + 'is_auto_renew': True, + 'successor_contract_line_id': successor_contract_line.id, + } + ) + + def test_check_no_date_end_contract_line_with_successor(self): + """ + A contract line with a successor must have a end date + """ + successor_contract_line = self.acct_line.copy() + with self.assertRaises(ValidationError): + self.acct_line.write( + { + 'date_end': False, + 'successor_contract_line_id': successor_contract_line.id, + } + ) + + def test_check_last_date_invoiced_1(self): + """ + start end can't be before the date of last invoice + """ + with self.assertRaises(ValidationError): + self.acct_line.write( + { + 'last_date_invoiced': self.acct_line.date_start + - relativedelta(days=1) + } + ) + + def test_check_last_date_invoiced_2(self): + """ + start date can't be after the date of last invoice + """ + self.acct_line.write({'date_end': self.today}) + with self.assertRaises(ValidationError): + self.acct_line.write( + { + 'last_date_invoiced': self.acct_line.date_end + + relativedelta(days=1) + } + ) + + def test_init_last_date_invoiced(self): + self.acct_line.write( + {'date_start': '2019-01-01', 'recurring_next_date': '2019-03-01'} + ) + line_monthlylastday = self.acct_line.copy( + { + 'recurring_rule_type': 'monthlylastday', + 'recurring_next_date': '2019-03-31', + } + ) + line_prepaid = self.acct_line.copy( + { + 'recurring_invoicing_type': 'pre-paid', + 'recurring_rule_type': 'monthly', + } + ) + line_postpaid = self.acct_line.copy( + { + 'recurring_invoicing_type': 'post-paid', + 'recurring_rule_type': 'monthly', + } + ) + lines = line_monthlylastday | line_prepaid | line_postpaid + lines.write({'last_date_invoiced': False}) + self.assertFalse(any(lines.mapped('last_date_invoiced'))) + lines._init_last_date_invoiced() + self.assertEqual( + line_monthlylastday.last_date_invoiced, to_date("2019-02-28") + ) + self.assertEqual( + line_prepaid.last_date_invoiced, to_date("2019-02-28") + ) + self.assertEqual( + line_postpaid.last_date_invoiced, to_date("2019-01-31") + ) + + def test_delay_invoiced_contract_line(self): + self.acct_line.write( + { + 'last_date_invoiced': self.acct_line.date_start + + relativedelta(days=1) + } + ) + with self.assertRaises(ValidationError): + self.acct_line._delay(relativedelta(months=1)) + + def test_cancel_invoiced_contract_line(self): + self.acct_line.write( + { + 'last_date_invoiced': self.acct_line.date_start + + relativedelta(days=1) + } + ) + with self.assertRaises(ValidationError): + self.acct_line.cancel() + + def test_action_uncancel(self): + action = self.acct_line.action_uncancel() + self.assertEqual( + action['context']['default_contract_line_id'], self.acct_line.id + ) + + def test_action_plan_successor(self): + action = self.acct_line.action_plan_successor() + self.assertEqual( + action['context']['default_contract_line_id'], self.acct_line.id + ) + + def test_action_stop(self): + action = self.acct_line.action_stop() + self.assertEqual( + action['context']['default_contract_line_id'], self.acct_line.id + ) + + def test_action_stop_plan_successor(self): + action = self.acct_line.action_stop_plan_successor() + self.assertEqual( + action['context']['default_contract_line_id'], self.acct_line.id + ) + + def test_purchase_fields_view_get(self): + purchase_tree_view = self.env.ref( + 'contract.account_analytic_invoice_line_purchase_view_tree' + ) + purchase_form_view = self.env.ref( + 'contract.account_analytic_invoice_line_purchase_view_form' + ) + view = self.acct_line.with_context( + default_contract_type='purchase' + ).fields_view_get(view_type='tree') + self.assertEqual(view['view_id'], purchase_tree_view.id) + view = self.acct_line.with_context( + default_contract_type='purchase' + ).fields_view_get(view_type='form') + self.assertEqual(view['view_id'], purchase_form_view.id) + + def test_sale_fields_view_get(self): + sale_form_view = self.env.ref( + 'contract.account_analytic_invoice_line_sale_view_form' + ) + view = self.acct_line.with_context( + default_contract_type='sale' + ).fields_view_get(view_type='form') + self.assertEqual(view['view_id'], sale_form_view.id) + + def test_contract_count_invoice(self): + self.contract.recurring_create_invoice() + self.contract.recurring_create_invoice() + self.contract.recurring_create_invoice() + self.contract._compute_invoice_count() + self.assertEqual(self.contract.invoice_count, 3) + + def test_contract_count_invoice(self): + invoices = self.env['account.invoice'] + invoices |= self.contract.recurring_create_invoice() + invoices |= self.contract.recurring_create_invoice() + invoices |= self.contract.recurring_create_invoice() + action = self.contract.action_show_invoices() + self.assertEqual(set(action['domain'][0][2]), set(invoices.ids)) + + def test_compute_create_invoice_visibility(self): + self.assertTrue(self.contract.create_invoice_visibility) + self.acct_line.write( + { + 'date_start': '2018-01-01', + 'date_end': '2018-12-31', + 'last_date_invoiced': '2018-12-31', + 'recurring_next_date': False, + } + ) + self.assertFalse(self.acct_line.create_invoice_visibility) + self.assertFalse(self.contract.create_invoice_visibility) + + def test_invoice_contract_without_lines(self): + self.contract.recurring_invoice_line_ids.cancel() + self.contract.recurring_invoice_line_ids.unlink() + self.assertFalse(self.contract.recurring_create_invoice()) diff --git a/contract/views/account_invoice_view.xml b/contract/views/account_invoice_view.xml index e618fbe20..e69de29bb 100644 --- a/contract/views/account_invoice_view.xml +++ b/contract/views/account_invoice_view.xml @@ -1,44 +0,0 @@ - - - - - account.invoice.select.contract - account.invoice - - - - - - - - - - - Invoices - account.invoice - - { - 'search_default_contract_id': [active_id], - 'default_contract_id': active_id} - - [('type','in', ['out_invoice', 'out_refund'])] - - - - Vendor Bills - account.invoice - - { - 'search_default_contract_id': [active_id], - 'default_contract_id': active_id} - - [('type','in', ['in_invoice', 'in_refund'])] - - - diff --git a/contract/views/contract.xml b/contract/views/contract.xml index 957b338ea..1f337a9d9 100644 --- a/contract/views/contract.xml +++ b/contract/views/contract.xml @@ -25,16 +25,17 @@ - -