diff --git a/pos_combinated_drinks/README.md b/pos_combinated_drinks/README.md
new file mode 100644
index 00000000..5b2d3dbc
--- /dev/null
+++ b/pos_combinated_drinks/README.md
@@ -0,0 +1 @@
+# pos_combinated_drinks
diff --git a/pos_combinated_drinks/__init__.py b/pos_combinated_drinks/__init__.py
new file mode 100755
index 00000000..e9f7639c
--- /dev/null
+++ b/pos_combinated_drinks/__init__.py
@@ -0,0 +1,7 @@
+# Copyright 2019 Xtendoo (http://www.xtendoo.es)
+# Copyright 2019 Manuel Calero Solís
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from .import models
+
+
diff --git a/pos_combinated_drinks/__manifest__.py b/pos_combinated_drinks/__manifest__.py
new file mode 100755
index 00000000..89cb3651
--- /dev/null
+++ b/pos_combinated_drinks/__manifest__.py
@@ -0,0 +1,23 @@
+# Copyright 2019 Xtendoo (http://www.xtendoo.es)
+# Copyright 2019 Manuel Calero Solís
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+{
+ 'name': 'Pos Combinated Drinks',
+ 'version': '13.0.1.0.0',
+ 'category': 'Point of Sale',
+ 'author': 'Xtendoo',
+ 'website': 'https://www.xtendoo.es',
+ 'license': 'AGPL-3',
+ 'summary': 'This module allows to use combined drinks in a bar or restaurant',
+ 'version': '1.0.1',
+ 'depends': ['base', 'pos_restaurant'],
+ "data": [
+ 'security/ir.model.access.csv',
+ 'views/point_of_sale.xml',
+ 'views/pos_combined_drinks.xml',
+ ],
+ 'qweb': ['static/src/xml/pos.xml'],
+ 'installable': True,
+ 'auto_install': False,
+}
diff --git a/pos_combinated_drinks/i18n/es.po b/pos_combinated_drinks/i18n/es.po
new file mode 100755
index 00000000..dead5dab
--- /dev/null
+++ b/pos_combinated_drinks/i18n/es.po
@@ -0,0 +1,154 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * pos_combinated_drinks
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 13.0-20191025\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-12-12 22:26+0000\n"
+"PO-Revision-Date: 2019-12-12 22:26+0000\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: pos_combinated_drinks
+#. openerp-web
+#: code:addons/pos_combinated_drinks/static/src/xml/pos.xml:0
+#, python-format
+msgid "% discount"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#. openerp-web
+#: code:addons/pos_combinated_drinks/static/src/xml/pos.xml:0
+#, python-format
+msgid "Cancel"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#: model_terms:ir.ui.view,arch_db:pos_combinated_drinks.package_product_template_only_form_view
+msgid "Categories"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#. openerp-web
+#: code:addons/pos_combinated_drinks/static/src/xml/pos.xml:0
+#, python-format
+msgid "Combined product"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#: model_terms:ir.ui.view,arch_db:pos_combinated_drinks.package_product_template_only_form_view
+msgid "Combo"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_product__combo_price
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_template__combo_price
+msgid "Combo price"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_combo__create_uid
+msgid "Created by"
+msgstr "Creado por"
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_combo__create_date
+msgid "Created on"
+msgstr "Creado el"
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_combo__display_name
+msgid "Display Name"
+msgstr "Nombre mostrado"
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_combo__id
+msgid "ID"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_pos_order_line__invisible
+msgid "Invisible Line"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_product__is_combo
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_template__is_combo
+msgid "Is Combo"
+msgstr "Combinable"
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_combo____last_update
+msgid "Last Modified on"
+msgstr "Última modificación en"
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_combo__write_uid
+msgid "Last Updated by"
+msgstr "Última actualización por"
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_combo__write_date
+msgid "Last Updated on"
+msgstr "Última actualización el"
+
+#. module: pos_combinated_drinks
+#. openerp-web
+#: code:addons/pos_combinated_drinks/static/src/xml/pos.xml:0
+#: code:addons/pos_combinated_drinks/static/src/xml/pos.xml:0
+#, python-format
+msgid "NOTE"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#: model:ir.model,name:pos_combinated_drinks.model_pos_order_line
+msgid "Point of Sale Order Lines"
+msgstr "Líneas de Orden de Punto de Venta"
+
+#. module: pos_combinated_drinks
+#: model:ir.model,name:pos_combinated_drinks.model_pos_order
+msgid "Point of Sale Orders"
+msgstr "Pedidos del TPV"
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_product__combo_category_ids
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_template__combo_category_ids
+msgid "Pos Categories"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#: model:ir.model,name:pos_combinated_drinks.model_product_combo
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_product__product_combo_ids
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_template__product_combo_ids
+msgid "Product Combo"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#: model:ir.model,name:pos_combinated_drinks.model_product_template
+msgid "Product Template"
+msgstr "Plantilla de producto"
+
+#. module: pos_combinated_drinks
+#: model:ir.model.fields,field_description:pos_combinated_drinks.field_product_combo__product_tmpl_id
+msgid "Product Tmpl"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#. openerp-web
+#: code:addons/pos_combinated_drinks/static/src/xml/pos.xml:0
+#, python-format
+msgid "Quantity"
+msgstr ""
+
+#. module: pos_combinated_drinks
+#. openerp-web
+#: code:addons/pos_combinated_drinks/static/src/xml/pos.xml:0
+#, python-format
+msgid "With a"
+msgstr ""
diff --git a/pos_combinated_drinks/models/__init__.py b/pos_combinated_drinks/models/__init__.py
new file mode 100755
index 00000000..7d3d4115
--- /dev/null
+++ b/pos_combinated_drinks/models/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+from .import point_of_sale
+from .import product
+
diff --git a/pos_combinated_drinks/models/point_of_sale.py b/pos_combinated_drinks/models/point_of_sale.py
new file mode 100755
index 00000000..7e424b3b
--- /dev/null
+++ b/pos_combinated_drinks/models/point_of_sale.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+from odoo import models, fields, api, _
+
+
+class PosOrderLine(models.Model):
+ _inherit = "pos.order.line"
+
+ invisible = fields.Boolean('Invisible Line', default=False)
+
diff --git a/pos_combinated_drinks/models/product.py b/pos_combinated_drinks/models/product.py
new file mode 100755
index 00000000..78849400
--- /dev/null
+++ b/pos_combinated_drinks/models/product.py
@@ -0,0 +1,21 @@
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+from odoo import models, fields, api, _
+
+
+class ProductTemplate(models.Model):
+ _inherit = "product.template"
+
+ is_combo = fields.Boolean("Is Combo")
+ combo_price = fields.Float('Combo price', company_dependent=True, digits='Product Price', groups="base.group_user")
+ combo_category_ids = fields.Many2many('pos.category', string='Pos Categories')
+
+ product_combo_ids = fields.One2many('product.combo', 'product_tmpl_id')
+
+
+class ProductCombo(models.Model):
+ _name = 'product.combo'
+ _description = 'Product Combo'
+
+ product_tmpl_id = fields.Many2one('product.template')
+
+
diff --git a/pos_combinated_drinks/security/ir.model.access.csv b/pos_combinated_drinks/security/ir.model.access.csv
new file mode 100755
index 00000000..3aa91fd1
--- /dev/null
+++ b/pos_combinated_drinks/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_product_combo,access_product_combo,model_product_combo,point_of_sale.group_pos_user,1,1,1,1
\ No newline at end of file
diff --git a/pos_combinated_drinks/static/description/icon.png b/pos_combinated_drinks/static/description/icon.png
new file mode 100755
index 00000000..3a0328b5
Binary files /dev/null and b/pos_combinated_drinks/static/description/icon.png differ
diff --git a/pos_combinated_drinks/static/src/css/pos.css b/pos_combinated_drinks/static/src/css/pos.css
new file mode 100755
index 00000000..4f575ff0
--- /dev/null
+++ b/pos_combinated_drinks/static/src/css/pos.css
@@ -0,0 +1,133 @@
+.product_pack{
+ background: green;
+ position: absolute;
+ top: 69px;
+ vertical-align: top;
+ color: white;
+ line-height: 13px;
+ padding: 2px 5px;
+ border-radius: 2px;
+ opacity: 0.6;
+ width: 100%;
+}
+.pos_combo_product_popup .title{
+ margin-bottom: 8px !important;
+}
+.pos_combo_product_popup .combo_product_header, .combo_product_header2{
+ text-align: left;
+ padding-left: 8px;
+ font-size: 16px;
+ height: 30px;
+ line-height: 30px;
+ background: #c4c4c4;
+ border-top: 3px solid #f0eeee;
+}
+.pos_combo_product_popup .combo_header_body{
+ text-align: left;
+ overflow-y: hidden !important;
+ white-space: nowrap;
+ height: 112px;
+ overflow-x: auto !important;
+ margin-left: 15px;
+}
+.pos_combo_product_popup .combo_header2_body{
+ text-align: left;
+ margin-left: 10px;
+ margin-right: 7px;
+ height: 454px;
+ overflow: auto;
+}
+.product.product_content{
+ width: 100px !important;
+ height: 80px !important;
+}
+.product.product_content .product-img{
+ width: 102px !important;
+ height: 80px !important;
+}
+.product.product_content.selected {
+ border: 2px solid cadetblue;
+}
+.product.product_content.selected .selected_product{
+ background: green;
+ position: absolute;
+ top: 40px;
+ vertical-align: top;
+ color: white;
+ line-height: 13px;
+ padding: 2px 5px;
+ border-radius: 2px;
+ opacity: 0.6;
+ width: 100%;
+}
+.pos_combo_product_popup{
+ height: 600px !important;
+ width: 630px !important;
+}
+.collaps_div{
+ float: right;
+ margin-right: 7px;
+ background: #555;
+ height: 20px;
+ margin-top: 5px;
+ color: #fff;
+ display: flex;
+ width: 20px;
+ justify-content: center;
+ border-radius: 50%;
+ font-weight: bold;
+ font-size: larger;
+ cursor:pointer;
+}
+.product.product_content .product-qty{
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ vertical-align: top;
+ color: white;
+ line-height: 13px;
+ background: #7f82ac;
+ padding: 2px 5px;
+}
+.product.product_content .product-remove{
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ vertical-align: top;
+ line-height: 13px;
+ padding: 0px 5px;
+ font-size: 24px;
+ background: #c4c4c4;
+}
+.categ_tile{
+ border: 1px solid #c4c4c4;
+ width: 99%;
+ margin-top: 5px;
+}
+.categ_tile div.categ_name{
+ font-size: 14px;
+ padding: 4px 0px;
+ background: #c4c4c4;
+ height: 18px;
+}
+.categ_tile div.categ_name div{
+ float: left;
+ width: 50%;
+}
+.blinking{
+ animation:blinkingText 2s infinite;
+}
+@keyframes blinkingText{
+ 50% {
+ opacity: 0;
+ }
+}
+.products_list{
+ overflow-y: hidden !important;
+ white-space: nowrap;
+ overflow-x: auto !important;
+ margin-left: 5px;
+}
+.hide{
+ display:none;
+}
\ No newline at end of file
diff --git a/pos_combinated_drinks/static/src/js/pos.js b/pos_combinated_drinks/static/src/js/pos.js
new file mode 100755
index 00000000..0dab09e5
--- /dev/null
+++ b/pos_combinated_drinks/static/src/js/pos.js
@@ -0,0 +1,175 @@
+odoo.define('pos_combinated_drinks.pos', function (require) {
+"use strict";
+
+ let PopupWidget = require('point_of_sale.popups');
+ let models = require('point_of_sale.models');
+ let gui = require('point_of_sale.gui');
+ let screens = require('point_of_sale.screens');
+
+ models.load_fields("product.product", ['is_combo', 'combo_price', 'product_combo_ids', 'combo_category_ids']);
+ models.load_fields("pos.order.line", ['invisible']);
+
+ let _super_Order = models.Order.prototype;
+ models.Order = models.Order.extend({
+ add_product: function(product, options){
+ let self = this;
+ if(product.is_combo && product.combo_category_ids.length > 0){
+
+// if (product['combo_price'] != 0){
+// options = {'price': product['combo_price']}
+// }
+
+ _super_Order.add_product.call(self, product, options);
+
+ self.pos.gui.show_popup('combo_product_popup',{
+ 'product': product
+ });
+ }
+ else{
+ _super_Order.add_product.call(self, product, options);
+ }
+ },
+ });
+
+ let _super_orderline = models.Orderline.prototype;
+ models.Orderline = models.Orderline.extend({
+ initialize: function(attr,options){
+ this.invisible = false;
+ this.combo_prod_info = false;
+ _super_orderline.initialize.call(this, attr, options);
+ },
+ init_from_JSON: function(json) {
+ let self = this;
+ let new_combo_data = [];
+
+ _super_orderline.init_from_JSON.apply(this,arguments);
+
+ this.invisible = json.invisible;
+
+ if(json.combo_ext_line_info && json.combo_ext_line_info.length > 0){
+ json.combo_ext_line_info.map(function(combo_data){
+ if(combo_data[2].product_id){
+ let product = self.pos.db.get_product_by_id(combo_data[2].product_id);
+ if(product){
+ new_combo_data.push({
+ 'product':product,
+ 'price':combo_data[2].price,
+ 'qty':combo_data[2].qty,
+ 'id':combo_data[2].id,
+ 'invisible':combo_data[2].invisible,
+ });
+ }
+ }
+ });
+ }
+ self.set_combo_prod_info(new_combo_data);
+ },
+ set_combo_prod_info: function(combo_prod_info){
+ this.combo_prod_info = combo_prod_info;
+ this.trigger('change',this);
+ },
+ get_combo_prod_info: function(){
+ return this.combo_prod_info;
+ },
+ export_as_JSON: function(){
+ let self = this;
+ let combo_ext_line_info = [];
+ let json = _super_orderline.export_as_JSON.call(this,arguments);
+
+ if(this.product.is_combo && this.combo_prod_info.length > 0){
+ _.each(this.combo_prod_info, function(item){
+ combo_ext_line_info.push([0, 0, {
+ 'product_id':item.product.id,
+ 'qty':item.qty,
+ 'price':item.price,
+ 'id':item.id,
+ 'invisible':item.invisible,
+ }]);
+ });
+ }
+ json.invisible = this.invisible;
+ json.combo_ext_line_info = this.product.is_combo ? combo_ext_line_info : [];
+ return json;
+ },
+ can_be_merged_with: function(orderline){
+ let result = _super_orderline.can_be_merged_with.call(this,orderline);
+ if(orderline.product.id == this.product.id && this.get_combo_prod_info()){
+ return false;
+ }
+ return result;
+ },
+ export_for_printing: function(){
+ let lines = _super_orderline.export_for_printing.call(this);
+ lines.combo_prod_info = this.get_combo_prod_info();
+ return lines;
+ },
+ get_display_price: function(){
+ let price = this.pos.config.iface_tax_included === 'total' ? this.get_price_with_tax() : this.get_base_price();
+ if (this.get_combo_prod_info()){
+ this.get_combo_prod_info().forEach(combo => price += combo['price']);
+ };
+ return price;
+ },
+ is_invisible: function(){
+ return this.invisible;
+ }
+ });
+
+ let POSComboProductPopup = PopupWidget.extend({
+ template: 'POSComboProductPopup',
+ events: _.extend({}, PopupWidget.prototype.events, {
+ 'click .product': 'select_product',
+ }),
+ show: function(options){
+ let self = this;
+ self._super(options);
+ self.product = options.product || false;
+ self.combo_product_info = options.combo_product_info || false;
+ self.combo_products_details = [];
+ self.new_combo_products_details = [];
+ self.scroll_position = 0;
+
+ self.product.combo_category_ids.map(function(id){
+ let products = self.pos.db.get_product_by_category(id);
+ products.map(function(product){
+ self.combo_products_details.push(product);
+ })
+ });
+ this.renderElement();
+ },
+ select_product: function(event){
+ let self = this;
+ let products_info = [];
+ let $el = $(event.currentTarget);
+ let product_id = Number($el.data('product-id'));
+ let line_id = Number($el.data('line-id'));
+ let order = self.pos.get_order();
+ let selected_line = order.get_selected_orderline();
+ let price_list = self.pos.gui.screen_instances.products.product_list_widget._get_active_pricelist();
+
+ if(selected_line){
+ let product = self.pos.db.get_product_by_id(product_id);
+ let price = 0;
+ if(product){
+ price = product['combo_price'] != 0 ? product['combo_price'] : product.get_price(price_list, 1);
+ products_info.push({
+ 'product':product,
+ 'id':product_id,
+ 'qty':1,
+ 'price':price,
+ 'invisible':true,
+ });
+ self.pos.get_order().add_product(product, {'price': price, 'extras': {'invisible': true} });
+ }
+ selected_line.set_combo_prod_info(products_info);
+ }else{
+ alert("Selected line not found!");
+ }
+ self.gui.close_popup();
+ },
+ click_cancel: function(){
+ this.gui.close_popup();
+ },
+ });
+ gui.define_popup({name:'combo_product_popup', widget: POSComboProductPopup});
+});
\ No newline at end of file
diff --git a/pos_combinated_drinks/static/src/xml/pos.xml b/pos_combinated_drinks/static/src/xml/pos.xml
new file mode 100755
index 00000000..c9a27167
--- /dev/null
+++ b/pos_combinated_drinks/static/src/xml/pos.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+ #{ line.invisible ? 'hide' : ( line.selected ? 'orderline selected' : 'orderline' ) }
+
+
+
+
+
+ +
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pos_combinated_drinks/views/point_of_sale.xml b/pos_combinated_drinks/views/point_of_sale.xml
new file mode 100755
index 00000000..64599c56
--- /dev/null
+++ b/pos_combinated_drinks/views/point_of_sale.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+ package.product.template.form.view
+ product.template
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pos_combinated_drinks/views/pos_combined_drinks.xml b/pos_combinated_drinks/views/pos_combined_drinks.xml
new file mode 100755
index 00000000..044b3d76
--- /dev/null
+++ b/pos_combinated_drinks/views/pos_combined_drinks.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file