diff --git a/product_multi_uom_pos/README.rst b/product_multi_uom_pos/README.rst new file mode 100644 index 000000000..91857c537 --- /dev/null +++ b/product_multi_uom_pos/README.rst @@ -0,0 +1,39 @@ +======================== +POS Product Multiple UOM +======================== + +Added option to update the uom of products in pos. + +Installation +============ + +Just select it from available modules to install it, there is no need to extra installations. + +Usage +===== + +After installation, go to Inventory -> Settings + and enable the multiple uom option under the 'Products' section. + +Known issues / Roadmap +====================== + +* ... + +Bug Tracker +=========== + +Contact odoo@cybrosys.com + +Contributors +------------ + +* Linto CT +* Shereef PT + +Maintainer +---------- + +This module is maintained by Cybrosys Technologies. + +For support and more information, please visit https://www.cybrosys.com. diff --git a/product_multi_uom_pos/__init__.py b/product_multi_uom_pos/__init__.py new file mode 100644 index 000000000..d1fb7a81f --- /dev/null +++ b/product_multi_uom_pos/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2017-TODAY Cybrosys Technologies(). +# Author: LINTO C T() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## + +import models diff --git a/product_multi_uom_pos/__manifest__.py b/product_multi_uom_pos/__manifest__.py new file mode 100644 index 000000000..7460d02e6 --- /dev/null +++ b/product_multi_uom_pos/__manifest__.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +############################################################################## +# +# Cybrosys Technologies Pvt. Ltd. +# Copyright (C) 2018-TODAY Cybrosys Technologies(). +# Author: LINTO C T() +# you can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# It is forbidden to publish, distribute, sublicense, or sell copies +# of the Software or modified copies of the Software. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# GENERAL PUBLIC LICENSE (LGPL v3) along with this program. +# If not, see . +# +############################################################################## +{ + 'name': 'POS Product Multiple UOM', + 'version': '10.0.1.0.0', + 'category': 'Point of Sale', + 'summary': 'Multiple UOM for Products', + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'website': 'https://www.cybrosys.com', + 'depends': ['point_of_sale', 'stock'], + 'data': [ + 'views/pos_template.xml', + 'views/pos_view_extended.xml', + ], + 'demo': [], + 'qweb': ['static/src/xml/pos.xml'], + 'images': ['static/description/banner.jpg'], + 'license': 'LGPL-3', + 'installable': True, + 'application': False, + 'auto_install': False, +} diff --git a/product_multi_uom_pos/models/__init__.py b/product_multi_uom_pos/models/__init__.py new file mode 100644 index 000000000..9fee21ae4 --- /dev/null +++ b/product_multi_uom_pos/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +import pos_orderline diff --git a/product_multi_uom_pos/models/pos_orderline.py b/product_multi_uom_pos/models/pos_orderline.py new file mode 100644 index 000000000..75d5159bf --- /dev/null +++ b/product_multi_uom_pos/models/pos_orderline.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- + +from odoo import _ +from odoo.tools import float_is_zero +from odoo import models, fields, api + + +class PosOrderLinesExtended(models.Model): + _inherit = 'pos.order.line' + + uom_id = fields.Many2one('product.uom', string="UOM") + + @api.model + def create(self, values): + """updating uom to orderlines""" + try: + if values.get('uom_id'): + values['uom_id'] = values['uom_id'][0] + except Exception: + values['uom_id'] = None + pass + res = super(PosOrderLinesExtended, self).create(values) + return res + + +class PosOrderExtended(models.Model): + _inherit = 'pos.order' + + # overwriting this function because, we need to set the uom id based on the unit + # in the orderline instead of product uom, at the time of creating the stock moves + def create_picking(self): + """Create a picking for each order and validate it.""" + Picking = self.env['stock.picking'] + Move = self.env['stock.move'] + StockWarehouse = self.env['stock.warehouse'] + for order in self: + if not order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu']): + continue + address = order.partner_id.address_get(['delivery']) or {} + picking_type = order.picking_type_id + return_pick_type = order.picking_type_id.return_picking_type_id or order.picking_type_id + order_picking = Picking + return_picking = Picking + moves = Move + location_id = order.location_id.id + if order.partner_id: + destination_id = order.partner_id.property_stock_customer.id + else: + if (not picking_type) or (not picking_type.default_location_dest_id): + customerloc, supplierloc = StockWarehouse._get_partner_locations() + destination_id = customerloc.id + else: + destination_id = picking_type.default_location_dest_id.id + + if picking_type: + message = _("This transfer has been created from the point of sale session: %s") % (order.id, order.name) + picking_vals = { + 'origin': order.name, + 'partner_id': address.get('delivery', False), + 'date_done': order.date_order, + 'picking_type_id': picking_type.id, + 'company_id': order.company_id.id, + 'move_type': 'direct', + 'note': order.note or "", + 'location_id': location_id, + 'location_dest_id': destination_id, + } + pos_qty = any([x.qty > 0 for x in order.lines if x.product_id.type in ['product', 'consu']]) + if pos_qty: + order_picking = Picking.create(picking_vals.copy()) + order_picking.message_post(body=message) + neg_qty = any([x.qty < 0 for x in order.lines if x.product_id.type in ['product', 'consu']]) + if neg_qty: + return_vals = picking_vals.copy() + return_vals.update({ + 'location_id': destination_id, + 'location_dest_id': return_pick_type != picking_type and return_pick_type.default_location_dest_id.id or location_id, + 'picking_type_id': return_pick_type.id + }) + return_picking = Picking.create(return_vals) + return_picking.message_post(body=message) + + for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu'] and not float_is_zero(l.qty, precision_rounding=l.product_id.uom_id.rounding)): + moves |= Move.create({ + 'name': line.name, + 'product_uom': line.uom_id.id, + 'picking_id': order_picking.id if line.qty >= 0 else return_picking.id, + 'picking_type_id': picking_type.id if line.qty >= 0 else return_pick_type.id, + 'product_id': line.product_id.id, + 'product_uom_qty': abs(line.qty), + 'state': 'draft', + 'location_id': location_id if line.qty >= 0 else destination_id, + 'location_dest_id': destination_id if line.qty >= 0 else return_pick_type != picking_type and return_pick_type.default_location_dest_id.id or location_id, + }) + # prefer associating the regular order picking, not the return + order.write({'picking_id': order_picking.id or return_picking.id}) + + if return_picking: + order._force_picking_done(return_picking) + if order_picking: + order._force_picking_done(order_picking) + + # when the pos.config has no picking_type_id set only the moves will be created + if moves and not return_picking and not order_picking: + tracked_moves = moves.filtered(lambda move: move.product_id.tracking != 'none') + untracked_moves = moves - tracked_moves + tracked_moves.action_confirm() + untracked_moves.action_assign() + moves.filtered(lambda m: m.state in ['confirmed', 'waiting']).force_assign() + moves.filtered(lambda m: m.product_id.tracking == 'none').action_done() + + return True diff --git a/product_multi_uom_pos/static/description/banner.jpg b/product_multi_uom_pos/static/description/banner.jpg new file mode 100644 index 000000000..8cc52b65b Binary files /dev/null and b/product_multi_uom_pos/static/description/banner.jpg differ diff --git a/product_multi_uom_pos/static/description/cybro_logo.png b/product_multi_uom_pos/static/description/cybro_logo.png new file mode 100644 index 000000000..bb309114c Binary files /dev/null and b/product_multi_uom_pos/static/description/cybro_logo.png differ diff --git a/product_multi_uom_pos/static/description/index.html b/product_multi_uom_pos/static/description/index.html new file mode 100644 index 000000000..edf0da502 --- /dev/null +++ b/product_multi_uom_pos/static/description/index.html @@ -0,0 +1,92 @@ +
+
+

POS Product Multiple UOM

+

+

Cybrosys Technologies

+
+
+

Features:

+
+ Multiple UOM for products in POS.
+
+
+
+ +
+
+
+

Overview

+

+ By default, Odoo does not provide the option to update the unit of measure + of the products we select in the point of sale. We can sell products only in its + default unit. This module provides an option to update the UOM of the products + in point of sale. The stock will also be updated in the new UOM. +

+
+
+
+ +
+
+
New button added for opening the wizard.
+
+
+ +
+
+
+
+
This button will open a wizard which will show all the UOMs + associated with the selected product. +
+
+
+
+ +
+
+
+

+ We can select the UOMs available for this product and update it. +

+
+ +
+
+ +
+
+
+ + +
+
+
+ +
+

Need Any Help?

+ +
diff --git a/product_multi_uom_pos/static/description/modify_uom_button.png b/product_multi_uom_pos/static/description/modify_uom_button.png new file mode 100644 index 000000000..1caeda8cb Binary files /dev/null and b/product_multi_uom_pos/static/description/modify_uom_button.png differ diff --git a/product_multi_uom_pos/static/description/multi_uom_wiz.png b/product_multi_uom_pos/static/description/multi_uom_wiz.png new file mode 100644 index 000000000..5d10ab996 Binary files /dev/null and b/product_multi_uom_pos/static/description/multi_uom_wiz.png differ diff --git a/product_multi_uom_pos/static/description/orderlines.png b/product_multi_uom_pos/static/description/orderlines.png new file mode 100644 index 000000000..bb1e65a2b Binary files /dev/null and b/product_multi_uom_pos/static/description/orderlines.png differ diff --git a/product_multi_uom_pos/static/src/css/style.css b/product_multi_uom_pos/static/src/css/style.css new file mode 100644 index 000000000..4debf796f --- /dev/null +++ b/product_multi_uom_pos/static/src/css/style.css @@ -0,0 +1,22 @@ +.button-multi-uom{ + width: 100%; + height: 32px; + font-size: 16px; + text-align: center; + border-radius: 5px; +} +.multi-uom-span { + width: 50%; + border: 1px solid; + /* vertical-align: middle; */ + align-self: center; + padding: 5px 10px; + background-color: #6EC89B; + color: #fff; + LINE-HEIGHT: 2.5; + transition: all 150ms linear; +} + +.popup-product-creation{ + height: 50% !important; +} \ No newline at end of file diff --git a/product_multi_uom_pos/static/src/img/icon.png b/product_multi_uom_pos/static/src/img/icon.png new file mode 100644 index 000000000..5084d7db7 Binary files /dev/null and b/product_multi_uom_pos/static/src/img/icon.png differ diff --git a/product_multi_uom_pos/static/src/js/multi_uom.js b/product_multi_uom_pos/static/src/js/multi_uom.js new file mode 100644 index 000000000..7bbd70cfb --- /dev/null +++ b/product_multi_uom_pos/static/src/js/multi_uom.js @@ -0,0 +1,152 @@ +odoo.define('product_multi_uom_pos.multi_uom',function(require) { +"use strict"; + +var gui = require('point_of_sale.gui'); +var PosBaseWidget = require('point_of_sale.BaseWidget'); +var core = require('web.core'); +var models = require('point_of_sale.models'); +var OrderlineSuper = models.Orderline; +var pos_screens = require('point_of_sale.screens'); + +var MultiUomWidget = PosBaseWidget.extend({ + template: 'MultiUomWidget', + init: function(parent, args) { + this._super(parent, args); + this.options = {}; + this.uom_list = []; + }, + events: { + 'click .button.cancel': 'click_cancel', + 'click .button.confirm': 'click_confirm', + }, + /*function returns all the uom s in the specified category*/ + get_units_by_category: function(uom_list, categ_id){ + var uom_by_categ = [] + for (var uom in uom_list){ + if(uom_list[uom].category_id[0] == categ_id[0]){ + uom_by_categ.push(uom_list[uom]); + } + } + return uom_by_categ; + }, + /*Find the base price(price of the product for reference unit)*/ + find_reference_unit_price: function(product, product_uom){ + if(product_uom.uom_type == 'reference'){ + return product.price; + } + else if(product_uom.uom_type == 'smaller'){ + return (product.price * product_uom.factor); + } + else if(product_uom.uom_type == 'bigger'){ + return (product.price / product_uom.factor_inv); + } + }, + /*finds the latest price for the product based on the new uom selected*/ + get_latest_price: function(uom, product){ + var uom_by_category = this.get_units_by_category(this.pos.units_by_id, uom.category_id); + var product_uom = this.pos.units_by_id[product.uom_id[0]]; + var ref_price = this.find_reference_unit_price(product, product_uom); + var ref_unit = null; + for (var i in uom_by_category){ + if(uom_by_category[i].uom_type == 'reference'){ + ref_unit = uom_by_category[i]; + break; + } + } + if(ref_unit){ + if(uom.uom_type == 'bigger'){ + return (ref_price * uom.factor_inv); + } + else if(uom.uom_type == 'smaller'){ + return (ref_price / uom.factor); + } + else if(uom.uom_type == 'reference'){ + return ref_price; + } + } + return product.price; + }, + /*Rendering the wizard*/ + show: function(options){ + options = options || {}; + var current_uom = this.pos.units_by_id[options.uom_list[0]]; + var uom_list = this.pos.units_by_id; + var uom_by_category = this.get_units_by_category(uom_list, current_uom.category_id); + this.uom_list = uom_by_category; + this.current_uom = options.uom_list[0]; + this.renderElement(); + }, + close: function(){ + if (this.pos.barcode_reader) { + this.pos.barcode_reader.restore_callbacks(); + } + }, + click_confirm: function(){ + var self = this; + var uom = parseInt(this.$('.uom').val()); + var order = self.pos.get_order(); + var orderline = order.get_selected_orderline(); + var selected_uom = this.pos.units_by_id[uom]; + orderline.uom_id = []; + orderline.uom_id[0] = uom; + orderline.uom_id[1] = selected_uom.display_name; + + /*Updating the orderlines*/ + order.remove_orderline(orderline); + order.add_orderline(orderline); + var latest_price = this.get_latest_price(selected_uom, orderline.product); + order.get_selected_orderline().set_unit_price(latest_price); + this.gui.close_popup(); + return; + + }, + click_cancel: function(){ + this.gui.close_popup(); + }, +}); +gui.define_popup({name:'multi_uom_screen', widget: MultiUomWidget}); + +models.Orderline = models.Orderline.extend({ + /*Adding uom_id to orderline*/ + initialize: function(attr,options){ + OrderlineSuper.prototype.initialize.call(this, attr, options); + this.uom_id = this ? this.product.uom_id: []; + }, + export_as_JSON: function() { + var result = OrderlineSuper.prototype.export_as_JSON.call(this); + result.uom_id = this.uom_id; + return result; + }, + /*this function now will return the uom_id of the orderline , + instead of the default uom_id of the product*/ + get_unit: function(){ + var res = OrderlineSuper.prototype.get_unit.call(this); + + var unit_id = this.uom_id; + if(!unit_id){ + return res; + } + unit_id = unit_id[0]; + if(!this.pos){ + return undefined; + } + return this.pos.units_by_id[unit_id]; + } +}); + +pos_screens.ActionpadWidget.include({ + /*opening the wizard on button click*/ + renderElement: function() { + this._super(); + var self = this; + this.$('.multi-uom-span').click(function(){ + var orderline = self.pos.get_order().get_selected_orderline(); + var options = { + 'uom_list': orderline.product.uom_id + }; + self.gui.show_popup('multi_uom_screen', options); + }); + } +}); + +}); \ No newline at end of file diff --git a/product_multi_uom_pos/static/src/xml/pos.xml b/product_multi_uom_pos/static/src/xml/pos.xml new file mode 100644 index 000000000..82c64b12f --- /dev/null +++ b/product_multi_uom_pos/static/src/xml/pos.xml @@ -0,0 +1,39 @@ + + + + +
+ + Modify Unit of Measure + +
+
+
+ + + + + +
\ No newline at end of file diff --git a/product_multi_uom_pos/views/pos_template.xml b/product_multi_uom_pos/views/pos_template.xml new file mode 100644 index 000000000..55ffc975d --- /dev/null +++ b/product_multi_uom_pos/views/pos_template.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/product_multi_uom_pos/views/pos_view_extended.xml b/product_multi_uom_pos/views/pos_view_extended.xml new file mode 100644 index 000000000..8e7f90284 --- /dev/null +++ b/product_multi_uom_pos/views/pos_view_extended.xml @@ -0,0 +1,16 @@ + + + + + + UOM for Order-line + pos.order + + + + + + + + + \ No newline at end of file