Skip to content

Commit 615b8e0

Browse files
committed
[18.0][MIG] product_tax_multicompany_default: migration to 18.0
This PR is a continuation of PR OCA#811. This PR is for the migration of the product_tax_multicompany_default module, which is used to propagate taxes across multiple companies. This commit makes changes so that it is not duplicated in the Sales Taxes form view, and removes the functionality when creating a new product in all companies, since Odoo 18 already does this. @moduon MT-11041
1 parent c443832 commit 615b8e0

7 files changed

Lines changed: 217 additions & 106 deletions

File tree

product_tax_multicompany_default/README.rst

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Product Tax Multi Company Default
33
=================================
44

5-
..
5+
..
66
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
77
!! This file is generated by oca-gen-addon-readme !!
88
!! changes will be overwritten. !!
@@ -31,7 +31,8 @@ Product Tax Multi Company Default
3131
This module sets the default company taxes for all the existing
3232
companies when a product is created. It also adds a button in product
3333
view to set all the taxes from other companies matching them by tax
34-
code.
34+
code. If a default tax is propagated, it will be propagated as the
35+
default tax in all companies, even if they are from different countries.
3536

3637
**Table of contents**
3738

@@ -94,20 +95,20 @@ Authors
9495
Contributors
9596
------------
9697

97-
- `Tecnativa <https://www.tecnativa.com>`__:
98+
- `Tecnativa <https://www.tecnativa.com>`__:
9899

99-
- Carlos Dauden
100-
- Pedro M. Baeza
101-
- Vicent Cubells
102-
- Ernesto Tejeda
100+
- Carlos Dauden
101+
- Pedro M. Baeza
102+
- Vicent Cubells
103+
- Ernesto Tejeda
103104

104-
- Loo <http://odooerp.cl/>`\_:
105+
- Loo <http://odooerp.cl/>`\_:
105106

106-
- Carlos Lopez
107+
- Carlos Lopez
107108

108-
- `Moduon <https://www.moduon.team>`__:
109+
- `Moduon <https://www.moduon.team>`__:
109110

110-
- Eduardo de Miguel
111+
- Eduardo de Miguel
111112

112113
Maintainers
113114
-----------
@@ -128,7 +129,7 @@ promote its widespread use.
128129

129130
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
130131

131-
|maintainer-Shide|
132+
|maintainer-Shide|
132133

133134
This module is part of the `OCA/multi-company <https://github.com/OCA/multi-company/tree/18.0/product_tax_multicompany_default>`_ project on GitHub.
134135

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
# Translation of Odoo Server.
22
# This file contains the translation of the following modules:
3-
# * product_tax_multicompany_default
3+
# * product_tax_multicompany_default
44
#
55
# Translators:
66
# OCA Transbot <transbot@odoo-community.org>, 2017
77
msgid ""
88
msgstr ""
9-
"Project-Id-Version: Odoo Server 9.0c\n"
9+
"Project-Id-Version: Odoo Server 18.0\n"
1010
"Report-Msgid-Bugs-To: \n"
11-
"POT-Creation-Date: 2019-12-04 19:36+0100\n"
12-
"PO-Revision-Date: 2021-03-09 10:45+0000\n"
13-
"Last-Translator: Ana Suárez <ana.suarez@qubiq.es>\n"
14-
"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n"
11+
"POT-Creation-Date: 2025-09-29 07:33+0000\n"
12+
"PO-Revision-Date: 2025-09-29 09:38+0200\n"
13+
"Last-Translator: Xino61122 <german@moduon.team>\n"
14+
"Language-Team: \n"
1515
"Language: es\n"
1616
"MIME-Version: 1.0\n"
1717
"Content-Type: text/plain; charset=UTF-8\n"
1818
"Content-Transfer-Encoding: 8bit\n"
19-
"Plural-Forms: nplurals=2; plural=n != 1;\n"
20-
"X-Generator: Weblate 4.3.2\n"
19+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
20+
"X-Generator: Poedit 3.5\n"
2121

2222
#. module: product_tax_multicompany_default
2323
#: model:ir.model.fields,help:product_tax_multicompany_default.field_product_product__divergent_company_taxes
@@ -26,12 +26,14 @@ msgid ""
2626
"Does this product have divergent cross-company taxes? (Only for multi-"
2727
"company products)"
2828
msgstr ""
29+
"¿Este producto tiene impuestos divergentes entre empresas? (Solo para "
30+
"productos multiempresa)"
2931

3032
#. module: product_tax_multicompany_default
3133
#: model:ir.model.fields,field_description:product_tax_multicompany_default.field_product_product__divergent_company_taxes
3234
#: model:ir.model.fields,field_description:product_tax_multicompany_default.field_product_template__divergent_company_taxes
3335
msgid "Has divergent cross-company taxes"
34-
msgstr ""
36+
msgstr "Tiene impuestos divergentes entre empresas"
3537

3638
#. module: product_tax_multicompany_default
3739
#: model:ir.model,name:product_tax_multicompany_default.model_product_template
@@ -41,13 +43,19 @@ msgstr "Producto"
4143
#. module: product_tax_multicompany_default
4244
#: model:ir.model,name:product_tax_multicompany_default.model_product_product
4345
msgid "Product Variant"
44-
msgstr ""
46+
msgstr "Variante de producto"
4547

4648
#. module: product_tax_multicompany_default
4749
#: model:ir.actions.server,name:product_tax_multicompany_default.action_set_multicompany_taxes
4850
#: model_terms:ir.ui.view,arch_db:product_tax_multicompany_default.product_template_form_view
4951
msgid "Propagate Taxes"
5052
msgstr "Propagar impuestos"
5153

52-
#~ msgid "Product Template (Multi-Company)"
53-
#~ msgstr "Plantilla de producto (multi-compañía)"
54+
#. module: product_tax_multicompany_default
55+
#: model_terms:ir.ui.view,arch_db:product_tax_multicompany_default.product_template_form_view
56+
msgid ""
57+
"This button propagates sales and purchase taxes if they have changed and if "
58+
"the companies have the same tax rate."
59+
msgstr ""
60+
"Este botón propaga los impuestos sobre las ventas y las compras si han "
61+
"cambiado y si las empresas tienen el mismo tipo de impuesto."

product_tax_multicompany_default/models/product.py

Lines changed: 143 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ class ProductTemplate(models.Model):
1414
string="Has divergent cross-company taxes",
1515
compute="_compute_divergent_company_taxes",
1616
compute_sudo=True,
17-
store=True,
1817
help=(
1918
"Does this product have divergent cross-company taxes? "
2019
"(Only for multi-company products)"
@@ -24,58 +23,153 @@ class ProductTemplate(models.Model):
2423
@api.depends("company_id", "taxes_id", "supplier_taxes_id")
2524
def _compute_divergent_company_taxes(self):
2625
"""Know if this product has divergent taxes across companies."""
26+
# Skip single-company products
27+
self.divergent_company_taxes = False
28+
if len(self.env["res.company"].search([]).ids) == 1:
29+
return
30+
for one in self:
31+
# A unique constraint in account.tax makes it impossible to have
32+
# duplicated tax names by company
33+
if self.company_id:
34+
continue
35+
one.divergent_company_taxes = one._is_divergent_company_taxes(
36+
"taxes"
37+
) or self._is_divergent_company_taxes("purchase")
38+
39+
def _is_divergent_company_taxes(self, tax_type):
40+
"""Returns true or false if there are differences in product taxes.
41+
42+
:param tax_type: can be 'taxes' or 'purchase'
43+
"""
44+
self.ensure_one()
2745
all_companies = self.env["res.company"].search(
2846
[
2947
# Useful for tests, to avoid pollution
30-
("id", "not in", self.env.context.get("ignored_company_ids", []))
48+
("id", "not in", self.env.context.get("ignored_company_ids", [])),
3149
]
3250
)
33-
for one in self:
34-
one.divergent_company_taxes = False
35-
# Skip single-company products
36-
if one.company_id:
37-
continue
38-
# A unique constraint in account.tax makes it impossible to have
39-
# duplicated tax names by company
40-
customer_taxes = {
41-
frozenset(tax.name for tax in one.taxes_id if tax.company_id == company)
42-
for company in all_companies
43-
}
44-
if len(customer_taxes) > 1:
45-
one.divergent_company_taxes = True
46-
continue
47-
supplier_taxes = {
48-
frozenset(
49-
tax.name
50-
for tax in one.supplier_taxes_id
51-
if tax.company_id == company
51+
current_company = self.env.company
52+
53+
company_tax_bd_map = dict(self._origin._get_product_taxes(tax_type))
54+
for company_id in list(company_tax_bd_map.keys()):
55+
if company_id not in all_companies.ids or company_id == current_company.id:
56+
company_tax_bd_map.pop(company_id)
57+
if tax_type == "taxes":
58+
current_tax = self.taxes_id
59+
field_name_data = "account_sale_tax_id"
60+
elif tax_type == "purchase":
61+
current_tax = self.supplier_taxes_id
62+
field_name_data = "account_purchase_tax_id"
63+
propagate_taxes = False
64+
65+
# Tax-free
66+
if not current_tax:
67+
# Since all companies can have an empty tax field,if there is data
68+
# in the database, it means that we can propagate leaving it empty.
69+
if len(company_tax_bd_map) > 0:
70+
propagate_taxes = True
71+
# No tax in other products
72+
elif not company_tax_bd_map:
73+
# If we do not have any taxes in the database and we add a tax, it means
74+
# that we can update the product taxes.
75+
current_product_tax_ids = current_tax.filtered(
76+
lambda tax: tax.company_id == current_company
77+
).ids
78+
current_product_taxes_other_companies = set()
79+
other_companies = all_companies.filtered(
80+
lambda company: company.id != current_company.id
81+
)
82+
for company in other_companies:
83+
current_product_taxes_other_companies.update(
84+
self._taxes_by_company(
85+
field_name_data, company, current_product_tax_ids
86+
)
5287
)
53-
for company in all_companies
54-
}
55-
if len(supplier_taxes) > 1:
56-
one.divergent_company_taxes = True
57-
continue
88+
if len(current_product_taxes_other_companies) > 0:
89+
propagate_taxes = True
90+
else:
91+
current_names = current_tax.filtered(
92+
lambda tax: tax.company_id == current_company
93+
).mapped("name")
94+
current_product_tax_ids = current_tax.filtered(
95+
lambda tax: tax.company_id == current_company
96+
).ids
97+
for company in all_companies - current_company:
98+
if company_tax_bd_map.get(company.id) and current_names == list(
99+
company_tax_bd_map.get(company.id).values()
100+
):
101+
continue
102+
# We are looking to see if there are any taxes that can be applied to
103+
# other companies from the taxes that the current product has.
104+
current_product_taxes_other_companies = self._taxes_by_company(
105+
field_name_data,
106+
self.env["res.company"].browse(company.id),
107+
current_product_tax_ids,
108+
)
109+
if len(current_product_taxes_other_companies) == len(current_tax):
110+
propagate_taxes = True
111+
break
112+
113+
return propagate_taxes
114+
115+
def _get_product_taxes(self, field):
116+
"""Returns taxes on purchase or sales taxes
117+
118+
We need the product taxes for other companies, and we cannot take
119+
them from the object itself, so we have to consult the database
120+
where the product taxes passed by parameter are located for all
121+
companies.
122+
123+
:param field: can be 'taxes' or 'purchase'
124+
"""
125+
if not self.ids:
126+
return []
127+
# Need stay her because sometimes can be empty
128+
self.ensure_one()
58129

59-
def taxes_by_company(self, field, company, match_tax_ids=None):
130+
table_by_field = {
131+
"taxes": "product_taxes_rel",
132+
"purchase": "product_supplier_taxes_rel",
133+
}
134+
field_name = table_by_field.get(field)
135+
136+
if not field_name:
137+
raise ValueError("field should be 'taxes' or 'purchase'")
138+
139+
sql = f"""
140+
SELECT DISTINCT a_tax.company_id, a_tax.name
141+
FROM {field_name} ptr
142+
LEFT JOIN account_tax a_tax ON tax_id = a_tax.id
143+
WHERE ptr.prod_id = %s
144+
"""
145+
self.env.cr.execute(sql, [self.id])
146+
return self.env.cr.fetchall()
147+
148+
def _taxes_by_company(self, field, company, match_tax_ids=None):
149+
"""Returns the IDs of all accounts that match the parameters passed.
150+
151+
:param field: fields to search (account_sale_tax_id or account_purchase_tax_id)
152+
:param company: company to search for
153+
:param match_tax_ids: tax IDs to search for
154+
"""
60155
taxes_ids = []
61156
if match_tax_ids is None:
62157
taxes_ids = company[field].ids
63158
# If None: return default taxes
64159
if not match_tax_ids:
65160
return taxes_ids
66161
type_tax_use = "sale" if field == "account_sale_tax_id" else "purchase"
67-
AccountTax = self.env["account.tax"]
68-
for tax in AccountTax.browse(match_tax_ids):
69-
taxes_ids.extend(
70-
AccountTax.search(
71-
[
72-
("type_tax_use", "=", type_tax_use),
73-
("name", "=", tax.name),
74-
("company_id", "=", company.id),
75-
]
76-
).ids
77-
)
78-
return taxes_ids
162+
account_tax = self.env["account.tax"].sudo().browse(match_tax_ids)
163+
taxes = account_tax.filtered_domain([("type_tax_use", "=", type_tax_use)])
164+
if not taxes:
165+
return []
166+
return account_tax.search(
167+
[
168+
("type_tax_use", "=", type_tax_use),
169+
("company_id", "=", company.id),
170+
("name", "in", taxes.mapped("name")),
171+
]
172+
).ids
79173

80174
def _delete_product_taxes(
81175
self,
@@ -94,31 +188,30 @@ def _delete_product_taxes(
94188
if excl_customer_tax_ids:
95189
customer_sql += tax_where
96190
customer_sql_params.append(tuple(excl_customer_tax_ids))
97-
self.env.cr.execute(customer_sql + ";", customer_sql_params)
191+
self.env.cr.execute(customer_sql, customer_sql_params)
98192
# Delete supplier taxes
99193
supplier_sql = "DELETE FROM product_supplier_taxes_rel WHERE prod_id IN %s"
100194
supplier_sql_params = [tuple(self.ids)]
101195
if excl_supplier_tax_ids:
102196
supplier_sql += tax_where
103197
supplier_sql_params.append(tuple(excl_supplier_tax_ids))
104-
self.env.cr.execute(supplier_sql + ";", supplier_sql_params)
198+
self.env.cr.execute(supplier_sql, supplier_sql_params)
105199

106200
def set_multicompany_taxes(self):
107201
self.ensure_one()
108202
user_company = self.env.company
109203
customer_tax = self.taxes_id
110204
customer_tax_ids = customer_tax.ids
111-
if not customer_tax.filtered(lambda r: r.company_id == user_company):
205+
if not customer_tax.filtered(lambda tax: tax.company_id == user_company):
112206
customer_tax_ids = []
113207
supplier_tax = self.supplier_taxes_id
114208
supplier_tax_ids = supplier_tax.ids
115-
if not supplier_tax.filtered(lambda r: r.company_id == user_company):
209+
if not supplier_tax.filtered(lambda tax: tax.company_id == user_company):
116210
supplier_tax_ids = []
117-
obj = self.sudo()
118-
default_customer_tax_ids = obj.taxes_by_company(
211+
default_customer_tax_ids = self._taxes_by_company(
119212
"account_sale_tax_id", user_company
120213
)
121-
default_supplier_tax_ids = obj.taxes_by_company(
214+
default_supplier_tax_ids = self._taxes_by_company(
122215
"account_purchase_tax_id", user_company
123216
)
124217
# Clean taxes from other companies (cannot replace it with sudo)
@@ -137,14 +230,14 @@ def set_multicompany_taxes(self):
137230
if default_supplier_tax_ids != supplier_tax_ids
138231
else None
139232
)
140-
for company in obj.env["res.company"].search([("id", "!=", user_company.id)]):
233+
for company in self.env["res.company"].search([("id", "!=", user_company.id)]):
141234
customer_tax_ids.extend(
142-
obj.taxes_by_company(
235+
self._taxes_by_company(
143236
"account_sale_tax_id", company, match_customer_tax_ids
144237
)
145238
)
146239
supplier_tax_ids.extend(
147-
obj.taxes_by_company(
240+
self._taxes_by_company(
148241
"account_purchase_tax_id", company, match_suplier_tax_ids
149242
)
150243
)
@@ -160,6 +253,7 @@ def create(self, vals_list):
160253
new_products = super().create(vals_list)
161254
for product in new_products:
162255
product.set_multicompany_taxes()
256+
new_products.invalidate_recordset(fnames=["taxes_id", "supplier_taxes_id"])
163257
return new_products
164258

165259

product_tax_multicompany_default/readme/DESCRIPTION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ This module sets the default company taxes for all the existing
22
companies when a product is created. It also adds a button in product
33
view to set all the taxes from other companies matching them by tax
44
code.
5+
If a default tax is propagated, it will be propagated as the default
6+
tax in all companies, even if they are from different countries.

0 commit comments

Comments
 (0)