# Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import Command, tests from .common import TestInterCompanyRulesCommon @tests.tagged('post_install', '-at_install') class TestInterCompanyInvoice(TestInterCompanyRulesCommon): @classmethod def setUpClass(cls): super(TestInterCompanyInvoice, cls).setUpClass() # Enable auto generate invoice in company. (cls.company_a + cls.company_b).write({ 'intercompany_generate_bills_refund': True, }) # Configure Chart of Account for company_b. cls.env.user.company_id = cls.company_b cls.env['account.chart.template'].try_loading('generic_coa', cls.company_b, install_demo=False) # Configure Chart of Account for company_a. cls.env.user.company_id = cls.company_a cls.env['account.chart.template'].try_loading('generic_coa', cls.company_a, install_demo=False) def _configure_analytic(self, product=None, company=None, partner=None): """ Configure Analytic Distribution Model for company_a based on Product A return: analytic account """ display_name = "Inter Company" if company: self.env.user.company_id = company display_name = company.display_name analytic_plan = self.env['account.analytic.plan'].create({'name': f'Analytic Plan {display_name}'}) analytic_account = self.env['account.analytic.account'].create({ 'name': f'Account {display_name}', 'company_id': company and company.id, 'plan_id': analytic_plan.id, }) self.env['account.analytic.distribution.model'].create({ 'analytic_distribution': {analytic_account.id: 100}, 'product_id': product and product.id, 'company_id': company and company.id, 'partner_id': partner and partner.id, }) return analytic_account def _create_post_invoice(self, product_id, analytic_distribution=None): """Create a Company A invoice with Company B as the customer and post it""" invoice_line_vals = { 'product_id': product_id, 'price_unit': 100.0, 'quantity': 1.0, } if analytic_distribution: invoice_line_vals['analytic_distribution'] = analytic_distribution customer_invoice = self.env['account.move'].with_user(self.res_users_company_a).create({ 'move_type': 'out_invoice', 'partner_id': self.company_b.partner_id.id, 'invoice_line_ids': [(0, 0, invoice_line_vals)] }) customer_invoice.with_user(self.res_users_company_a).action_post() def test_00_inter_company_invoice_flow(self): """ Test inter company invoice flow """ self.env.ref('base.EUR').active = True # Create customer invoice for company A. (No need to call onchange as all the needed values are specified) self.res_users_company_a.company_ids = [(4, self.company_b.id)] customer_invoice = self.env['account.move'].with_user(self.res_users_company_a).create({ 'move_type': 'out_invoice', 'partner_id': self.company_b.partner_id.id, 'currency_id': self.env.ref('base.EUR').id, 'invoice_line_ids': [(0, 0, { 'product_id': self.product_consultant.id, 'price_unit': 450.0, 'quantity': 1.0, 'name': 'test' })] }) # Check account invoice state should be draft. self.assertEqual(customer_invoice.state, 'draft', 'Initially customer invoice should be in the "Draft" state') # Validate invoice customer_invoice.with_user(self.res_users_company_a).action_post() # Check Invoice status should be open after validate. self.assertEqual(customer_invoice.state, 'posted', 'Invoice should be in Open state.') # I check that the vendor bill is created with proper data. supplier_invoice = self.env['account.move'].with_user(self.res_users_company_b).search([('move_type', '=', 'in_invoice')], limit=1) self.assertTrue(supplier_invoice.invoice_line_ids[0].quantity == 1, "Quantity in invoice line is incorrect.") self.assertTrue(supplier_invoice.invoice_line_ids[0].product_id.id == self.product_consultant.id, "Product in line is incorrect.") self.assertTrue(supplier_invoice.invoice_line_ids[0].price_unit == 450, "Unit Price in invoice line is incorrect.") self.assertTrue(supplier_invoice.invoice_line_ids[0].account_id.company_ids == self.company_b, "Applied account in created invoice line is not relevant to company.") self.assertTrue(supplier_invoice.state == "draft", "invoice should be in draft state.") self.assertEqual(supplier_invoice.amount_total, 517.5, "Total amount is incorrect.") self.assertTrue(supplier_invoice.company_id.id == self.company_b.id, "Applied company in created invoice is incorrect.") def test_default_analytic_distribution_company_b(self): """ [Analytic Distribution Model is set for Company B + Inter Company Analytic Account is set] - With Company A, create an Invoice for Company B with product A set with an analytic distribution model available for Company B -> The Analytic Distribution set on the Supplier Invoice Line should be the same as defined in the analytic distribution model set by default for Company B and the one manually set when the analytic account is also available for Company B """ analytic_account_company_b = self._configure_analytic(company=self.company_b, product=self.product_a) inter_company_analytic_account = self._configure_analytic(product=self.product_b) self._create_post_invoice(product_id=self.product_a.id, analytic_distribution={inter_company_analytic_account.id: 100}) supplier_invoice = self.env['account.move'].with_user(self.res_users_company_b).search([('move_type', '=', 'in_invoice')], limit=1) self.assertEqual(supplier_invoice.invoice_line_ids.analytic_distribution, {str(analytic_account_company_b.id): 100, str(inter_company_analytic_account.id): 100}) def test_no_default_analytic_distribution_company_b(self): """ [Analytic Distribution Model is not set for Company B + Inter Company Analytic Account is set] - With Company A, create an Invoice for Company B with a line set with an analytic distribution model available for Company B -> The analytic distribution set on the supplier invoice line should be the same as defined in the customer invoice line created in Company A as the analytic account is available for Company B and there are no default analytic distribution model set for Company B """ inter_company_analytic_account = self._configure_analytic(product=self.product_b) self._create_post_invoice(product_id=self.product_a.id, analytic_distribution={inter_company_analytic_account.id: 100}) supplier_invoice = self.env['account.move'].with_user(self.res_users_company_b).search([('move_type', '=', 'in_invoice')], limit=1) self.assertEqual(supplier_invoice.invoice_line_ids.analytic_distribution, {str(inter_company_analytic_account.id): 100}) def test_multi_analytic_account_distribution_company_b(self): """ Test that the analytic distribution is set properly when multiple analytic accounts (with or without a company) are set on the invoice line """ analytic_account_company_a = self._configure_analytic(company=self.company_a, product=self.product_a) inter_company_analytic_account = self._configure_analytic(product=self.product_a) self._create_post_invoice(product_id=self.product_a.id, analytic_distribution={ analytic_account_company_a.id: 50, inter_company_analytic_account.id: 50, f"{analytic_account_company_a.id},{inter_company_analytic_account.id}": 100 }) supplier_invoice = self.env['account.move'].with_user(self.res_users_company_b).search([('move_type', '=', 'in_invoice')], limit=1) self.assertEqual(supplier_invoice.invoice_line_ids.analytic_distribution, {str(inter_company_analytic_account.id): 50}) def test_default_analytic_distribution_company_a(self): """ [Analytic Distribution Model is set for Company A] - With Company A, create an Invoice for Company B with a line set with an analytic distribution model not available for Company B -> There should be no analytic distribution set on the supplier invoice line as there is no analytic distribution model available for Company B and the analytic account is not available for Company B """ analytic_account_company_a = self._configure_analytic(company=self.company_a, product=self.product_a) self._create_post_invoice(product_id=self.product_a.id, analytic_distribution={analytic_account_company_a.id: 100}) supplier_invoice = self.env['account.move'].with_user(self.res_users_company_b).search([('move_type', '=', 'in_invoice')], limit=1) self.assertFalse(supplier_invoice.invoice_line_ids.analytic_distribution, "Analytic distribution should not be set on the invoice line.") def test_inter_company_invoice_flow_sub_companies(self): """ Test that the flow with inter company invoice is also working properly with sub companies """ # Create branches for company a self.company_a.write({'child_ids': [ Command.create({'name': 'Branch 1 of company a'}), Command.create({'name': 'Branch 2 of company a'}), ]}) self.cr.precommit.run() # load the COA branch_1, branch_2 = self.company_a.child_ids (branch_1 + branch_2).write({ 'intercompany_generate_bills_refund': True, }) # It's required to have an intercompany_journal_id set to be able to do the generation for branch in [branch_1, branch_2]: branch.intercompany_purchase_journal_id = self.env['account.journal'].create({ 'name': 'Purchases - Test', 'code': 'TEXJ', 'type': 'purchase', 'company_id': branch.id, }) # Select the two branches self.env.user.write({ 'company_ids': [Command.set((branch_1 + branch_2).ids)], 'company_id': branch_1.id, }) # Invoice from Branch 1 to Branch 2 customer_invoice = self.env['account.move'].with_context(allowed_company_ids=branch_1.ids).create({ 'move_type': 'out_invoice', 'invoice_date': '2023-05-01', 'partner_id': branch_2.partner_id.id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, 'price_unit': 100.0, 'quantity': 1.0, 'tax_ids': False, })] }) customer_invoice.action_post() bill = self.env['account.move'].search([('move_type', '=', 'in_invoice')], limit=1) self.assertRecordValues(bill, [{ 'partner_id': branch_1.partner_id.id, 'company_id': branch_2.id, 'payment_reference': customer_invoice.payment_reference, }]) def test_inter_company_invoice_product_not_accessible(self): """ Whenever Company A invoices Company B with a Product A defined only for Company A We don't set Product A (access error) but we define only the invoice line's label with Product A's name """ self.product_a.company_id = self.company_a self._create_post_invoice(self.product_a.id) supplier_invoice = self.env['account.move'].with_user(self.res_users_company_b).search([('move_type', '=', 'in_invoice')], limit=1) self.assertFalse(supplier_invoice.invoice_line_ids.product_id, "No product should be set") self.assertEqual(supplier_invoice.invoice_line_ids.name, self.product_a.name) def test_analytic_distribution_model_partner(self): """ If company B defines Company A as a partner in its distribution model, the distribution should be retrieved """ inter_company_analytic_account = self._configure_analytic(product=self.product_b) analytic_account_company_b = self._configure_analytic(company=self.company_b, partner=self.company_a.partner_id) self._create_post_invoice(product_id=self.product_a.id, analytic_distribution={inter_company_analytic_account.id: 100}) supplier_invoice = self.env['account.move'].with_user(self.res_users_company_b).search([('move_type', '=', 'in_invoice')], limit=1) expected_distribution = { str(inter_company_analytic_account.id): 100, str(analytic_account_company_b.id): 100 } self.assertEqual(supplier_invoice.invoice_line_ids.analytic_distribution, expected_distribution) def test_inter_company_attachment_with_contact_as_partner(self): """ Test that when creating and printing an invoice in company A for an individual contact belonging to company B, the corresponding bill in company B there is an attachment. """ company_partner = self.env['res.partner'].create({ 'name': 'company partner', 'parent_id': self.company_b.partner_id.id, }) customer_invoice = self.env['account.move'].create({ 'company_id': self.company_a.id, 'move_type': 'out_invoice', 'invoice_date': '2023-05-01', 'partner_id': company_partner.id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, 'price_unit': 100.0, 'quantity': 1.0, 'tax_ids': False, })] }) customer_invoice.action_post() self.env['account.move.send.wizard'].with_context(active_model='account.move', active_ids=customer_invoice.id)._generate_and_send_invoices(customer_invoice) bill = self.env['account.move'].search([('move_type', '=', 'in_invoice'), ('company_id', '=', self.company_b.id)], limit=1) self.assertTrue(bill.attachment_ids) def test_inter_company_with_contact_as_partner_same_company(self): """ Test that when creating a bill in company A for an individual contact belonging to the same company A, no invoice is created. """ company_partner = self.env['res.partner'].create({ 'name': 'company partner', 'parent_id': self.company_a.partner_id.id, }) customer_expense_bill = self.env['account.move'].create({ 'company_id': self.company_a.id, 'move_type': 'in_invoice', 'invoice_date': '2023-05-01', 'partner_id': company_partner.id, 'invoice_line_ids': [Command.create({ 'product_id': self.product_a.id, 'price_unit': 100.0, 'quantity': 1.0, 'tax_ids': False, })] }) customer_expense_bill.action_post() invoice = self.env['account.move'].search([('move_type', '=', 'out_invoice'), ('company_id', '=', self.company_a.id)], limit=1) self.assertFalse(invoice)