diff --git a/docsource/modules130-140.rst b/docsource/modules130-140.rst
index a194b9a8efbc..a4ecbd07a409 100644
--- a/docsource/modules130-140.rst
+++ b/docsource/modules130-140.rst
@@ -760,7 +760,7 @@ Module coverage 13.0 -> 14.0
+--------------------------------------------+-------------------------------------------------+
| |del| website_rating | Nothing to do. Renamed to portal_rating |
+--------------------------------------------+-------------------------------------------------+
-|website_sale | |
+|website_sale | Done |
+--------------------------------------------+-------------------------------------------------+
|website_sale_comparison | |
+--------------------------------------------+-------------------------------------------------+
diff --git a/openupgrade_scripts/scripts/website_sale/14.0.1.0/noupdate_changes.xml b/openupgrade_scripts/scripts/website_sale/14.0.1.0/noupdate_changes.xml
index 52acf0700c79..8317b5b562e7 100644
--- a/openupgrade_scripts/scripts/website_sale/14.0.1.0/noupdate_changes.xml
+++ b/openupgrade_scripts/scripts/website_sale/14.0.1.0/noupdate_changes.xml
@@ -1,9 +1,9 @@
-
+
+
diff --git a/openupgrade_scripts/scripts/website_sale/14.0.1.0/post-migration.py b/openupgrade_scripts/scripts/website_sale/14.0.1.0/post-migration.py
new file mode 100644
index 000000000000..9ae016aeb654
--- /dev/null
+++ b/openupgrade_scripts/scripts/website_sale/14.0.1.0/post-migration.py
@@ -0,0 +1,59 @@
+# Copyright (C) 2021 Open Source Integrators
+# Copyright 2021 ForgeFlow S.L.
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from openupgradelib import openupgrade
+
+
+@openupgrade.migrate()
+def migrate(env, version):
+ # set simple ribbons (simple case):
+ openupgrade.logged_query(
+ env.cr,
+ """
+ UPDATE product_template pt
+ SET website_ribbon_id = sub.id
+ FROM (
+ SELECT min(rel.product_style_id) as id, rel.product_template_id
+ FROM product_style_product_template_rel rel
+ JOIN product_ribbon pr ON pr.id = rel.product_style_id
+ WHERE pr.html_class NOT IN ('oe_image_full', '')
+ GROUP BY rel.product_template_id
+ HAVING count(pr.id) = 1
+ ) as sub
+ WHERE sub.product_template_id = pt.id""",
+ )
+ # set new fused ribbons (complex case):
+ openupgrade.logged_query(
+ env.cr,
+ """
+ WITH sub AS (
+ SELECT STRING_AGG(pr.html, ' ' ORDER BY pr.html) as new_name,
+ STRING_AGG(pr.html_class, ' ' ORDER BY pr.html_class) as group_class,
+ rel.product_template_id
+ FROM product_style_product_template_rel rel
+ JOIN product_ribbon pr ON pr.id = rel.product_style_id
+ WHERE pr.html_class NOT IN ('oe_image_full', '')
+ GROUP BY rel.product_template_id
+ HAVING count(pr.id) > 1
+ ), new_ribbon AS (
+ INSERT INTO product_ribbon (html, html_class)
+ SELECT DISTINCT new_name, group_class
+ FROM sub
+ RETURNING id, html, html_class
+ )
+ UPDATE product_template pt
+ SET website_ribbon_id = pr.id
+ FROM sub
+ JOIN new_ribbon pr ON (
+ sub.new_name = pr.html AND sub.group_class = pr.html_class)
+ WHERE pt.id = sub.product_template_id AND pt.website_ribbon_id IS NULL""",
+ )
+ openupgrade.load_data(env.cr, "website_sale", "14.0.1.0/noupdate_changes.xml")
+ openupgrade.logged_query(
+ env.cr,
+ """
+ DELETE FROM ir_model_data
+ WHERE module = 'website_sale' and name = 'image_full'
+ """,
+ )
diff --git a/openupgrade_scripts/scripts/website_sale/14.0.1.0/pre-migration.py b/openupgrade_scripts/scripts/website_sale/14.0.1.0/pre-migration.py
new file mode 100644
index 000000000000..23f3274d4c06
--- /dev/null
+++ b/openupgrade_scripts/scripts/website_sale/14.0.1.0/pre-migration.py
@@ -0,0 +1,63 @@
+# Copyright (C) 2021 Open Source Integrators
+# Copyright 2021 ForgeFlow S.L.
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from openupgradelib import openupgrade
+
+_model_renames = [
+ ("product.style", "product.ribbon"),
+]
+
+_field_renames = [
+ ("product.ribbon", "product_ribbon", "name", "html"),
+]
+
+_table_renames = [
+ ("product_style", "product_ribbon"),
+]
+
+_xmlid_renames = [
+ ("website_sale.image_promo", "website_sale.sale_ribbon"),
+ ("website_sale.access_product_style", "website_sale.access_product_ribbon_public"),
+]
+
+
+def update_product_ribbon_sale_ribbon(env):
+ # it is noupdate data, not in noupdate_changes.xml
+ openupgrade.logged_query(
+ env.cr,
+ """
+ UPDATE product_ribbon pr
+ SET html_class = 'bg-success o_ribbon_left', html = 'Sale'
+ FROM ir_model_data imd
+ WHERE imd.model = 'product.ribbon' AND imd.res_id = pr.id
+ AND imd.module = 'website_sale' AND imd.name = 'sale_ribbon'""",
+ )
+
+
+def set_product_ribbon_html_class_default(env):
+ openupgrade.logged_query(
+ env.cr,
+ """
+ UPDATE product_ribbon
+ SET html_class = trim(both from html_class)
+ WHERE NULLIF(html_class, '') IS NOT NULL""",
+ )
+ openupgrade.logged_query(
+ env.cr,
+ """
+ UPDATE product_ribbon
+ SET html_class = ''
+ WHERE html_class IS NULL""",
+ )
+
+
+@openupgrade.migrate()
+def migrate(env, version):
+ openupgrade.rename_models(env.cr, _model_renames)
+ openupgrade.rename_tables(env.cr, _table_renames)
+ openupgrade.rename_fields(env, _field_renames)
+ openupgrade.rename_xmlids(env.cr, _xmlid_renames)
+ update_product_ribbon_sale_ribbon(env)
+ set_product_ribbon_html_class_default(env)
+ openupgrade.remove_tables_fks(env.cr, ["product_style_product_template_rel"])
diff --git a/openupgrade_scripts/scripts/website_sale/14.0.1.0/upgrade_analysis_work.txt b/openupgrade_scripts/scripts/website_sale/14.0.1.0/upgrade_analysis_work.txt
new file mode 100644
index 000000000000..ef4fe49cddde
--- /dev/null
+++ b/openupgrade_scripts/scripts/website_sale/14.0.1.0/upgrade_analysis_work.txt
@@ -0,0 +1,89 @@
+---Models in module 'website_sale'---
+obsolete model product.style (renamed to product.ribbon)
+new model product.ribbon (renamed from product.style)
+# DONE: pre-migration: renamed models and tables
+
+new model website.sale.extra.field
+# NOTHING TO DO
+
+---Fields in module 'website_sale'---
+website_sale / product.public.category / _order : _order is now 'sequence, name, id' ('sequence, name')
+website_sale / product.public.category / seo_name (char) : NEW
+# NOTHING TO DO
+
+website_sale / product.style / name (char) : DEL required
+website_sale / product.ribbon / html (char) : NEW required
+# DONE: pre-migration: renamed fields
+
+website_sale / product.style / html_class (char) : now required, req_default: function
+# DONE: pre-migration: set default '' in case is NULL
+
+website_sale / product.ribbon / bg_color (char) : NEW
+website_sale / product.ribbon / text_color (char) : NEW
+website_sale / product.template / seo_name (char) : NEW
+# NOTHING TO DO
+
+website_sale / product.template / website_ribbon_id (many2one) : NEW relation: product.ribbon
+website_sale / product.template / website_style_ids (many2many) : DEL relation: product.style
+# DONE: post-migration: if there is more than one valid website_style_ids, then create a fused ribbon from those styles
+
+website_sale / website / shop_extra_field_ids (one2many): NEW relation: website.sale.extra.field
+website_sale / website.sale.extra.field / field_id (many2one) : NEW relation: ir.model.fields
+website_sale / website.sale.extra.field / sequence (integer) : NEW hasdefault
+website_sale / website.sale.extra.field / website_id (many2one) : NEW relation: website
+# NOTHING TO DO: new features
+
+---XML records in module 'website_sale'---
+DEL ir.actions.act_window: website_sale.action_abandoned_orders_ecommerce
+NEW ir.actions.server: website_sale.dynamic_snippet_products_action
+NEW ir.model.access: website_sale.access_ecom_extra_fields_public
+NEW ir.model.access: website_sale.access_ecom_extra_fields_publisher
+NEW ir.model.access: website_sale.access_product_ribbon_sale_manager
+NEW ir.model.access: website_sale.access_website_sale_payment_acquirer_onboarding_wizard
+DEL ir.model.access: website_sale.access_product_supplierinfo
+DEL ir.model.access: website_sale.access_product_supplierinfo_user
+# NOTHING TO DO
+
+DEL ir.model.access: website_sale.access_product_style
+NEW ir.model.access: website_sale.access_product_ribbon_public
+# DONE: pre-migration: renamed xmlids
+
+DEL ir.rule: website_sale.product_supplierinfo_public (noupdate)
+NEW ir.ui.view: website_sale.assets_snippet_s_dynamic_snippet_products_js_000
+NEW ir.ui.view: website_sale.assets_snippet_s_products_searchbar_js_000
+NEW ir.ui.view: website_sale.ecom_show_extra_fields
+NEW ir.ui.view: website_sale.header_cart_link
+NEW ir.ui.view: website_sale.header_hide_empty_cart_link
+NEW ir.ui.view: website_sale.product_custom_text
+NEW ir.ui.view: website_sale.products_images_full
+NEW ir.ui.view: website_sale.s_dynamic_snippet_products
+NEW ir.ui.view: website_sale.s_dynamic_snippet_products_options
+NEW ir.ui.view: website_sale.searchbar_input_snippet_options
+NEW ir.ui.view: website_sale.template_header_boxed
+NEW ir.ui.view: website_sale.template_header_centered_logo
+NEW ir.ui.view: website_sale.template_header_contact
+NEW ir.ui.view: website_sale.template_header_default
+NEW ir.ui.view: website_sale.template_header_hamburger
+NEW ir.ui.view: website_sale.template_header_hamburger_full
+NEW ir.ui.view: website_sale.template_header_image
+NEW ir.ui.view: website_sale.template_header_magazine
+NEW ir.ui.view: website_sale.template_header_minimalist
+NEW ir.ui.view: website_sale.template_header_sidebar
+NEW ir.ui.view: website_sale.template_header_slogan
+NEW ir.ui.view: website_sale.template_header_vertical
+NEW ir.ui.view: website_sale.view_website_sale_website_form
+DEL ir.ui.view: website_sale.404
+DEL ir.ui.view: website_sale.header
+DEL ir.ui.view: website_sale.remove_external_snippets
+DEL ir.ui.view: website_sale.website_sale_pricelist_form_view2
+NEW product.ribbon: website_sale.new_ribbon (noupdate)
+NEW product.ribbon: website_sale.sold_out_ribbon (noupdate)
+NEW website.snippet.filter: website_sale.dynamic_filter_demo_products
+# NOTHING TO DO
+
+DEL product.style: website_sale.image_promo (noupdate)
+NEW product.ribbon: website_sale.sale_ribbon (noupdate)
+# DONE: pre-migration: renamed xmlid (and update values)
+
+DEL product.style: website_sale.image_full (noupdate)
+# DONE: post-migration: deleted safely