| @ -1,42 +0,0 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################## | |||
| # | |||
| #    Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #    Copyright (C) 2023-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). | |||
| #    Author: Jumana Jabin MP(<http://www.cybrosys.com>) | |||
| #    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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################## | |||
| from odoo import api, models | |||
| 
 | |||
| 
 | |||
| class SerialNoValidation(models.Model): | |||
|     """Serial Number Validation Model.This model is used for serial number | |||
|        validation in Odoo.""" | |||
|     _name = 'serial_no.validation' | |||
| 
 | |||
|     @api.model | |||
|     def validate_lots(self, lots): | |||
|         """ This method validates a list of lots.""" | |||
|         processed = [] | |||
|         LotObj = self.env['stock.lot'] | |||
|         for lot in lots: | |||
|             lot_id = LotObj.search([('name', '=', lot)], limit=1) | |||
|             if lot_id.product_qty > 0 and lot not in processed: | |||
|                 processed.append(lot) | |||
|                 continue | |||
|             if lot in processed: | |||
|                 return ['duplicate', lot] | |||
|             return ['no_stock', lot] | |||
|         return True | |||
| @ -0,0 +1,72 @@ | |||
| # -*- coding: utf-8 -*- | |||
| ############################################################################## | |||
| # | |||
| #    Cybrosys Technologies Pvt. Ltd. | |||
| # | |||
| #    Copyright (C) 2023-TODAY Cybrosys Technologies(<http://www.cybrosys.com>). | |||
| #    Author: Jumana Jabin MP(<http://www.cybrosys.com>) | |||
| #    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 <http://www.gnu.org/licenses/>. | |||
| # | |||
| ############################################################################## | |||
| from odoo import api, models | |||
| 
 | |||
| 
 | |||
| class StockLot(models.Model): | |||
|     """ | |||
|     This class is inherited for adding a new function to validate the lots and | |||
|     serial numbers. | |||
|     Methods: | |||
|         validate_lots(lots): | |||
|             check and validate the lots and serial numbers for the product | |||
|             based on the stock location. | |||
|     """ | |||
|     _inherit = 'stock.lot' | |||
| 
 | |||
|     @api.model | |||
|     def validate_lots(self, lots, product_id, picking_type_id): | |||
|         """ To check | |||
|         - the invalid lots/ serial numbers | |||
|         - duplicate serial numbers | |||
|         - insufficient stock for the lots or serial numbers. | |||
|         All these cases are checked based on the product and the stock location | |||
|         set for the active PoS. | |||
|         Args: | |||
|            lots (list[str,..., str]): the lots for validation. | |||
|            product_id (int): id of the selected product. | |||
|            picking_type_id (int): id of the operation type added for the PoS. | |||
|         Returns: | |||
|             list[str, str] or Bool: True if the lot is valid, else the list of | |||
|             the string that indicates the exception: 'invalid', 'duplicate' or | |||
|             'no_stock' with the lot/ serial number. | |||
|         """ | |||
|         processed = [] | |||
|         if not product_id: | |||
|             return ['invalid', 'product'] | |||
|         for lot in lots: | |||
|             stock_lots = self.sudo().search([ | |||
|                 ('name', '=', lot), ('product_id', '=', product_id)]) | |||
|             if not stock_lots: | |||
|                 return ['invalid', lot] | |||
|             picking_type = self.env['stock.picking.type'].sudo().browse( | |||
|                 picking_type_id) | |||
|             stock_quant = self.env['stock.quant'].sudo().search( | |||
|                 [('location_id', '=', picking_type.default_location_src_id.id), | |||
|                  ('lot_id', 'in', stock_lots.ids)]) | |||
|             if (stock_quant and stock_quant.available_quantity > 0 | |||
|                     and lot not in processed): | |||
|                 processed.append(lot) | |||
|             else: | |||
|                 if lot in processed: | |||
|                     return ['duplicate', lot] | |||
|                 return ['no_stock', lot] | |||
|         return True | |||
| Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 134 KiB | 
| After Width: | Height: | Size: 290 KiB | 
| After Width: | Height: | Size: 352 KiB | 
| Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 129 KiB | 
| Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 143 KiB | 
| Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 137 KiB | 
| Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 68 KiB | 
| Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 249 KiB | 
| Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 290 KiB | 
| Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 281 KiB | 
| After Width: | Height: | Size: 257 KiB | 
| Before Width: | Height: | Size: 383 KiB After Width: | Height: | Size: 475 KiB | 
| @ -0,0 +1,77 @@ | |||
| odoo.define('pos_traceability_validation.PoSEditListPopup', function (require) { | |||
| "use strict"; | |||
|     const EditListPopup = require('point_of_sale.EditListPopup'); | |||
|     const Registries = require('point_of_sale.Registries'); | |||
|     var rpc = require('web.rpc'); | |||
|      /** | |||
|      * EditListPopup Override | |||
|      * | |||
|      * This module overrides the EditListPopup component in the Point of Sale (POS) module | |||
|      * to add custom behavior for serial number validation. | |||
|      */ | |||
|     const PosEditlistpopup = (EditListPopup) => | |||
|         class extends EditListPopup { | |||
|             constructor() { | |||
|                 super(...arguments); | |||
|                 this.product = this.props.product; | |||
|             } | |||
|             /** | |||
|              * On confirming from the popup after adding lots/ serial numbers, | |||
|              * the values are passed to the function validate_lots() for the | |||
|              * validation. The corresponding error messages will be displayed | |||
|              * on the popup if the lot is invalid or duplicated, or there is | |||
|              * no insufficient stock. | |||
|              */ | |||
|             async confirm() { | |||
|                 if (this.props.title == 'Lot/Serial Number(s) Required'){ | |||
|                     var lot_string = this.state.array | |||
|                     var lot_names = []; | |||
|                     for (var i = 0; i < lot_string.length; i++) { | |||
|                         if (lot_string[i].text != ""){ | |||
|                             lot_names.push(lot_string[i].text); | |||
|                         } | |||
|                     } | |||
|                     const picking_type_id = this.env.pos.config && this.env.pos.config.picking_type_id && this.env.pos.config.picking_type_id[0] | |||
|                     const result =  await rpc.query({ | |||
|                         model: 'stock.lot', | |||
|                         method: 'validate_lots', | |||
|                         args: [lot_names, this.props.product, picking_type_id] | |||
|                     }) | |||
|                     if(result != true){ | |||
|                         if(result[0] == 'no_stock'){ | |||
|                             this.showPopup('ErrorPopup', { | |||
|                                 'title': this.env._t('Out of stock'), | |||
|                                 'body': this.env._t("The product is out of stock for " + result[1] + '.'), | |||
|                             }); | |||
|                         } | |||
|                         else if(result[0] == 'duplicate'){ | |||
|                             this.showPopup('ErrorPopup', { | |||
|                                 'title': this.env._t('Duplicate entry'), | |||
|                                 'body': this.env._t("Duplicate entry for " + result[1] + '.'), | |||
|                             }); | |||
|                         } | |||
|                         else if(result[0] == 'invalid'){ | |||
|                             this.showPopup('ErrorPopup', { | |||
|                                 'title': this.env._t('Invalid Lot/ Serial Number'), | |||
|                                 'body': this.env._t("The Lot/ Serial Number " + result[1]+ ' is not available for this product.'), | |||
|                             }); | |||
|                         } | |||
|                     } | |||
|                     else{ | |||
|                         this.env.posbus.trigger('close-popup', { | |||
|                             popupId: this.props.id, | |||
|                             response: { confirmed: true, payload: await this.getPayload() }, | |||
|                         }); | |||
|                     } | |||
|                 } | |||
|                 else{ | |||
|                     this.env.posbus.trigger('close-popup', { | |||
|                         popupId: this.props.id, | |||
|                         response: { confirmed: true, payload: await this.getPayload() }, | |||
|                     }); | |||
|                 } | |||
|             } | |||
|         }; | |||
|     Registries.Component.extend(EditListPopup, PosEditlistpopup); | |||
|     return EditListPopup; | |||
| }); | |||
| @ -0,0 +1,37 @@ | |||
| odoo.define('pos_traceability_validation.PoSOrderWidget', function (require) { | |||
|     'use strict'; | |||
|     const OrderWidget = require('point_of_sale.OrderWidget'); | |||
|     const Registries = require('point_of_sale.Registries'); | |||
|     /** | |||
|      * Extends OrderWidget for passing the product IDs to the EditListPopup | |||
|      * validation | |||
|      */ | |||
|     const PoSOrderWidget = (OrderWidget) => | |||
|         class extends OrderWidget { | |||
|             async _editPackLotLines(event) { | |||
|                 const orderline = event.detail.orderline; | |||
|                 const isAllowOnlyOneLot = orderline.product.isAllowOnlyOneLot(); | |||
|                 const packLotLinesToEdit = orderline.getPackLotLinesToEdit(isAllowOnlyOneLot); | |||
|                 const { confirmed, payload } = await this.showPopup('EditListPopup', { | |||
|                     title: this.env._t('Lot/Serial Number(s) Required'), | |||
|                     isSingleItem: isAllowOnlyOneLot, | |||
|                     array: packLotLinesToEdit, | |||
|                     product: orderline.product.id | |||
|                 }); | |||
|                 if (confirmed) { | |||
|                     // Segregate the old and new packlot lines
 | |||
|                     const modifiedPackLotLines = Object.fromEntries( | |||
|                         payload.newArray.filter(item => item.id).map(item => [item.id, item.text]) | |||
|                     ); | |||
|                     const newPackLotLines = payload.newArray | |||
|                         .filter(item => !item.id) | |||
|                         .map(item => ({ lot_name: item.text })); | |||
| 
 | |||
|                     orderline.setPackLotLines({ modifiedPackLotLines, newPackLotLines }); | |||
|                 } | |||
|                 this.order.select_orderline(event.detail.orderline); | |||
|             } | |||
|         } | |||
|     Registries.Component.extend(OrderWidget, PoSOrderWidget); | |||
|     return OrderWidget; | |||
| }); | |||
| @ -0,0 +1,90 @@ | |||
| odoo.define('pos_traceability_validation.PoSProductScreen', function (require) { | |||
|     'use strict'; | |||
|     const ProductScreen = require('point_of_sale.ProductScreen'); | |||
|     const Registries = require('point_of_sale.Registries'); | |||
|     /** | |||
|      * Extends ProductScreen for passing the product ID to the EditListPopup | |||
|      * validation | |||
|      */ | |||
|     const PoSProductScreen = (ProductScreen) => | |||
|         class extends ProductScreen { | |||
|             async _getAddProductOptions(product, base_code) { | |||
|                 let price_extra = 0.0; | |||
|                 let draftPackLotLines, weight, description, packLotLinesToEdit; | |||
|                 if (this.env.pos.config.product_configurator && _.some(product.attribute_line_ids, (id) => id in this.env.pos.attributes_by_ptal_id)) { | |||
|                     let attributes = _.map(product.attribute_line_ids, (id) => this.env.pos.attributes_by_ptal_id[id]) | |||
|                                       .filter((attr) => attr !== undefined); | |||
|                     let { confirmed, payload } = await this.showPopup('ProductConfiguratorPopup', { | |||
|                         product: product, | |||
|                         attributes: attributes, | |||
|                     }); | |||
|                     if (confirmed) { | |||
|                         description = payload.selected_attributes.join(', '); | |||
|                         price_extra += payload.price_extra; | |||
|                     } else { | |||
|                         return; | |||
|                     } | |||
|                 } | |||
|                 // Gather lot information if required.
 | |||
|                 if (['serial', 'lot'].includes(product.tracking) && (this.env.pos.picking_type.use_create_lots || this.env.pos.picking_type.use_existing_lots)) { | |||
|                     const isAllowOnlyOneLot = product.isAllowOnlyOneLot(); | |||
|                     if (isAllowOnlyOneLot) { | |||
|                         packLotLinesToEdit = []; | |||
|                     } else { | |||
|                         const orderline = this.currentOrder | |||
|                             .get_orderlines() | |||
|                             .filter(line => !line.get_discount()) | |||
|                             .find(line => line.product.id === product.id); | |||
|                         if (orderline) { | |||
|                             packLotLinesToEdit = orderline.getPackLotLinesToEdit(); | |||
|                         } else { | |||
|                             packLotLinesToEdit = []; | |||
|                         } | |||
|                     } | |||
|                     const { confirmed, payload } = await this.showPopup('EditListPopup', { | |||
|                         title: this.env._t('Lot/Serial Number(s) Required'), | |||
|                         isSingleItem: isAllowOnlyOneLot, | |||
|                         array: packLotLinesToEdit, | |||
|                         product: product.id | |||
|                     }); | |||
|                     if (confirmed) { | |||
|                         // Segregate the old and new packlot lines
 | |||
|                         const modifiedPackLotLines = Object.fromEntries( | |||
|                             payload.newArray.filter(item => item.id).map(item => [item.id, item.text]) | |||
|                         ); | |||
|                         const newPackLotLines = payload.newArray | |||
|                             .filter(item => !item.id) | |||
|                             .map(item => ({ lot_name: item.text })); | |||
| 
 | |||
|                         draftPackLotLines = { modifiedPackLotLines, newPackLotLines }; | |||
|                     } else { | |||
|                         // We don't proceed on adding product.
 | |||
|                         return; | |||
|                     } | |||
|                 } | |||
|                 // Take the weight if necessary.
 | |||
|                 if (product.to_weight && this.env.pos.config.iface_electronic_scale) { | |||
|                     // Show the ScaleScreen to weigh the product.
 | |||
|                     if (this.isScaleAvailable) { | |||
|                         const { confirmed, payload } = await this.showTempScreen('ScaleScreen', { | |||
|                             product, | |||
|                         }); | |||
|                         if (confirmed) { | |||
|                             weight = payload.weight; | |||
|                         } else { | |||
|                             // do not add the product;
 | |||
|                             return; | |||
|                         } | |||
|                     } else { | |||
|                         await this._onScaleNotAvailable(); | |||
|                     } | |||
|                 } | |||
|                 if (base_code && this.env.pos.db.product_packaging_by_barcode[base_code.code]) { | |||
|                     weight = this.env.pos.db.product_packaging_by_barcode[base_code.code].qty; | |||
|                 } | |||
|                 return { draftPackLotLines, quantity: weight, description, price_extra }; | |||
|             } | |||
|         } | |||
|     Registries.Component.extend(ProductScreen, PoSProductScreen); | |||
|     return ProductScreen; | |||
| }); | |||
| @ -1,69 +0,0 @@ | |||
| odoo.define('pos_traceability_validation.pos_models', function (require) { | |||
| "use strict"; | |||
|  /** | |||
|      * EditListPopup Override | |||
|      * | |||
|      * This module overrides the EditListPopup component in the Point of Sale (POS) module | |||
|      * to add custom behavior for serial number validation. | |||
|      */ | |||
|     const EditListPopup = require('point_of_sale.EditListPopup'); | |||
|     const Registries = require('point_of_sale.Registries'); | |||
|     var rpc = require('web.rpc'); | |||
|     const PosEditlistpopup = (EditListPopup) => | |||
|         class extends EditListPopup { | |||
|         /** | |||
|              * Confirm Override | |||
|              * | |||
|              * Overrides the base confirm method to handle serial number validation. | |||
|              * If the title of the popup is 'Lot/Serial Number(s) Required', it validates | |||
|              * the entered lot numbers using the 'serial_no.validation' model. | |||
|              */ | |||
|              async confirm() { | |||
|                  if (this.props.title == 'Lot/Serial Number(s) Required'){ | |||
|                          var lot_string = this.state.array | |||
|                          var lot_names = []; | |||
|                          for (var i = 0; i < lot_string.length; i++) { | |||
|                             if (lot_string[i].text != ""){ | |||
|                                 lot_names.push(lot_string[i].text); | |||
|                             } | |||
|                          } | |||
|                          const result =  await rpc.query({ | |||
|                                                 model: 'serial_no.validation', | |||
|                                                 method: 'validate_lots', | |||
|                                                 args: [lot_names] | |||
|                                                 }) | |||
|                             if(result != true){ | |||
|                                 if(result[0] == 'no_stock'){ | |||
|                                     this.showPopup('ErrorPopup', { | |||
|                                         'title': this.env._t('Insufficient stock'), | |||
|                                         'body': this.env._t("Insufficient stock for " + result[1]), | |||
|                                     }); | |||
|                                 } | |||
|                                 else if(result[0] == 'duplicate'){ | |||
|                                     this.showPopup('ErrorPopup', { | |||
|                                         'title': this.env._t('Duplicate entry'), | |||
|                                         'body': this.env._t("Duplicate entry for " + result[1]), | |||
|                                     }); | |||
|                                 } | |||
|                                 else if(result[0] == 'except'){ | |||
|                                     alert("Exception occurred with " + result[1]) | |||
|                                     this.showPopup('ErrorPopup', { | |||
|                                         'title': this.env._t('Exception'), | |||
|                                         'body': this.env._t("Exception occurred with" + result[1]), | |||
|                                     }); | |||
|                                 } | |||
|                             } | |||
|                             else{ | |||
|                                     this.props.resolve({ confirmed: true, payload: await this.getPayload() }); | |||
|                                     this.trigger('close-popup'); | |||
|                             } | |||
|                  } | |||
|                  else{ | |||
|                         this.props.resolve({ confirmed: true, payload: await this.getPayload() }); | |||
|                         this.trigger('close-popup'); | |||
|                  } | |||
|             } | |||
|         }; | |||
|     Registries.Component.extend(EditListPopup, PosEditlistpopup); | |||
|     return EditListPopup; | |||
| }); | |||