diff --git a/contract/__manifest__.py b/contract/__manifest__.py index 10d14ddc..dcf9aac9 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 5262b394..0d53bb05 100644 --- a/contract/migrations/12.0.2.0.0/pre-migration.py +++ b/contract/migrations/12.0.2.0.0/pre-migration.py @@ -20,6 +20,5 @@ def migrate(cr, version): ) cr.execute( "UPDATE account_analytic_account set recurring_next_date=null " - "where id in (%)" - % ','.join(finished_contract.ids) + "where id in (%)" % ','.join(finished_contract.ids) ) diff --git a/contract/models/__init__.py b/contract/models/__init__.py index 0bbc06e9..28dc9922 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/account_invoice.py b/contract/models/account_invoice.py index b651ea2a..7d9f4ceb 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 00000000..30e1829c --- /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 55e5acf8..63d02038 100644 --- a/contract/models/contract.py +++ b/contract/models/contract.py @@ -50,6 +50,46 @@ class AccountAnalyticAccount(models.Model): payment_term_id = fields.Many2one( 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): @@ -185,7 +225,6 @@ class AccountAnalyticAccount(models.Model): '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, } diff --git a/contract/models/contract_line.py b/contract/models/contract_line.py index b2d51428..2b000d26 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 @@ -40,6 +41,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 +51,11 @@ class AccountAnalyticInvoiceLine(models.Model): string="Predecessor Contract Line", required=False, readonly=True, + index=True, copy=False, help="Contract Line origin of this one.", ) + is_suspended = fields.Boolean(string="Suspended", default=False) is_plan_successor_allowed = fields.Boolean( string="Plan successor allowed?", compute='_compute_allowed' ) @@ -72,11 +76,14 @@ class AccountAnalyticInvoiceLine(models.Model): selection=[ ('upcoming', 'Upcoming'), ('in-progress', 'In-progress'), + ('suspension-planed', 'Suspension Planed'), + ('suspended', 'Suspended'), ('upcoming-close', 'Upcoming Close'), ('closed', 'Closed'), ('canceled', 'Canceled'), ], compute="_compute_state", + search='_search_state', ) active = fields.Boolean( string="Active", @@ -90,20 +97,135 @@ class AccountAnalyticInvoiceLine(models.Model): 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 - ): - rec.state = 'in-progress' - elif today <= rec.date_end and not 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.is_suspended: + rec.state = 'suspension-planed' + continue + if 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: + if rec.is_suspended and not rec.successor_contract_line_id: + rec.state = 'suspended' 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_auto_renew', '=', True), + ('is_suspended', '=', False), + ('is_canceled', '=', False), + "|", + ('date_end', '>=', today), + ('date_end', '=', False), + ] + if state == 'suspension-planed': + return [ + "&", + "&", + "&", + ('date_start', '<=', today), + ('is_suspended', '=', True), + ('is_canceled', '=', False), + '|', + ('date_end', '>=', today), + ('date_end', '=', False), + ] + if state == 'suspended': + return [ + "&", + "&", + "&", + ('date_end', '<', today), + ('successor_contract_line_id', '=', False), + ('is_suspended', '=', True), + ('is_canceled', '=', False), + ] + if state == 'upcoming-close': + return [ + "&", + "&", + "&", + "&", + ('date_start', '<=', today), + ('is_auto_renew', '=', False), + ('is_suspended', '=', False), + ('is_canceled', '=', False), + '|', + ('date_end', '>=', today), + ('date_end', '=', False), + ] + if state == 'closed': + return [ + "&", + "&", + ('is_canceled', '=', False), + ('date_end', '<', today), + "|", + "&", + ('is_suspended', '=', True), + ('successor_contract_line_id', '!=', False), + ('is_suspended', '=', False), + ] + + if state == 'canceled': + return [('is_canceled', '=', True)] + + @api.model + def _search_state(self, operator, value): + if operator == '!=' and not value: + return [] + if operator == '=' and not value: + return [('id', '=', False)] + if operator == '=': + return self._get_state_domain(value) + if operator == '!=': + states = [ + 'upcoming', + 'in-progress', + 'suspension-planed', + 'suspended', + 'upcoming-close', + 'closed', + 'canceled', + ] + domain = [] + for state in states: + if state != value: + if domain: + domain.insert(0, '|') + domain.extend(self._get_state_domain(state)) + return domain + @api.depends( 'date_start', 'date_end', @@ -316,6 +438,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 @@ -460,7 +583,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, is_suspended=False, post_message=True): """ Put date_end on contract line We don't consider contract lines that end's before the new end date @@ -487,9 +610,17 @@ 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, + "is_suspended": is_suspended, + } + ) else: - rec.write({'is_auto_renew': False}) + rec.write( + {'is_auto_renew': False, "is_suspended": is_suspended} + ) return True @api.multi @@ -624,7 +755,9 @@ class AccountAnalyticInvoiceLine(models.Model): + relativedelta(days=1) ) rec.stop( - date_start - relativedelta(days=1), post_message=False + date_start - relativedelta(days=1), + is_suspended=True, + post_message=False, ) contract_line |= rec.plan_successor( new_date_start, @@ -644,7 +777,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), + is_suspended=True, + post_message=False, ) contract_line |= rec.plan_successor( new_date_start, diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py index 5194f99b..9e90417d 100644 --- a/contract/tests/test_contract.py +++ b/contract/tests/test_contract.py @@ -143,9 +143,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 @@ -182,9 +180,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 @@ -198,9 +194,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 @@ -214,9 +208,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 @@ -230,9 +222,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 @@ -247,9 +237,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 @@ -263,9 +251,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 @@ -302,13 +288,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, @@ -345,13 +327,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, @@ -522,7 +500,6 @@ 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(), @@ -531,18 +508,12 @@ class TestContract(TestContractBase): } ) 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): @@ -798,6 +769,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.is_suspended) def test_stop_plan_successor_contract_line_3(self): """ @@ -838,6 +810,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.is_suspended) def test_stop_plan_successor_contract_line_3_without_end_date(self): """ @@ -874,6 +847,7 @@ class TestContract(TestContractBase): new_line.date_start, suspension_end + relativedelta(days=1) ) self.assertFalse(new_line.date_end) + self.assertTrue(self.acct_line.is_suspended) def test_stop_plan_successor_contract_line_4(self): """ diff --git a/contract/views/account_invoice_view.xml b/contract/views/account_invoice_view.xml index e618fbe2..e69de29b 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 55a5ff40..1f337a9d 100644 --- a/contract/views/contract.xml +++ b/contract/views/contract.xml @@ -25,16 +25,17 @@ - -