diff --git a/addons/account/models/account_move_line.py b/addons/account/models/account_move_line.py index baf779c2fd4..b5d8178053f 100644 --- a/addons/account/models/account_move_line.py +++ b/addons/account/models/account_move_line.py @@ -866,17 +866,13 @@ class AccountMoveLine(models.Model): company_domain = self.env['account.tax']._check_company_domain(self.move_id.company_id) if self.move_id.is_sale_document(include_receipts=True): # Out invoice. - if self.product_id.taxes_id: - tax_ids = self.product_id.taxes_id.filtered_domain(company_domain) - else: - tax_ids = self.account_id.tax_ids.filtered(lambda tax: tax.type_tax_use == 'sale') + filtered_taxes_id = self.product_id.taxes_id.filtered_domain(company_domain) + tax_ids = filtered_taxes_id or self.account_id.tax_ids.filtered(lambda tax: tax.type_tax_use == 'sale') elif self.move_id.is_purchase_document(include_receipts=True): # In invoice. - if self.product_id.supplier_taxes_id: - tax_ids = self.product_id.supplier_taxes_id.filtered_domain(company_domain) - else: - tax_ids = self.account_id.tax_ids.filtered(lambda tax: tax.type_tax_use == 'purchase') + filtered_supplier_taxes_id = self.product_id.supplier_taxes_id.filtered_domain(company_domain) + tax_ids = filtered_supplier_taxes_id or self.account_id.tax_ids.filtered(lambda tax: tax.type_tax_use == 'purchase') else: tax_ids = self.account_id.tax_ids diff --git a/addons/account/models/account_tax.py b/addons/account/models/account_tax.py index acafbf14f0c..476c56d7b87 100644 --- a/addons/account/models/account_tax.py +++ b/addons/account/models/account_tax.py @@ -381,6 +381,8 @@ class AccountTax(models.Model): name += ' (%s)' % type_tax_use.get(record.type_tax_use) if record.tax_scope: name += ' (%s)' % tax_scope.get(record.tax_scope) + if len(self.env.companies) > 1 and self.env.context.get('params', {}).get('model') == 'product.template': + name += ' (%s)' % record.company_id.display_name record.display_name = name @api.onchange('amount') diff --git a/addons/account/models/product.py b/addons/account/models/product.py index faf9e35437f..509181c8bed 100644 --- a/addons/account/models/product.py +++ b/addons/account/models/product.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from odoo import api, fields, models, _ +from odoo import api, Command, fields, models, _ from odoo.exceptions import ValidationError from odoo.osv import expression from odoo.tools import format_amount @@ -27,12 +27,12 @@ class ProductTemplate(models.Model): taxes_id = fields.Many2many('account.tax', 'product_taxes_rel', 'prod_id', 'tax_id', help="Default taxes used when selling the product.", string='Customer Taxes', domain=[('type_tax_use', '=', 'sale')], - default=lambda self: self.env.company.account_sale_tax_id or self.env.company.root_id.account_sale_tax_id, + default=lambda self: self.env.companies.account_sale_tax_id or self.env.companies.root_id.account_sale_tax_id, ) tax_string = fields.Char(compute='_compute_tax_string') supplier_taxes_id = fields.Many2many('account.tax', 'product_supplier_taxes_rel', 'prod_id', 'tax_id', string='Vendor Taxes', help='Default taxes used when buying the product.', domain=[('type_tax_use', '=', 'purchase')], - default=lambda self: self.env.company.account_purchase_tax_id or self.env.company.root_id.account_purchase_tax_id, + default=lambda self: self.env.companies.account_purchase_tax_id or self.env.companies.root_id.account_purchase_tax_id, ) property_account_income_id = fields.Many2one('account.account', company_dependent=True, string="Income Account", @@ -118,6 +118,27 @@ class ProductTemplate(models.Model): "If you want to change its Unit of Measure, please archive this product and create a new one." )) + @api.model_create_multi + def create(self, vals_list): + products = super().create(vals_list) + # If no company was set for the product, the product will be available for all companies and therefore should + # have the default taxes of the other companies as well. sudo() is used since we're going to need to fetch all + # the other companies default taxes which the user may not have access to. + other_companies = self.env['res.company'].sudo().search([('id', 'not in', self.env.companies.ids)]) + if not other_companies: + return products + + default_customer_tax_ids = other_companies.filtered('account_sale_tax_id').account_sale_tax_id.ids + default_supplier_tax_ids = other_companies.filtered('account_purchase_tax_id').account_purchase_tax_id.ids + + products_without_company = products.filtered(lambda p: not p.company_id).sudo() + products_without_company.taxes_id = [Command.link(tax) for tax in default_customer_tax_ids] + products_without_company.supplier_taxes_id = [Command.link(tax) for tax in default_supplier_tax_ids] + + products_without_company.invalidate_recordset(['taxes_id', 'supplier_taxes_id']) + products.invalidate_recordset(['taxes_id', 'supplier_taxes_id']) + return products + class ProductProduct(models.Model): _inherit = "product.product" diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py index f97496fa462..8bb201b73d5 100644 --- a/addons/account/tests/__init__.py +++ b/addons/account/tests/__init__.py @@ -42,3 +42,4 @@ from . import test_multivat from . import test_account_partner from . import test_setup_wizard from . import test_structured_reference +from . import test_product diff --git a/addons/account/tests/test_product.py b/addons/account/tests/test_product.py new file mode 100644 index 00000000000..4d3e3c4872c --- /dev/null +++ b/addons/account/tests/test_product.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged + + +@tagged('post_install', '-at_install') +class TestProduct(AccountTestInvoicingCommon): + + def test_multi_company_product_tax(self): + """ Ensure default taxes are set for all companies on products with no company set. """ + product_without_company = self.env['product.template'].with_context(allowed_company_ids=self.env.company.ids).create({ + 'name': 'Product Without a Company', + }) + product_with_company = self.env['product.template'].with_context(allowed_company_ids=self.env.company.ids).create({ + 'name': 'Product With a Company', + 'company_id': self.company_data['company'].id, + }) + companies = self.env['res.company'].sudo().search([]) + # Product should have all the default taxes of the other companies. + self.assertRecordValues(product_without_company.sudo(), [{ + 'taxes_id': companies.account_sale_tax_id.ids, + 'supplier_taxes_id': companies.account_purchase_tax_id.ids, + }]) + # Product should have only the default tax of the company it belongs to. + self.assertRecordValues(product_with_company.sudo(), [{ + 'taxes_id': self.company_data['company'].account_sale_tax_id.ids, + 'supplier_taxes_id': self.company_data['company'].account_purchase_tax_id.ids, + }]) diff --git a/addons/membership/models/partner.py b/addons/membership/models/partner.py index 8204176e4ee..a35e9c974e8 100644 --- a/addons/membership/models/partner.py +++ b/addons/membership/models/partner.py @@ -110,7 +110,16 @@ class Partner(models.Model): 'move_type': 'out_invoice', 'partner_id': partner.id, 'invoice_line_ids': [ - (0, None, {'product_id': product.id, 'quantity': 1, 'price_unit': amount, 'tax_ids': [(6, 0, product.taxes_id.ids)]}) + ( + 0, + None, + { + 'product_id': product.id, + 'quantity': 1, + 'price_unit': amount, + 'tax_ids': [(6, 0, product.taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(self.env.company)).ids)] + } + ) ] }) diff --git a/addons/point_of_sale/tests/common.py b/addons/point_of_sale/tests/common.py index a6ab74afd34..2115f8968ad 100644 --- a/addons/point_of_sale/tests/common.py +++ b/addons/point_of_sale/tests/common.py @@ -486,7 +486,7 @@ class TestPoSCommon(ValuationReconciliationTestCommon): def create_order_line(product, quantity, discount=0.0): price_unit = self.pricelist._get_product_price(product, quantity) - tax_ids = fiscal_position.map_tax(product.taxes_id) + tax_ids = fiscal_position.map_tax(product.taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(self.env.company))) price_unit_after_discount = price_unit * (1 - discount / 100.0) tax_values = ( tax_ids.compute_all(price_unit_after_discount, self.currency, quantity) diff --git a/addons/project_sale_expense/tests/test_project_profitability.py b/addons/project_sale_expense/tests/test_project_profitability.py index 5c11555401d..30967abfd8b 100644 --- a/addons/project_sale_expense/tests/test_project_profitability.py +++ b/addons/project_sale_expense/tests/test_project_profitability.py @@ -81,7 +81,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr ) self.assertDictEqual( expense_profitability['costs'], - {'id': 'expenses', 'sequence': expense_sequence, 'billed': -280.0 - expense_foreign.untaxed_amount * 0.2, 'to_bill': 0.0}, + {'id': 'expenses', 'sequence': expense_sequence, 'billed': expense.currency_id.round(-280.0 - expense_foreign.untaxed_amount * 0.2), 'to_bill': 0.0}, ) expense_sheet.action_sheet_move_create() @@ -109,7 +109,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr ) self.assertDictEqual( expense_profitability['costs'], - {'id': 'expenses', 'sequence': expense_sequence, 'billed': -280.0 - expense_foreign.untaxed_amount * 0.2, 'to_bill': 0.0}, + {'id': 'expenses', 'sequence': expense_sequence, 'billed': expense.currency_id.round(-280.0 - expense_foreign.untaxed_amount * 0.2), 'to_bill': 0.0}, ) self.assertDictEqual( @@ -135,7 +135,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr ) self.assertDictEqual( expense_profitability['costs'], - {'id': 'expenses', 'sequence': expense_sequence, 'billed': -280.0 - expense_foreign.untaxed_amount * 0.2, 'to_bill': 0.0}, + {'id': 'expenses', 'sequence': expense_sequence, 'billed': expense.currency_id.round(-280.0 - expense_foreign.untaxed_amount * 0.2), 'to_bill': 0.0}, ) self.assertDictEqual( @@ -183,7 +183,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr ) self.assertDictEqual( expense_profitability['costs'], - {'id': 'expenses', 'sequence': expense_sequence, 'billed': -280.0 - expense_foreign.untaxed_amount * 0.2, 'to_bill': 0.0}, + {'id': 'expenses', 'sequence': expense_sequence, 'billed': expense.currency_id.round(-280.0 - expense_foreign.untaxed_amount * 0.2), 'to_bill': 0.0}, ) expense_sheet._do_refuse('Test Cancel Expense') @@ -194,7 +194,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr ) self.assertDictEqual( expense_profitability.get('costs', {}), - {'id': 'expenses', 'sequence': expense_sequence, 'billed': -expense_foreign.untaxed_amount * 0.2, 'to_bill': 0.0}, + {'id': 'expenses', 'sequence': expense_sequence, 'billed': expense.currency_id.round(-expense_foreign.untaxed_amount * 0.2), 'to_bill': 0.0}, ) invoice = self.env['sale.advance.payment.inv'].with_context({ @@ -227,7 +227,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr ) self.assertDictEqual( expense_profitability['costs'], - {'id': 'expenses', 'sequence': expense_sequence, 'billed': -expense_foreign.untaxed_amount * 0.2, 'to_bill': 0.0}, + {'id': 'expenses', 'sequence': expense_sequence, 'billed': expense.currency_id.round(-expense_foreign.untaxed_amount * 0.2), 'to_bill': 0.0}, ) expense_sheet_foreign._do_refuse('Test Cancel Expense') diff --git a/addons/purchase_stock/tests/test_anglo_saxon_valuation_reconciliation.py b/addons/purchase_stock/tests/test_anglo_saxon_valuation_reconciliation.py index 05a43e4ee0a..0e24f3544ee 100644 --- a/addons/purchase_stock/tests/test_anglo_saxon_valuation_reconciliation.py +++ b/addons/purchase_stock/tests/test_anglo_saxon_valuation_reconciliation.py @@ -316,6 +316,7 @@ class TestValuationReconciliation(ValuationReconciliationTestCommon): 'amount': 33.3333, 'company_id': self.company_data['company'].id, 'cash_basis_transition_account_id': cash_basis_transfer_account.id, + 'type_tax_use': 'purchase', 'tax_exigibility': 'on_payment', 'invoice_repartition_line_ids': [ (0, 0, {