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