diff --git a/docsource/modules170-180.rst b/docsource/modules170-180.rst index 4fccf7559990..1030d1d13ca7 100644 --- a/docsource/modules170-180.rst +++ b/docsource/modules170-180.rst @@ -848,7 +848,7 @@ Module coverage 17.0 -> 18.0 +---------------------------------------------------+----------------------+-------------------------------------------------+ | privacy_lookup | |No DB layout changes. | +---------------------------------------------------+----------------------+-------------------------------------------------+ -| product | | | +| product |Done | | +---------------------------------------------------+----------------------+-------------------------------------------------+ | product_email_template | |No DB layout changes. | +---------------------------------------------------+----------------------+-------------------------------------------------+ diff --git a/openupgrade_scripts/scripts/product/18.0.1.2/post-migration.py b/openupgrade_scripts/scripts/product/18.0.1.2/post-migration.py new file mode 100644 index 000000000000..e5546520a72f --- /dev/null +++ b/openupgrade_scripts/scripts/product/18.0.1.2/post-migration.py @@ -0,0 +1,73 @@ +# Copyright 2025 Hunki Enterprises BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from openupgradelib import openupgrade, openupgrade_180 + + +def product_document_sequence(env): + """ + Set sequence matching previous name only ordering + """ + openupgrade.logged_query( + env.cr, + """ + UPDATE product_document + SET sequence=sequence.sequence + FROM + ( + SELECT + product_document.id, row_number() OVER ( + PARTITION BY ir_attachment.res_id, ir_attachment.res_model + ORDER BY ir_attachment.name + ) sequence + FROM + product_document + JOIN ir_attachment + ON product_document.ir_attachment_id=ir_attachment.id + ) sequence + WHERE + sequence.id=product_document.id + AND product_document.sequence IS NULL + """, + ) + + +def product_template_is_favorite(env): + """ + Set is_favorite flag based on priority + """ + openupgrade.logged_query( + env.cr, + """ + UPDATE + product_template + SET is_favorite = (priority IS NOT NULL AND priority != '0') + """, + ) + + +def res_partner_specific_property_product_pricelist(env): + """ + Get value for specific_property_product_pricelist from v17 + property_product_pricelist + """ + old_field = env.ref("product.field_res_partner__property_product_pricelist") + openupgrade_180.convert_company_dependent( + env, + "res.partner", + "specific_property_product_pricelist", + old_field_id=old_field.id, + ) + # convert_company_dependent might have created ir.default entries, wipe them + new_field = env.ref( + "product.field_res_partner__specific_property_product_pricelist" + ) + env["ir.default"].search([("field_id", "=", new_field.id)]).unlink() + + +@openupgrade.migrate() +def migrate(env, version): + product_document_sequence(env) + product_template_is_favorite(env) + res_partner_specific_property_product_pricelist(env) + openupgrade_180.convert_company_dependent(env, "product.product", "standard_price") diff --git a/openupgrade_scripts/scripts/product/18.0.1.2/pre-migration.py b/openupgrade_scripts/scripts/product/18.0.1.2/pre-migration.py new file mode 100644 index 000000000000..1b2873eb2075 --- /dev/null +++ b/openupgrade_scripts/scripts/product/18.0.1.2/pre-migration.py @@ -0,0 +1,73 @@ +# Copyright 2025 Hunki Enterprises BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from openupgradelib import openupgrade + +xmlid_renames = [ + ("product.group_discount_per_so_line", "sale.group_discount_per_so_line"), +] + +column_creates = [ + ("product.attribute", "active", "boolean", "TRUE"), + ("product.attribute.value", "active", "boolean", "TRUE"), + ("product.pricelist.item", "display_applied_on", "char"), + ("product.pricelist.item", "price_markup", "float"), +] + + +def rename_pos_models(env): + """ + pos.combo and pos.combo.line have been moved to product from point_of_sale and + renamed to product.combo and product.combo.item respectively + """ + if not openupgrade.table_exists(env.cr, "pos_combo"): + return + + openupgrade.rename_tables( + env.cr, + [ + ("pos_combo", "product_combo"), + ("pos_combo_line", "product_combo_item"), + ], + ) + openupgrade.rename_models( + env.cr, + [ + ("pos.combo", "product.combo"), + ("pos.combo.line", "product.combo.item"), + ], + ) + openupgrade.rename_fields( + env, + [ + ("product.combo", "product_combo", "combo_line_ids", "combo_item_ids"), + ("product.combo.item", "product_combo_item", "combo_price", "extra_price"), + ], + ) + + +def fill_product_pricelist_item_columns(env): + """ + Set display_applied_on to '2_product_category' if applied_on is + '2_product_category', else '1_product' + Set price_markup = -price_discount + """ + env.cr.execute( + """ + UPDATE product_pricelist_item + SET + display_applied_on=CASE + WHEN applied_on='2_product_category' THEN '2_product_category' + ELSE '1_product' + END, + price_markup=-price_discount + """ + ) + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.rename_xmlids(env.cr, xmlid_renames) + openupgrade.add_columns(env, column_creates) + fill_product_pricelist_item_columns(env) + rename_pos_models(env) diff --git a/openupgrade_scripts/scripts/product/18.0.1.2/upgrade_analysis_work.txt b/openupgrade_scripts/scripts/product/18.0.1.2/upgrade_analysis_work.txt new file mode 100644 index 000000000000..9a540b4da0fe --- /dev/null +++ b/openupgrade_scripts/scripts/product/18.0.1.2/upgrade_analysis_work.txt @@ -0,0 +1,106 @@ +---Models in module 'product'--- +new model product.combo +new model product.combo.item +# DONE: pre-migration: rename models and fields from point_of_sale models if installed + +new model update.product.attribute.value [transient] +# NOTHING TO DO: new functionality + +---Fields in module 'product'--- +product / product.attribute / active (boolean) : NEW hasdefault: default +# DONE: added in pre-migration + +product / product.attribute / template_value_ids (one2many) : NEW relation: product.template.attribute.value +# NOTHING TO DO + +product / product.attribute.value / active (boolean) : NEW hasdefault: default +# DONE: added in pre-migration + +product / product.category / message_follower_ids (one2many): NEW relation: mail.followers +product / product.category / message_ids (one2many) : NEW relation: mail.message +product / product.combo / combo_item_ids (one2many) : NEW relation: product.combo.item +product / product.combo / company_id (many2one) : NEW relation: res.company +product / product.combo / name (char) : NEW required +product / product.combo / sequence (integer) : NEW hasdefault: default +product / product.combo.item / combo_id (many2one) : NEW relation: product.combo, required +product / product.combo.item / company_id (many2one) : NEW relation: res.company, isrelated: related, stored +product / product.combo.item / extra_price (float) : NEW hasdefault: default +product / product.combo.item / product_id (many2one) : NEW relation: product.product, required +# NOTHING TO DO + +product / product.document / _order : _order is now 'sequence, name' ('name') +# DONE: see product.document#sequence below + +product / product.document / sequence (integer) : NEW hasdefault: default +# DONE: post-migration: create sequence matching name + +product / product.pricelist / _order : _order is now 'sequence, id, name' ('sequence asc, id asc') +# NOTHING TO DO: change doesn't actually affect ordering + +product / product.pricelist / discount_policy (selection) : DEL required, selection_keys: ['with_discount', 'without_discount'] +product / product.pricelist.item / display_applied_on (selection): NEW required, selection_keys: ['1_product', '2_product_category'], hasdefault: default +product / product.pricelist.item / price_markup (float) : NEW isfunction: function, stored +# DONE: added and filled in pre-migration + +product / product.product / _order : _order is now 'is_favorite desc, default_code, name, id' ('priority desc, default_code, name, id') +# NOTHING TO DO: see product.template#is_favorite below + +product / product.product / combo_ids (many2many) : previously in module point_of_sale +# DONE: see product.combo above + +product / product.product / service_tracking (selection) : previously in module sale_project +# NOTHING TO DO + +product / product.product / standard_price (float) : needs conversion to v18-style company dependent +# DONE: post-migration + +product / product.product / type (selection) : now required +# NOTHING TO DO: had a default in previous version already + +product / product.tag / _order : _order is now 'sequence, id' ('id') +product / product.tag / sequence (integer) : NEW hasdefault: default +product / product.template / _order : _order is now 'is_favorite desc, name' ('priority desc, name') +# NOTHING TO DO: ordering stays the same + +product / product.template / combo_ids (many2many) : previously in module point_of_sale +# NOTHING TO DO: see product.combo above + +product / product.template / detailed_type (selection) : DEL required, selection_keys: ['consu', 'service'] +# NOTHING TO DO, might need followup in subsequent addons + +product / product.template / is_favorite (boolean) : NEW +product / product.template / priority (selection) : DEL selection_keys: ['0', '1'] +# DONE: set is_favorite flag based on priority + +product / product.template / service_tracking (selection) : previously in module sale_project +product / product.template / type (selection) : now required +product / product.template / type (selection) : selection_keys is now '['combo', 'consu', 'service']' ('['consu', 'service']') +# NOTHING TO DO + +product / res.partner / specific_property_product_pricelist (many2one): NEW relation: product.pricelist +# DONE: post-migration: convert from v17 property res.partner#property_product_pricelist + +---XML records in module 'product'--- +NEW ir.actions.act_window: product.product_combo_action +NEW ir.model.access: product.access_product_combo_item_manager +NEW ir.model.access: product.access_product_combo_item_user +NEW ir.model.access: product.access_product_combo_manager +NEW ir.model.access: product.access_product_combo_user +NEW ir.model.access: product.access_update_product_attribute_value_manager +DEL ir.model.constraint: product.constraint_product_attribute_value_value_company_uniq +NEW ir.rule: product.product_combo_comp_rule (noupdate) +NEW ir.ui.view: product.product_attribute_search +NEW ir.ui.view: product.product_combo_view_form +NEW ir.ui.view: product.product_combo_view_tree +NEW ir.ui.view: product.product_packaging_search_view +NEW ir.ui.view: product.product_product_view_form_normalized +NEW ir.ui.view: product.product_template_attribute_line_view_tree +NEW ir.ui.view: product.update_product_attribute_value_form +NEW res.groups: product.group_product_manager +# NOTHING TO DO + +DEL res.groups: product.group_discount_per_so_line [renamed to sale module] +# DONE: pre-migration: moved xmlid to sale + +DEL res.groups: product.group_sale_pricelist +# NOTHING TO DO diff --git a/openupgrade_scripts/scripts/product/tests/data.py b/openupgrade_scripts/scripts/product/tests/data.py new file mode 100644 index 000000000000..4a09fd65a952 --- /dev/null +++ b/openupgrade_scripts/scripts/product/tests/data.py @@ -0,0 +1,15 @@ +env = locals().get("env") +# set specific pricelist +pricelist_main = env["product.pricelist"].create({"name": "Pricelist for main company"}) +company = env["res.company"].create({"name": "Product migration test company"}) +pricelist = ( + env["product.pricelist"] + .with_company(company) + .create({"name": "Pricelist for demo company"}) +) +partner = env.ref("base.user_demo").partner_id + +partner.property_product_pricelist = pricelist_main +partner.with_company(company).property_product_pricelist = pricelist + +env.cr.commit() diff --git a/openupgrade_scripts/scripts/product/tests/test_migration.py b/openupgrade_scripts/scripts/product/tests/test_migration.py new file mode 100644 index 000000000000..bcb2ce22f662 --- /dev/null +++ b/openupgrade_scripts/scripts/product/tests/test_migration.py @@ -0,0 +1,30 @@ +from odoo.tests import TransactionCase + +from odoo.addons.openupgrade_framework import openupgrade_test + + +@openupgrade_test +class TestProductMigration(TransactionCase): + def test_pricelist(self): + partner = self.env.ref("base.user_demo").partner_id + company = self.env["res.company"].search( + [ + ("name", "=", "Product migration test company"), + ] + ) + pricelist_main = self.env["product.pricelist"].search( + [ + ("name", "=", "Pricelist for main company"), + ] + ) + pricelist = self.env["product.pricelist"].search( + [ + ("name", "=", "Pricelist for demo company"), + ] + ) + self.assertTrue(company) + self.assertTrue(pricelist) + self.assertEqual(partner.property_product_pricelist, pricelist_main) + self.assertEqual( + partner.with_company(company).property_product_pricelist, pricelist + )