diff --git a/addons/pos_sale_product_configurator/__init__.py b/addons/pos_sale_product_configurator/__init__.py
new file mode 100644
index 00000000000..dc5e6b693d1
--- /dev/null
+++ b/addons/pos_sale_product_configurator/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import models
diff --git a/addons/pos_sale_product_configurator/__manifest__.py b/addons/pos_sale_product_configurator/__manifest__.py
new file mode 100644
index 00000000000..d17197c0593
--- /dev/null
+++ b/addons/pos_sale_product_configurator/__manifest__.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+
+{
+ 'name': 'pos_sale_product_configurator',
+ 'version': '1.0',
+ 'category': 'Hidden',
+ 'summary': 'Link module between point_of_sale and sale_product_configurator',
+ 'description': """
+This module adds features depending on both modules.
+""",
+ 'depends': ['point_of_sale', 'sale_product_configurator'],
+ 'installable': True,
+ 'auto_install': True,
+ 'data': [
+ 'views/pos_config_views.xml',
+ ],
+ 'assets': {
+ 'point_of_sale.assets': [
+ 'pos_sale_product_configurator/static/src/js/models.js',
+ 'pos_sale_product_configurator/static/src/css/popups/product_info_popup.css',
+ ],
+ 'web.assets_qweb': [
+ 'pos_sale_product_configurator/static/src/xml/**/*'
+ ]
+ },
+ 'license': 'LGPL-3',
+}
diff --git a/addons/pos_sale_product_configurator/models/__init__.py b/addons/pos_sale_product_configurator/models/__init__.py
new file mode 100644
index 00000000000..8c7aba25471
--- /dev/null
+++ b/addons/pos_sale_product_configurator/models/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import pos_config
+from . import product
diff --git a/addons/pos_sale_product_configurator/models/pos_config.py b/addons/pos_sale_product_configurator/models/pos_config.py
new file mode 100644
index 00000000000..a5ffc90637a
--- /dev/null
+++ b/addons/pos_sale_product_configurator/models/pos_config.py
@@ -0,0 +1,7 @@
+from odoo import models, fields
+
+
+class PosConfig(models.Model):
+ _inherit = 'pos.config'
+
+ iface_open_product_info = fields.Boolean(string="Open Product Info", help="Display the 'Product Info' page when a product with optional products are added in the customer cart")
diff --git a/addons/pos_sale_product_configurator/models/product.py b/addons/pos_sale_product_configurator/models/product.py
new file mode 100644
index 00000000000..c810fcf92cd
--- /dev/null
+++ b/addons/pos_sale_product_configurator/models/product.py
@@ -0,0 +1,26 @@
+from odoo import models
+
+
+class ProductProduct(models.Model):
+ _inherit = 'product.product'
+
+ def get_product_info_pos(self, price, quantity, pos_config_id):
+ res = super().get_product_info_pos(price, quantity, pos_config_id)
+
+ # Optional products
+ res['optional_products'] = [
+ {'name': p.name, 'price': min(p.product_variant_ids.mapped('lst_price'))}
+ for p in self.optional_product_ids.filtered_domain(self._optional_product_pos_domain())
+ ]
+
+ return res
+
+ def has_optional_product_in_pos(self):
+ self.ensure_one()
+ return bool(self.optional_product_ids.filtered_domain(self._optional_product_pos_domain()))
+
+ def _optional_product_pos_domain(self):
+ return [
+ '&', '&', ['sale_ok', '=', True], ['available_in_pos', '=', True],
+ '|', ['company_id', '=', self.env.company], ['company_id', '=', False]
+ ]
diff --git a/addons/pos_sale_product_configurator/static/src/css/popups/product_info_popup.css b/addons/pos_sale_product_configurator/static/src/css/popups/product_info_popup.css
new file mode 100644
index 00000000000..c59e17c8a0b
--- /dev/null
+++ b/addons/pos_sale_product_configurator/static/src/css/popups/product_info_popup.css
@@ -0,0 +1,16 @@
+.product-info-popup .extra {
+ display: flex;
+}
+
+.product-info-popup .extra div {
+ width: 100%;
+}
+
+@media screen and (max-width: 768px) {
+ .product-info-popup .extra{
+ flex-direction: column;
+ }
+ .product-info-popup .section-optional-product-body td {
+ width: 50%;
+ }
+}
diff --git a/addons/pos_sale_product_configurator/static/src/js/models.js b/addons/pos_sale_product_configurator/static/src/js/models.js
new file mode 100644
index 00000000000..05cc5855406
--- /dev/null
+++ b/addons/pos_sale_product_configurator/static/src/js/models.js
@@ -0,0 +1,31 @@
+odoo.define('pos_sale_product_configurator.models', function (require) {
+ "use strict";
+
+ const { Gui } = require('point_of_sale.Gui');
+ var models = require('point_of_sale.models');
+
+ models.load_fields("product.product", ["optional_product_ids"]);
+ models.load_fields("pos.config", ["iface_open_product_info"]);
+
+ const super_order_model = models.Order.prototype;
+ models.Order = models.Order.extend({
+ async add_product(product, options) {
+ super_order_model.add_product.apply(this, arguments);
+ if (this.pos.config.iface_open_product_info && product.optional_product_ids.length) {
+ // The `optional_product_ids` only contains ids of the product templates and not the product itself
+ // We don't load all the product template in the pos, so it'll be hard to know if the id comes from
+ // a product available in POS. We send a quick cal to the back end to verify.
+ const isProductLoaded = await this.pos.rpc(
+ {
+ model: 'product.product',
+ method: 'has_optional_product_in_pos',
+ args: [[product.id]]
+ }
+ );
+ if (isProductLoaded) {
+ Gui.showPopup('ProductInfoPopup', {product, quantity: this.get_selected_orderline().get_quantity()});
+ }
+ }
+ }
+ })
+})
diff --git a/addons/pos_sale_product_configurator/static/src/xml/Popups/ProductInfoPopup.xml b/addons/pos_sale_product_configurator/static/src/xml/Popups/ProductInfoPopup.xml
new file mode 100644
index 00000000000..e6b7ab3f419
--- /dev/null
+++ b/addons/pos_sale_product_configurator/static/src/xml/Popups/ProductInfoPopup.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/addons/pos_sale_product_configurator/views/pos_config_views.xml b/addons/pos_sale_product_configurator/views/pos_config_views.xml
new file mode 100644
index 00000000000..e69fc8765c9
--- /dev/null
+++ b/addons/pos_sale_product_configurator/views/pos_config_views.xml
@@ -0,0 +1,23 @@
+
+
+
+ pos.config.form.view.inherit.pos.sale.product.configuration
+ pos.config
+
+
+
+
+
+