@ -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; |
|
||||
}); |
|