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