diff --git a/addons/point_of_sale/__manifest__.py b/addons/point_of_sale/__manifest__.py
index 351ca106e87..c275471a0c9 100644
--- a/addons/point_of_sale/__manifest__.py
+++ b/addons/point_of_sale/__manifest__.py
@@ -23,6 +23,7 @@
'views/point_of_sale_view.xml',
'views/pos_order_view.xml',
'views/pos_category_view.xml',
+ 'views/pos_combo_view.xml',
'views/product_view.xml',
'views/account_journal_view.xml',
'views/pos_payment_method_views.xml',
diff --git a/addons/point_of_sale/models/__init__.py b/addons/point_of_sale/models/__init__.py
index c71d05f2c84..676f237afe6 100644
--- a/addons/point_of_sale/models/__init__.py
+++ b/addons/point_of_sale/models/__init__.py
@@ -13,6 +13,8 @@ from . import pos_category
from . import pos_config
from . import pos_order
from . import pos_session
+from . import pos_combo
+from . import pos_combo_line
from . import product
from . import res_partner
from . import res_company
diff --git a/addons/point_of_sale/models/pos_combo.py b/addons/point_of_sale/models/pos_combo.py
new file mode 100644
index 00000000000..74e627c4e24
--- /dev/null
+++ b/addons/point_of_sale/models/pos_combo.py
@@ -0,0 +1,25 @@
+from odoo import api, fields, models, _
+from odoo.exceptions import ValidationError
+
+class PosCombo(models.Model):
+ """
+ This model is used to allow the pos user to create menus.
+ This means that products can be grouped together and sold as a combo.
+ """
+ _name = "pos.combo"
+ _description = "Product combo choices"
+
+ name = fields.Char(string="Name", required=True)
+ combo_line_ids = fields.One2many("pos.combo.line", "combo_id", string="Products in Combo")
+ num_of_products = fields.Integer("No of Products", compute="_compute_num_of_products")
+
+ @api.depends("combo_line_ids")
+ def _compute_num_of_products(self):
+ # optimization trick to count the number of products in each combo
+ for rec, num_of_products in self.env["pos.combo.line"]._read_group([("combo_id", "in", self.ids)], groupby=["combo_id"], aggregates=["__count"]):
+ rec.num_of_products = num_of_products
+
+ @api.constrains("combo_line_ids")
+ def _check_combo_line_ids_is_not_null(self):
+ if any(not rec.combo_line_ids for rec in self):
+ raise ValidationError(_("Please add products in combo."))
diff --git a/addons/point_of_sale/models/pos_combo_line.py b/addons/point_of_sale/models/pos_combo_line.py
new file mode 100644
index 00000000000..6293b2a427a
--- /dev/null
+++ b/addons/point_of_sale/models/pos_combo_line.py
@@ -0,0 +1,11 @@
+from odoo import fields, models
+
+
+class PosComboLine(models.Model):
+ _name = "pos.combo.line"
+ _description = "Product Combo Items"
+
+ product_id = fields.Many2one("product.product", string="Product")
+ price = fields.Float("Price Extra", default=0.0)
+ lst_price = fields.Float("Original Price", related="product_id.lst_price")
+ combo_id = fields.Many2one("pos.combo")
diff --git a/addons/point_of_sale/models/product.py b/addons/point_of_sale/models/product.py
index 99f3b0e3869..d694bec80f2 100644
--- a/addons/point_of_sale/models/product.py
+++ b/addons/point_of_sale/models/product.py
@@ -15,6 +15,7 @@ class ProductTemplate(models.Model):
pos_categ_ids = fields.Many2many(
'pos.category', string='Point of Sale Category',
help="Category used in the Point of Sale.")
+ combo_ids = fields.Many2many('pos.combo', string='Combinations')
@api.ondelete(at_uninstall=False)
def _unlink_except_open_session(self):
diff --git a/addons/point_of_sale/security/ir.model.access.csv b/addons/point_of_sale/security/ir.model.access.csv
index 41b5594da75..9dbd50ab155 100644
--- a/addons/point_of_sale/security/ir.model.access.csv
+++ b/addons/point_of_sale/security/ir.model.access.csv
@@ -47,6 +47,10 @@ access_decimal_precision_user,decimal.precision,base.model_decimal_precision,gro
access_pos_payment_user,pos.payment user,model_pos_payment,group_pos_user,1,1,1,1
access_pos_payment_method_user,pos.payment.method user,model_pos_payment_method,group_pos_user,1,0,0,0
access_pos_payment_method_manager,pos.payment.method manager,model_pos_payment_method,group_pos_manager,1,1,1,1
+access_pos_combo_manager,pos.combo manager,model_pos_combo,group_pos_manager,1,1,1,1
+access_pos_combo_user,pos.combo user,model_pos_combo,group_pos_user,1,0,0,0
+access_pos_combo_line_manager,pos.combo.line manager,model_pos_combo_line,group_pos_manager,1,1,1,1
+access_pos_combo_line_user,pos.combo.line user,model_pos_combo_line,group_pos_user,1,0,0,0
access_pos_details_wizard,access.pos.details.wizard,model_pos_details_wizard,point_of_sale.group_pos_manager,1,1,1,0
access_pos_make_payment,access.pos.make.payment,model_pos_make_payment,point_of_sale.group_pos_manager,1,1,1,0
access_pos_close_session_wizard,access.pos.close.session.wizard,model_pos_close_session_wizard,point_of_sale.group_pos_user,1,1,1,0
diff --git a/addons/point_of_sale/views/pos_combo_view.xml b/addons/point_of_sale/views/pos_combo_view.xml
new file mode 100644
index 00000000000..386e3325b42
--- /dev/null
+++ b/addons/point_of_sale/views/pos_combo_view.xml
@@ -0,0 +1,41 @@
+
+
+
+ pos.combo.form
+ pos.combo
+
+
+
+
+
+
+ pos.combo.tree
+ pos.combo
+
+
+
+
+
+
+
+
+
+ Combo Choices
+ pos.combo
+ tree,form
+
+
diff --git a/addons/point_of_sale/views/product_view.xml b/addons/point_of_sale/views/product_view.xml
index 66003b96b5a..e63cd4bea57 100644
--- a/addons/point_of_sale/views/product_view.xml
+++ b/addons/point_of_sale/views/product_view.xml
@@ -69,6 +69,7 @@
+
@@ -88,6 +89,11 @@
action="product_product_action"
groups="product.group_product_variant"
sequence="10"/>
+