Skip to content

Commit 3672790

Browse files
committed
[MIG] product
1 parent efc2739 commit 3672790

File tree

6 files changed

+348
-1
lines changed

6 files changed

+348
-1
lines changed

docsource/modules180-190.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -908,7 +908,7 @@ Module coverage 18.0 -> 19.0
908908
+---------------------------------------------------+----------------------+-------------------------------------------------+
909909
| privacy_lookup | |No DB layout changes. |
910910
+---------------------------------------------------+----------------------+-------------------------------------------------+
911-
| product | | |
911+
| product |Done | |
912912
+---------------------------------------------------+----------------------+-------------------------------------------------+
913913
| product_email_template | |No DB layout changes. |
914914
+---------------------------------------------------+----------------------+-------------------------------------------------+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright 2025 Hunki Enterprises BV
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
from openupgradelib import openupgrade
5+
6+
7+
def _product_packaging(env):
8+
"""
9+
Create UOMs corresponding to the previous packaging's qty field,
10+
reuse existing UOMs
11+
"""
12+
env.cr.execute("SELECT id, name, qty FROM product_uom")
13+
for _id, name, qty in env.cr.fetchall():
14+
product_uom = env["product.uom"].browse(_id)
15+
uom = product_uom.product_id.uom_id
16+
17+
all_uoms = uom
18+
while all_uoms.related_uom_ids - all_uoms:
19+
all_uoms += all_uoms.related_uom_ids
20+
while all_uoms.relative_uom_id - all_uoms:
21+
all_uoms += all_uoms.relative_uom_id
22+
existing_uom = env["uom.uom"]
23+
for possible_uom in all_uoms:
24+
if uom._compute_quantity(qty, possible_uom) == 1:
25+
existing_uom = possible_uom
26+
break
27+
28+
if not existing_uom:
29+
existing_uom = env["uom.uom"].create(
30+
{
31+
"name": name,
32+
"relative_uom_id": uom.id,
33+
"relative_factor": qty,
34+
}
35+
)
36+
37+
product_uom.uom_id = existing_uom
38+
39+
40+
@openupgrade.migrate()
41+
def migrate(env, version):
42+
_product_packaging(env)
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Copyright 2025 Hunki Enterprises BV
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
from openupgradelib import openupgrade
5+
6+
7+
def _product_product_is_favorite(env):
8+
"""
9+
Precreate product.product#is_favorite and set with sql
10+
"""
11+
openupgrade.add_columns(
12+
env, [("product.product", "is_favorite", "boolean", False, "product_product")]
13+
)
14+
env.cr.execute(
15+
"""
16+
UPDATE product_product
17+
SET is_favorite=product_template.is_favorite
18+
FROM product_template
19+
WHERE
20+
product_product.product_tmpl_id=product_template.id
21+
AND product_template.is_favorite
22+
"""
23+
)
24+
25+
26+
def _product_supplierinfo_product_tmpl_id(env):
27+
"""
28+
Precreate product.supplierinfo#{product_tmpl_id,product_uom_id} and set with sql
29+
"""
30+
openupgrade.add_columns(
31+
env,
32+
[
33+
(
34+
"product.supplierinfo",
35+
"product_tmpl_id",
36+
"many2one",
37+
None,
38+
"product_supplierinfo",
39+
),
40+
(
41+
"product.supplierinfo",
42+
"product_uom_id",
43+
"many2one",
44+
None,
45+
"product_supplierinfo",
46+
),
47+
],
48+
)
49+
env.cr.execute(
50+
"""
51+
UPDATE product_supplierinfo
52+
SET product_tmpl_id=product_product.product_tmpl_id
53+
FROM product_product
54+
WHERE
55+
product_supplierinfo.product_id=product_product.id
56+
AND product_supplierinfo.product_tmpl_id IS NULL
57+
"""
58+
)
59+
env.cr.execute(
60+
"""
61+
UPDATE product_supplierinfo
62+
SET product_uom_id=product_template.uom_id
63+
FROM product_template
64+
WHERE
65+
product_supplierinfo.product_tmpl_id=product_template.id
66+
AND product_supplierinfo.product_uom_id IS NULL
67+
"""
68+
)
69+
70+
71+
def _prepare_product_packaging_migration(env):
72+
"""
73+
product.packaging is replaced by UOMs being pointed at by records of product.uom
74+
Rename product_packaging here to product_uom, and amend with data in post-migration
75+
Set uom_id to dummy UOM to have the ORM set up the not null constraint correctly
76+
"""
77+
openupgrade.rename_tables(env.cr, [("product_packaging", "product_uom")])
78+
openupgrade.rename_models(env.cr, [("product.packaging", "product.uom")])
79+
openupgrade.add_columns(
80+
env,
81+
[
82+
(
83+
"product.uom",
84+
"uom_id",
85+
"many2one",
86+
env.ref("uom.product_uom_unit").id,
87+
"product_uom",
88+
),
89+
],
90+
)
91+
92+
93+
@openupgrade.migrate()
94+
def migrate(env, version):
95+
openupgrade.delete_sql_constraint_safely(
96+
env,
97+
"product",
98+
"product_packaging",
99+
"positive_qty",
100+
)
101+
openupgrade.delete_records_safely_by_xml_id(
102+
env,
103+
[
104+
"product.product_packaging_comp_rule",
105+
],
106+
)
107+
openupgrade.rename_xmlids(
108+
env.cr,
109+
[
110+
("product.cat_expense", "product.product_category_expenses"),
111+
("product.product_category_all", "product.product_category_goods"),
112+
],
113+
)
114+
openupgrade.rename_fields(
115+
env,
116+
[
117+
("product.product", "product_product", "packaging_ids", "product_uom_ids"),
118+
],
119+
)
120+
_product_product_is_favorite(env)
121+
_product_supplierinfo_product_tmpl_id(env)
122+
_prepare_product_packaging_migration(env)
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---Models in module 'product'---
2+
obsolete model product.packaging (renamed to product.uom)
3+
new model product.uom (renamed from product.packaging)
4+
5+
# DONE: packagings are folded into UOMs, with product.uom linking products to the UOM that used to be a packaging
6+
7+
---Fields in module 'product'---
8+
product / product.attribute / display_type (selection) : selection_keys added: [image] (most likely nothing to do)
9+
10+
# NOTHING TO DO
11+
12+
product / product.packaging / _order : _order is now 'id' ('product_id, sequence, id')
13+
product / product.packaging / barcode (char) : now required
14+
product / product.packaging / name (char) : DEL required
15+
product / product.packaging / qty (float) : DEL
16+
product / product.packaging / sequence (integer) : DEL
17+
18+
# DONE: table is renamed to product_uom in pre-migration and matching UOMs created in post-migration
19+
20+
product / product.pricelist.item / company_id (many2one) : not related anymore
21+
product / product.pricelist.item / company_id (many2one) : now a function
22+
product / product.pricelist.item / currency_id (many2one) : not related anymore
23+
product / product.pricelist.item / currency_id (many2one) : now a function
24+
25+
# NOTHING TO DO: related field was stored before
26+
27+
product / product.product / _order : _order is now 'default_code, name, id' ('is_favorite desc, default_code, name, id')
28+
29+
# NOTHING TO DO
30+
31+
product / product.product / is_favorite (boolean) : is now stored
32+
33+
# DONE: pre-created in pre-migration and filled from product_template
34+
35+
product / product.product / is_in_selected_section_of_order (boolean): NEW
36+
37+
# NOTHING TO DO: virtual field only used for searching
38+
39+
product / product.product / packaging_ids (one2many) : DEL relation: product.packaging
40+
product / product.product / product_uom_ids (one2many) : NEW relation: product.uom
41+
42+
# DONE: renamed in pre-migration, also see above at product.packaging
43+
44+
product / product.supplierinfo / product_tmpl_id (many2one) : now required
45+
product / product.supplierinfo / product_uom_id (many2one) : NEW relation: uom.uom, required, hasdefault: compute
46+
47+
# DONE: filled from product_id
48+
49+
product / product.tag / image (binary) : previously in module website_sale
50+
51+
# NOTHING TO DO
52+
53+
product / product.tag / visible_to_customers (boolean): NEW hasdefault: default
54+
55+
# NOTHING TO DO
56+
57+
product / product.template / pricelist_rule_ids (one2many) : NEW relation: product.pricelist.item
58+
59+
# NOTHING TO DO
60+
61+
product / product.template / uom_ids (many2many) : NEW relation: uom.uom
62+
product / product.template / uom_po_id (many2one) : DEL relation: uom.uom, required
63+
64+
# NOTHING TO DO
65+
66+
product / product.uom / uom_id (many2one) : NEW relation: uom.uom, required
67+
product / uom.uom / product_uom_ids (one2many) : NEW relation: product.uom
68+
69+
# DONE: see above at product.packaging
70+
71+
---XML records in module 'product'---
72+
DEL decimal.precision: product.decimal_product_uom [renamed to uom module] (noupdate)
73+
74+
# DONE: in uom migration
75+
76+
DEL ir.actions.act_window: product.action_packaging_view
77+
NEW ir.actions.server: product.action_product_print_labels
78+
NEW ir.actions.server: product.action_product_template_print_labels
79+
NEW ir.model.access: product.access_product_document_manager
80+
NEW ir.model.access: product.access_product_uom_manager
81+
NEW ir.model.access: product.access_product_uom_user
82+
NEW ir.model.access: product.access_uom_uom_product_manager
83+
DEL ir.model.access: product.access_product_packaging_manager
84+
DEL ir.model.access: product.access_product_packaging_user
85+
86+
# NOTHING TO DO
87+
88+
NEW ir.model.constraint: product.constraint_product_product_combination_unique
89+
NEW ir.model.constraint: product.constraint_product_product_is_favorite_index
90+
NEW ir.model.constraint: product.constraint_product_template_is_favorite_index
91+
NEW ir.model.constraint: product.constraint_product_uom_barcode_uniq
92+
DEL ir.model.constraint: product.constraint_product_packaging_barcode_uniq
93+
94+
# NOTHING TO DO
95+
96+
DEL ir.model.constraint: product.constraint_product_packaging_positive_qty
97+
DEL ir.rule: product.product_packaging_comp_rule (noupdate)
98+
99+
# DONE: deleted in pre-migration
100+
101+
NEW ir.ui.view: product.product_pricelist_item_product_product_form_view
102+
NEW ir.ui.view: product.product_pricelist_item_product_template_form_view
103+
NEW ir.ui.view: product.product_template_list_view_purchasable
104+
NEW ir.ui.view: product.product_template_list_view_sellable
105+
NEW ir.ui.view: product.product_uom_list_view
106+
NEW ir.ui.view: product.uom_uom_form_view_inherit
107+
DEL ir.ui.view: product.product_packaging_form_view
108+
DEL ir.ui.view: product.product_packaging_form_view2
109+
DEL ir.ui.view: product.product_packaging_search_view
110+
DEL ir.ui.view: product.product_packaging_tree_view
111+
DEL ir.ui.view: product.product_packaging_tree_view2
112+
113+
# NOTHING TO DO
114+
115+
NEW product.category: product.product_category_expenses (noupdate)
116+
NEW product.category: product.product_category_goods (noupdate)
117+
NEW product.category: product.product_category_services (noupdate)
118+
DEL product.category: product.cat_expense (noupdate)
119+
DEL product.category: product.product_category_1 (noupdate)
120+
DEL product.category: product.product_category_all (noupdate)
121+
122+
# DONE: renamed in pre-migration
123+
124+
DEL res.groups: product.group_stock_packaging
125+
NEW res.groups.privilege: product.res_groups_privilege_product
126+
127+
# NOTHING TO DO
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
env = locals().get("env")
2+
# packaging that should be assigned existing UOM
3+
product = env.ref("product.product_delivery_01")
4+
packaging = env["product.packaging"].create(
5+
{
6+
"name": "Sixpack",
7+
"barcode": "42 42",
8+
"product_id": product.id,
9+
"qty": 6,
10+
}
11+
)
12+
# packaging that should have a new UOM created
13+
product = env.ref("product.product_delivery_01")
14+
packaging = env["product.packaging"].create(
15+
{
16+
"name": "Thirteenpack",
17+
"barcode": "42 43",
18+
"product_id": product.id,
19+
"qty": 13,
20+
}
21+
)
22+
# packaging that should reuse UOM created for the one above
23+
product = env.ref("product.product_delivery_02")
24+
packaging = env["product.packaging"].create(
25+
{
26+
"name": "Thirteenpack",
27+
"barcode": "42 44",
28+
"product_id": product.id,
29+
"qty": 13,
30+
}
31+
)
32+
env.cr.commit()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from odoo.tests import TransactionCase
2+
3+
from odoo.addons.openupgrade_framework import openupgrade_test
4+
5+
6+
@openupgrade_test
7+
class TestProductMigration(TransactionCase):
8+
def test_product_uom(self):
9+
"""
10+
Test that packages have been correctly migrated to uoms
11+
"""
12+
product = self.env.ref("product.product_delivery_01")
13+
self.assertIn(
14+
self.env.ref("uom.product_uom_pack_6"), product.product_uom_ids.uom_id
15+
)
16+
new_uom = product.product_uom_ids.uom_id - self.env.ref(
17+
"uom.product_uom_pack_6"
18+
)
19+
self.assertEqual(new_uom.relative_uom_id, self.env.ref("uom.product_uom_unit"))
20+
self.assertEqual(new_uom.relative_factor, 13)
21+
self.assertEqual(
22+
self.env.ref("product.product_delivery_02").product_uom_ids.uom_id,
23+
new_uom,
24+
)

0 commit comments

Comments
 (0)