Skip to content

Commit a7ff45a

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 a7ff45a

5 files changed

Lines changed: 184 additions & 89 deletions

File tree

product_tax_multicompany_default/README.rst

Lines changed: 11 additions & 11 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. !!
@@ -94,20 +94,20 @@ Authors
9494
Contributors
9595
------------
9696

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

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

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

106-
- Carlos Lopez
106+
- Carlos Lopez
107107

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

110-
- Eduardo de Miguel
110+
- Eduardo de Miguel
111111

112112
Maintainers
113113
-----------
@@ -128,7 +128,7 @@ promote its widespread use.
128128

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

131-
|maintainer-Shide|
131+
|maintainer-Shide|
132132

133133
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.
134134

product_tax_multicompany_default/models/product.py

Lines changed: 137 additions & 46 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,57 +23,149 @@ 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+
if self.company_id or len(self.env["res.company"].search([]).ids) <= 1:
28+
self.divergent_company_taxes = False
29+
return
30+
self.divergent_company_taxes = False
31+
for one in self:
32+
# A unique constraint in account.tax makes it impossible to have
33+
# duplicated tax names by company
34+
35+
one.divergent_company_taxes = one._is_divergent_company_taxes(
36+
"taxes"
37+
) or self._is_divergent_company_taxes("purchase")
38+
if one.divergent_company_taxes:
39+
continue
40+
41+
def _is_divergent_company_taxes(self, tax_type):
42+
"""Returns true or false if there are differences in product taxes.
43+
44+
:param one: product to be checked
45+
:param tax_type: can be 'taxes' or 'purchase'
46+
"""
47+
self.ensure_one()
2748
all_companies = self.env["res.company"].search(
2849
[
2950
# Useful for tests, to avoid pollution
30-
("id", "not in", self.env.context.get("ignored_company_ids", []))
51+
("id", "not in", self.env.context.get("ignored_company_ids", [])),
3152
]
3253
)
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
54+
current_company = self.env.company
55+
56+
dict_product_taxes_bd = dict(self._origin._get_product_taxes(tax_type))
57+
for company_id in list(dict_product_taxes_bd.keys()):
58+
if company_id not in all_companies.ids or company_id == current_company.id:
59+
dict_product_taxes_bd.pop(company_id)
60+
if tax_type == "taxes":
61+
current_tax = self.taxes_id
62+
field_name_data = "account_sale_tax_id"
63+
elif tax_type == "purchase":
64+
current_tax = self.supplier_taxes_id
65+
field_name_data = "account_purchase_tax_id"
66+
propagate_taxes = False
67+
68+
# Tax-free
69+
if not current_tax:
70+
# Since all companies can have an empty tax field,if there is data
71+
# in the database, it means that we can propagate leaving it empty.
72+
if len(dict_product_taxes_bd) > 0:
73+
propagate_taxes = True
74+
# No tax in other products
75+
elif not dict_product_taxes_bd:
76+
# If we do not have any taxes in the database and we add a tax, it means
77+
# that we can update the product taxes.
78+
current_product_tax_ids = current_tax.filtered(
79+
lambda t: t.company_id == current_company
80+
).ids
81+
current_product_taxes_other_companies = set()
82+
other_companies = all_companies.filtered(
83+
lambda c: c.id != current_company.id
84+
)
85+
for company in other_companies:
86+
current_product_taxes_other_companies.update(
87+
self._taxes_by_company(
88+
field_name_data, company, current_product_tax_ids
89+
)
5290
)
53-
for company in all_companies
54-
}
55-
if len(supplier_taxes) > 1:
56-
one.divergent_company_taxes = True
57-
continue
91+
if len(current_product_taxes_other_companies) > 0:
92+
propagate_taxes = True
93+
else:
94+
current_names = current_tax.filtered(
95+
lambda t: t.company_id == current_company
96+
).mapped("name")
97+
current_product_tax_ids = current_tax.filtered(
98+
lambda t: t.company_id == current_company
99+
).ids
100+
for company_id, value in dict_product_taxes_bd.items():
101+
if current_names == list(value.values()):
102+
continue
103+
# We are looking to see if there are any taxes that can be applied to
104+
# other companies from the taxes that the current product has.
105+
current_product_taxes_other_companies = self._taxes_by_company(
106+
field_name_data,
107+
self.env["res.company"].browse(company_id),
108+
current_product_tax_ids,
109+
)
110+
if len(current_product_taxes_other_companies) == len(current_tax):
111+
propagate_taxes = True
112+
break
113+
114+
return propagate_taxes
115+
116+
def _get_product_taxes(self, field):
117+
"""Returns taxes on purchase or sales taxes
58118
59-
def taxes_by_company(self, field, company, match_tax_ids=None):
119+
We need the product taxes for other companies, and we cannot take
120+
them from the object itself, so we have to consult the database
121+
where the product taxes passed by parameter are located for all
122+
companies.
123+
124+
:param field: can be 'taxes' or 'purchase'
125+
"""
126+
if not self.id:
127+
return []
128+
129+
self.ensure_one()
130+
131+
table_by_field = {
132+
"taxes": "product_taxes_rel",
133+
"purchase": "product_supplier_taxes_rel",
134+
}
135+
field_name = table_by_field.get(field)
136+
137+
if not field_name:
138+
raise ValueError("field should be 'taxes' or 'purchase'")
139+
140+
sql = f"""
141+
SELECT DISTINCT a_tax.company_id, a_tax.name
142+
FROM {field_name} ptr
143+
LEFT JOIN account_tax a_tax ON tax_id = a_tax.id
144+
WHERE ptr.prod_id = %s
145+
"""
146+
self.env.cr.execute(sql, [self.id])
147+
return self.env.cr.fetchall()
148+
149+
def _taxes_by_company(self, field, company, match_tax_ids=None):
60150
taxes_ids = []
61151
if match_tax_ids is None:
62152
taxes_ids = company[field].ids
63153
# If None: return default taxes
64154
if not match_tax_ids:
65155
return taxes_ids
66156
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-
)
157+
account_tax = self.env["account.tax"].sudo().browse(match_tax_ids)
158+
taxes_ids = account_tax.filtered_domain([("type_tax_use", "=", type_tax_use)])
159+
if not taxes_ids:
160+
return taxes_ids
161+
taxes_name = list({n for n in taxes_ids.mapped("name") if n})
162+
taxes_ids = account_tax.search(
163+
[
164+
("type_tax_use", "=", type_tax_use),
165+
("company_id", "=", company.id),
166+
("name", "in", taxes_name),
167+
]
168+
).ids
78169
return taxes_ids
79170

80171
def _delete_product_taxes(
@@ -94,14 +185,14 @@ def _delete_product_taxes(
94185
if excl_customer_tax_ids:
95186
customer_sql += tax_where
96187
customer_sql_params.append(tuple(excl_customer_tax_ids))
97-
self.env.cr.execute(customer_sql + ";", customer_sql_params)
188+
self.env.cr.execute(customer_sql, customer_sql_params)
98189
# Delete supplier taxes
99190
supplier_sql = "DELETE FROM product_supplier_taxes_rel WHERE prod_id IN %s"
100191
supplier_sql_params = [tuple(self.ids)]
101192
if excl_supplier_tax_ids:
102193
supplier_sql += tax_where
103194
supplier_sql_params.append(tuple(excl_supplier_tax_ids))
104-
self.env.cr.execute(supplier_sql + ";", supplier_sql_params)
195+
self.env.cr.execute(supplier_sql, supplier_sql_params)
105196

106197
def set_multicompany_taxes(self):
107198
self.ensure_one()
@@ -114,11 +205,10 @@ def set_multicompany_taxes(self):
114205
supplier_tax_ids = supplier_tax.ids
115206
if not supplier_tax.filtered(lambda r: r.company_id == user_company):
116207
supplier_tax_ids = []
117-
obj = self.sudo()
118-
default_customer_tax_ids = obj.taxes_by_company(
208+
default_customer_tax_ids = self._taxes_by_company(
119209
"account_sale_tax_id", user_company
120210
)
121-
default_supplier_tax_ids = obj.taxes_by_company(
211+
default_supplier_tax_ids = self._taxes_by_company(
122212
"account_purchase_tax_id", user_company
123213
)
124214
# Clean taxes from other companies (cannot replace it with sudo)
@@ -137,14 +227,14 @@ def set_multicompany_taxes(self):
137227
if default_supplier_tax_ids != supplier_tax_ids
138228
else None
139229
)
140-
for company in obj.env["res.company"].search([("id", "!=", user_company.id)]):
230+
for company in self.env["res.company"].search([("id", "!=", user_company.id)]):
141231
customer_tax_ids.extend(
142-
obj.taxes_by_company(
232+
self._taxes_by_company(
143233
"account_sale_tax_id", company, match_customer_tax_ids
144234
)
145235
)
146236
supplier_tax_ids.extend(
147-
obj.taxes_by_company(
237+
self._taxes_by_company(
148238
"account_purchase_tax_id", company, match_suplier_tax_ids
149239
)
150240
)
@@ -160,6 +250,7 @@ def create(self, vals_list):
160250
new_products = super().create(vals_list)
161251
for product in new_products:
162252
product.set_multicompany_taxes()
253+
new_products.invalidate_recordset(fnames=["taxes_id", "supplier_taxes_id"])
163254
return new_products
164255

165256

product_tax_multicompany_default/static/description/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ <h1 class="title">Product Tax Multi Company Default</h1>
369369
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
370370
!! source digest: sha256:9754e5fe5a17246d3dcbb1677fb59090a7e43a4c9f7df455ee968461f3abfbac
371371
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
372-
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/multi-company/tree/18.0/product_tax_multicompany_default"><img alt="OCA/multi-company" src="https://img.shields.io/badge/github-OCA%2Fmulti--company-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/multi-company-18-0/multi-company-17-0-product_tax_multicompany_default"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/multi-company&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
372+
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/multi-company/tree/18.0/product_tax_multicompany_default"><img alt="OCA/multi-company" src="https://img.shields.io/badge/github-OCA%2Fmulti--company-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/multi-company-18-0/multi-company-18-0-product_tax_multicompany_default"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/multi-company&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
373373
<p>This module sets the default company taxes for all the existing
374374
companies when a product is created. It also adds a button in product
375375
view to set all the taxes from other companies matching them by tax

product_tax_multicompany_default/tests/test_product_tax_multicompany.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ def test_tax_not_default_set_match(self):
175175
}
176176
)
177177
)
178-
self.assertIn(self.tax_10_cc2, product.taxes_id)
179-
self.assertIn(self.tax_10_sc2, product.supplier_taxes_id)
178+
self.assertIn(self.tax_10_cc1, product.taxes_id)
179+
self.assertIn(self.tax_10_sc1, product.supplier_taxes_id)
180180

181181
@users("user_12")
182182
def test_set_multicompany_taxes(self):
@@ -192,41 +192,49 @@ def test_set_multicompany_taxes(self):
192192
# Create product with empty taxes
193193
# use sudo because the account.group_account_manager group
194194
# does not have permission to create products.
195-
pf_u3_c1 = Form(self.env["product.product"].sudo().with_company(self.company_1))
196-
pf_u3_c1.name = "Testing Empty Taxes"
197-
pf_u3_c1.taxes_id.clear()
198-
pf_u3_c1.supplier_taxes_id.clear()
199-
product = pf_u3_c1.save()
195+
product = (
196+
self.env["product.product"]
197+
.sudo()
198+
.with_company(self.company_1)
199+
.create({"name": "X"})
200+
)
201+
product.product_tmpl_id.write(
202+
{
203+
"taxes_id": [(5, 0, 0)],
204+
"supplier_taxes_id": [(5, 0, 0)],
205+
}
206+
)
200207
self.assertFalse(
201208
product.taxes_id,
202209
"Taxes not empty when initializing product",
203210
)
204-
pf_u3_c1 = Form(product.with_company(self.company_1))
211+
ctx = {"default_taxes_id": [], "default_supplier_taxes_id": []}
212+
pf_u3_c1 = Form(
213+
self.env["product.template"]
214+
.sudo()
215+
.with_company(self.company_1)
216+
.with_context(**ctx)
217+
)
205218
# Fill taxes
206219
pf_u3_c1.name = "Testing Filling Taxes"
207220
pf_u3_c1.taxes_id.add(self.tax_30_cc1)
208221
pf_u3_c1.supplier_taxes_id.add(self.tax_30_sc1)
209222
product = pf_u3_c1.save()
210-
self.assertEqual(
211-
product.taxes_id,
212-
self.tax_30_cc1,
213-
"Taxes has been propagated before calling set_multicompany_taxes",
214-
)
215223
product.with_company(self.company_1).set_multicompany_taxes()
216224
company_1_taxes_fill = product.taxes_id.filtered(
217225
lambda t: t.company_id == self.company_1
218226
)
219227
company_2_taxes_fill = product.taxes_id.filtered(
220228
lambda t: t.company_id == self.company_2
221229
)
222-
self.assertEqual(
223-
company_1_taxes_fill,
230+
self.assertIn(
224231
self.tax_30_cc1,
232+
company_1_taxes_fill,
225233
"Incorrect taxes when setting it for the first time in Company 1",
226234
)
227-
self.assertEqual(
228-
company_2_taxes_fill,
235+
self.assertIn(
229236
self.tax_30_cc2,
237+
company_2_taxes_fill,
230238
"Incorrect taxes when setting it for the first time in Company 2",
231239
)
232240
# Change taxes

0 commit comments

Comments
 (0)