diff --git a/product_multi_uom_pos/__manifest__.py b/product_multi_uom_pos/__manifest__.py index 1b5951033..0dea78825 100644 --- a/product_multi_uom_pos/__manifest__.py +++ b/product_multi_uom_pos/__manifest__.py @@ -34,8 +34,7 @@ 'views/pos_view_extended.xml', ], 'qweb': ['static/src/xml/pos.xml'], - 'images': ['static/description/banner.png', - 'static/description/icon.png'], + 'images': ['static/description/banner.png'], 'license': 'LGPL-3', 'installable': True, 'application': False, diff --git a/product_multi_uom_pos/doc/RELEASE_NOTES.md b/product_multi_uom_pos/doc/RELEASE_NOTES.md index a140c9ff2..140011fa0 100644 --- a/product_multi_uom_pos/doc/RELEASE_NOTES.md +++ b/product_multi_uom_pos/doc/RELEASE_NOTES.md @@ -5,6 +5,8 @@ #### Migration Migration Of POS Product Multiple UOM - - - +#### 12.02.2024 +#### Version 14.0.2.0.1 +##### FIX +-Removed the issue in updating quantity +-Update stock move lines with new unit of measure form pos. diff --git a/product_multi_uom_pos/models/__init__.py b/product_multi_uom_pos/models/__init__.py index 822abbf55..e946c471e 100644 --- a/product_multi_uom_pos/models/__init__.py +++ b/product_multi_uom_pos/models/__init__.py @@ -19,5 +19,6 @@ # If not, see . # ############################################################################## - from . import pos_orderline +from . import pos_order +from . import stock_picking \ No newline at end of file diff --git a/product_multi_uom_pos/models/pos_order.py b/product_multi_uom_pos/models/pos_order.py new file mode 100644 index 000000000..d23075f39 --- /dev/null +++ b/product_multi_uom_pos/models/pos_order.py @@ -0,0 +1,36 @@ +# -*- 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. +# +# 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 . +# +############################################################################## +from odoo import models + +class PosOrderExtended(models.Model): + _inherit = 'pos.order' + + def _prepare_invoice_line(self, order_line): + return { + 'product_id': order_line.product_id.id, + 'quantity': order_line.qty if self.amount_total >= 0 else -order_line.qty, + 'discount': order_line.discount, + 'price_unit': order_line.price_unit, + 'name': order_line.product_id.display_name, + 'tax_ids': [(6, 0, order_line.tax_ids_after_fiscal_position.ids)], + 'product_uom_id': order_line.uom_id.id, + } diff --git a/product_multi_uom_pos/models/pos_orderline.py b/product_multi_uom_pos/models/pos_orderline.py index 108d3e0f3..44195e55e 100644 --- a/product_multi_uom_pos/models/pos_orderline.py +++ b/product_multi_uom_pos/models/pos_orderline.py @@ -19,10 +19,10 @@ # If not, see . # ############################################################################## - from odoo import _ from odoo.tools import float_is_zero from odoo import models, fields, api +from itertools import groupby class PosOrderLinesExtended(models.Model): @@ -32,102 +32,10 @@ class PosOrderLinesExtended(models.Model): @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: + + if values.get('uom_id'): + values['uom_id'] = values['uom_id'][0] + else: 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 - source_loc = picking_type.default_location_src_id - 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': source_loc.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': source_loc.id, - 'location_dest_id': return_pick_type != picking_type and return_pick_type.default_location_dest_id.id or source_loc.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 if line.uom_id.id else line.product_id.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': source_loc.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 source_loc.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/models/stock_picking.py b/product_multi_uom_pos/models/stock_picking.py new file mode 100644 index 000000000..e5d51e59d --- /dev/null +++ b/product_multi_uom_pos/models/stock_picking.py @@ -0,0 +1,110 @@ +# -*- 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. +# +# 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 . +# +############################################################################## +from odoo import models +from itertools import groupby + + +class StockPicking(models.Model): + _inherit = 'stock.picking' + + def _prepare_stock_move_vals(self, first_line, order_lines): + return { + 'name': first_line.name, + 'product_uom': first_line.uom_id.id, + 'picking_id': self.id, + 'picking_type_id': self.picking_type_id.id, + 'product_id': first_line.product_id.id, + 'product_uom_qty': abs(sum(order_lines.mapped('qty'))), + 'state': 'draft', + 'location_id': self.location_id.id, + 'location_dest_id': self.location_dest_id.id, + 'company_id': self.company_id.id, + } + + def _create_move_from_pos_order_lines(self, lines): + self.ensure_one() + lines_by_product = groupby(sorted(lines, key=lambda l: l.product_id.id), + key=lambda l: (l.product_id.id, l.uom_id.id)) + for product, lines in lines_by_product: + order_lines = self.env['pos.order.line'].concat(*lines) + first_line = order_lines[0] + current_move = self.env['stock.move'].create( + self._prepare_stock_move_vals(first_line, order_lines) + ) + confirmed_moves = current_move._action_confirm() + for move in confirmed_moves: + if first_line.product_id == move.product_id and first_line.product_id.tracking != 'none': + if self.picking_type_id.use_existing_lots or self.picking_type_id.use_create_lots: + for line in order_lines: + sum_of_lots = 0 + for lot in line.pack_lot_ids.filtered(lambda l: l.lot_name): + if line.product_id.tracking == 'serial': + qty = 1 + else: + qty = abs(line.qty) + ml_vals = move._prepare_move_line_vals() + ml_vals.update({'qty_done': qty}) + if self.picking_type_id.use_existing_lots: + existing_lot = self.env['stock.production.lot'].search([ + ('company_id', '=', self.company_id.id), + ('product_id', '=', line.product_id.id), + ('name', '=', lot.lot_name) + ]) + if not existing_lot and self.picking_type_id.use_create_lots: + existing_lot = self.env['stock.production.lot'].create({ + 'company_id': self.company_id.id, + 'product_id': line.product_id.id, + 'name': lot.lot_name, + }) + ml_vals.update({ + 'lot_id': existing_lot.id, + }) + else: + ml_vals.update({ + 'lot_name': lot.lot_name, + }) + self.env['stock.move.line'].create(ml_vals) + sum_of_lots += qty + if abs(line.qty) != sum_of_lots: + difference_qty = abs(line.qty) - sum_of_lots + ml_vals = current_move._prepare_move_line_vals() + if line.product_id.tracking == 'serial': + ml_vals.update({'qty_done': 1}) + for i in range(int(difference_qty)): + self.env['stock.move.line'].create(ml_vals) + else: + ml_vals.update({'qty_done': difference_qty}) + self.env['stock.move.line'].create(ml_vals) + else: + move._action_assign() + sum_of_lots = 0 + for move_line in move.move_line_ids: + move_line.qty_done = move_line.product_uom_qty + sum_of_lots += move_line.product_uom_qty + if float_compare(move.product_uom_qty, move.quantity_done, + precision_rounding=move.product_uom.rounding) > 0: + remaining_qty = move.product_uom_qty - move.quantity_done + ml_vals = move._prepare_move_line_vals() + ml_vals.update({'qty_done': remaining_qty}) + self.env['stock.move.line'].create(ml_vals) + else: + move.quantity_done = move.product_uom_qty diff --git a/product_multi_uom_pos/static/src/js/models.js b/product_multi_uom_pos/static/src/js/models.js index eaad8f979..e0b05593f 100644 --- a/product_multi_uom_pos/static/src/js/models.js +++ b/product_multi_uom_pos/static/src/js/models.js @@ -10,103 +10,26 @@ var _t = core._t; var utils = require('web.utils'); var round_pr = utils.round_precision; var _super_orderline = models.Orderline.prototype; - - - 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); - console.log("result",result); - 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]; - }, - - set_quantity: function(quantity, keep_price) { - OrderlineSuper.prototype.set_quantity.call(this, quantity, keep_price); - this.order.assert_editable(); - if(quantity === 'remove'){ - this.order.remove_orderline(this); - return; - }else{ - var quant = parseFloat(quantity) || 0; - var unit = this.get_unit(); - if(unit){ - if (unit.rounding) { - var decimals = this.pos.dp['Product Unit of Measure']; - var rounding = Math.max(unit.rounding, Math.pow(10, -decimals)); - this.quantity = round_pr(quant, rounding); - this.quantityStr = field_utils.format.float(this.quantity, {digits: [69, decimals]}); - } else { - this.quantity = round_pr(quant, 1); - this.quantityStr = this.quantity.toFixed(0); - } - }else{ - this.quantity = quant; - this.quantityStr = '' + this.quantity; - } - } - // just like in sale.order changing the quantity will recompute the unit price - if(! keep_price && ! this.price_manually_set){ - var self = this; - var order = self.pos.get_order(); - var orderline = order.get_selected_orderline(); - if (orderline){ - var uom = orderline.uom_id[0]; - var lst_uom = this.pos.units_by_id[uom]; - var ref_qty = orderline.quantity; - var ref_price = orderline.product.lst_price; - if (lst_uom.uom_type == 'bigger') { - this.set_unit_price(ref_price * lst_uom.factor_inv); - this.order.fix_tax_included_price(this); - } - else if (lst_uom.uom_type == 'smaller') { - this.set_unit_price(ref_price / lst_uom.factor); - this.order.fix_tax_included_price(this); - } - else { - this.set_unit_price(ref_price); - this.order.fix_tax_included_price(this); - } - - } - else{ - this.set_unit_price(this.product.get_price(this.order.pricelist, this.get_quantity())); - this.order.fix_tax_included_price(this); - } - - } - this.trigger('change', this); - }, - - -}); - - - + initialize: function(attr, options) { + _super_orderline.initialize.call(this,attr,options); + this.uom_id = this.product.get_unit(); + }, + export_as_JSON: function() { + var result = _super_orderline.export_as_JSON.call(this); + result.uom_id = this.uom_id; + return result; + }, + get_custom_unit: function(){ + return this.uom_id; + }, + export_for_printing: function() { + var line = _super_orderline.export_for_printing.apply(this,arguments); + line.unit_name = this.get_custom_unit().name; + return line; + }, + }); models.Order = models.Order.extend({ - updatePricelist: function(newClient) { let newClientPricelist, newClientFiscalPosition; const defaultFiscalPosition = this.pos.fiscal_positions.find( @@ -126,23 +49,13 @@ models.Order = models.Order.extend({ newClientFiscalPosition = defaultFiscalPosition; newClientPricelist = this.pos.default_pricelist; } - - if (this.selected_orderline.uom_id != this.selected_orderline.product.uom_id){ this.fiscal_position = newClientFiscalPosition; }else{ this.fiscal_position = newClientFiscalPosition; this.set_pricelist(newClientPricelist); - } - - } - - - }); - - }); \ No newline at end of file diff --git a/product_multi_uom_pos/static/src/js/multi_uom.js b/product_multi_uom_pos/static/src/js/multi_uom.js deleted file mode 100644 index 7bc22a142..000000000 --- a/product_multi_uom_pos/static/src/js/multi_uom.js +++ /dev/null @@ -1,88 +0,0 @@ -odoo.define('product_multi_uom_pos.multi_uom',function(require) { -"use strict"; - -console.log("multi_uom_main") - -var gui = require('point_of_sale.gui'); -var core = require('web.core'); -var models = require('point_of_sale.models'); -var pos_screens = require('point_of_sale.screens'); -var field_utils = require('web.field_utils'); -var rpc = require('web.rpc'); -var QWeb = core.qweb; -var _t = core._t; -var utils = require('web.utils'); -var round_pr = utils.round_precision; - - - -//pos_screens.OrderWidget.include({ -// set_value: function(val) { -// this._super(); -// var order = this.pos.get_order(); -// var orderline = order.get_selected_orderline(); -// var uom = orderline.uom_id[0]; -// var lst_uom = this.pos.units_by_id[uom]; -// if (order.get_selected_orderline()) { -// -// -// var orderline = order.get_selected_orderline(); -// var latestprice = orderline.lst_price; -// var current_pricelist = this.pos.default_pricelist; -// orderline.set_unit_price(latestprice); -// var mode = this.numpad_state.get('mode'); -// if( mode === 'quantity'){ -// var selected_orderline = order.get_selected_orderline(); -// selected_orderline.set_unit_price(latestprice); -// order.get_selected_orderline().set_quantity(val); -// }else if( mode === 'discount'){ -// order.get_selected_orderline().set_discount(val); -// }else if( mode === 'price'){ -// var selected_orderline = order.get_selected_orderline(); -// selected_orderline.price_manually_set = true; -// selected_orderline.set_unit_price(val); -// } -// if (this.pos.config.iface_customer_facing_display) { -// this.pos.send_current_order_to_customer_facing_display(); -// } -// } -// }, -// }); -// -// -// -//}); - - -// updatePricelist: function(newClient) { -// let newClientPricelist, newClientFiscalPosition; -// const defaultFiscalPosition = this.pos.fiscal_positions.find( -// (position) => position.id === this.pos.config.default_fiscal_position_id[0] -// ); -// if (newClient) { -// newClientFiscalPosition = newClient.property_account_position_id -// ? this.pos.fiscal_positions.find( -// (position) => position.id === newClient.property_account_position_id[0] -// ) -// : defaultFiscalPosition; -// newClientPricelist = -// this.pos.pricelists.find( -// (pricelist) => pricelist.id === newClient.property_product_pricelist[0] -// ) || this.pos.default_pricelist; -// } else { -// newClientFiscalPosition = defaultFiscalPosition; -// newClientPricelist = this.pos.default_pricelist; -// } -// -// -// if (this.selected_orderline.uom_id != this.selected_orderline.product.uom_id){ -// this.fiscal_position = newClientFiscalPosition; -// -// }else{ -// this.fiscal_position = newClientFiscalPosition; -// this.set_pricelist(newClientPricelist); -// -// } -// -// -// } \ No newline at end of file diff --git a/product_multi_uom_pos/static/src/js/multi_uom_widget.js b/product_multi_uom_pos/static/src/js/multi_uom_widget.js index 249137b16..76a17bc73 100644 --- a/product_multi_uom_pos/static/src/js/multi_uom_widget.js +++ b/product_multi_uom_pos/static/src/js/multi_uom_widget.js @@ -4,7 +4,6 @@ odoo.define('product_multi_uom_pos.multi_uom_widget',function(require) { var gui = require('point_of_sale.Gui'); var core = require('web.core'); var QWeb = core.qweb; - const NumberBuffer = require('point_of_sale.NumberBuffer'); const { onChangeOrder, useBarcodeReader } = require('point_of_sale.custom_hooks'); const PosComponent = require('point_of_sale.PosComponent'); @@ -12,32 +11,20 @@ var QWeb = core.qweb; const ProductScreen = require('point_of_sale.ProductScreen'); const { useListener } = require('web.custom_hooks'); const { useState, useRef } = owl.hooks; - - - - class MultiUomWidget extends PosComponent { - - constructor() { super(...arguments); - this.options = {}; this.uom_list = []; - } - mounted(options){ - var current_uom = this.env.pos.units_by_id[this.props.options.uom_list[0]]; var uom_list = this.env.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 = this.props.options.uom_list[0]; this.render(); - } - get_units_by_category(uom_list, categ_id){ var uom_by_categ = [] for (var uom in uom_list){ @@ -74,34 +61,19 @@ var QWeb = core.qweb; } if(ref_unit){ if(uom.uom_type == 'bigger'){ - console.log("bigggg"); - console.log("ref_price * uom.factor_inv",ref_price * uom.factor_inv); - return (ref_price * uom.factor_inv); } else if(uom.uom_type == 'smaller'){ - console.log("smalll"); - console.log("small",(ref_price / uom.factor_inv)); return (ref_price / uom.factor); } else if(uom.uom_type == 'reference'){ - console.log("refernce"); - console.log("ref_price",ref_price); return ref_price; } } return product.lst_price; } - -// close(){ -// if (this.pos.barcode_reader) { -// this.pos.barcode_reader.restore_callbacks(); -// } -// } - - - click_confirm(){ + click_confirm(ev){ var self = this; var uom = parseInt($('.uom').val()); var order = this.env.pos.get_order(); @@ -110,25 +82,19 @@ var QWeb = core.qweb; 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); orderline.lst_price = latest_price; - this.trigger('close-popup'); return; - } click_cancel(){ this.trigger('close-popup'); } - } - - MultiUomWidget.template = 'MultiUomWidget'; MultiUomWidget.defaultProps = { confirmText: 'Return', @@ -139,4 +105,3 @@ var QWeb = core.qweb; Registries.Component.add(MultiUomWidget); return MultiUomWidget; }); - diff --git a/product_multi_uom_pos/static/src/js/uom_button.js b/product_multi_uom_pos/static/src/js/uom_button.js index db3fa9ca6..f98a64cf3 100644 --- a/product_multi_uom_pos/static/src/js/uom_button.js +++ b/product_multi_uom_pos/static/src/js/uom_button.js @@ -3,7 +3,6 @@ odoo.define('product_multi_uom_pos.uom_button',function(require) { var core = require('web.core'); var QWeb = core.qweb; - const { onChangeOrder, useBarcodeReader } = require('point_of_sale.custom_hooks'); const PosComponent = require('point_of_sale.PosComponent'); const Registries = require('point_of_sale.Registries'); @@ -11,42 +10,27 @@ var QWeb = core.qweb; const { useListener } = require('web.custom_hooks'); const { useState, useRef } = owl.hooks; const { Gui } = require('point_of_sale.Gui'); - - - class UOMButton extends PosComponent { - constructor() { super(...arguments); useListener('click', this.button_click); } - button_click() { var orderline = this.env.pos.get_order().get_selected_orderline(); var options = { 'uom_list': orderline.product.uom_id }; - Gui.showPopup('MultiUomWidget',{options:options}); } - } - - UOMButton.template = 'UOMButton'; ProductScreen.addControlButton({ - component: UOMButton, condition: function () { return true; }, position: ['before', 'SetFiscalPositionButton'], - }); - Registries.Component.add(UOMButton); return UOMButton; - - - }); \ No newline at end of file