Skip to content

Commit

Permalink
[FIX] pos: prevent reuse of serial numbers from active orders
Browse files Browse the repository at this point in the history
Steps:
- Open the POS.
- Add a product with serial number tracking to the order line.
- Create a new order.
- Add the same product to the new order

Issue:
- it is possible to select the same serial number that was previously
  used in another order.

Fix:
- Hide serial numbers that are being used in active orders

Task - 3944652

closes odoo#187176

Signed-off-by: Joseph Caburnay (jcb) <[email protected]>
  • Loading branch information
parp-odoo committed Jan 6, 2025
1 parent ed8a035 commit 876b733
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 1 deletion.
2 changes: 1 addition & 1 deletion addons/point_of_sale/models/pos_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -1388,7 +1388,7 @@ def get_existing_lots(self, company_id, product_id):
filtered(lambda q: float_compare(q.quantity, 0, precision_rounding=q.product_id.uom_id.rounding) > 0).\
mapped('lot_id')

return available_lots.read(['id', 'name'])
return available_lots.read(['id', 'name', 'product_qty'])

@api.ondelete(at_uninstall=False)
def _unlink_except_order_state(self):
Expand Down
33 changes: 33 additions & 0 deletions addons/point_of_sale/static/src/app/store/pos_store.js
Original file line number Diff line number Diff line change
Expand Up @@ -1884,6 +1884,38 @@ export class PosStore extends Reactive {
canCreateLots = true;
}

const usedLotsQty = this.models["pos.pack.operation.lot"]
.filter(
(lot) =>
lot.pos_order_line_id?.product_id?.id === product.id &&
lot.pos_order_line_id?.order_id?.state === "draft"
)
.reduce((acc, lot) => {
if (!acc[lot.lot_name]) {
acc[lot.lot_name] = { total: 0, currentOrderCount: 0 };
}
acc[lot.lot_name].total += lot.pos_order_line_id?.qty || 0;

if (lot.pos_order_line_id?.order_id?.id === this.selectedOrder.id) {
acc[lot.lot_name].currentOrderCount += lot.pos_order_line_id?.qty || 0;
}
return acc;
}, {});

// Remove lot/serial names that are already used in draft orders
existingLots = existingLots.filter((lot) => {
return lot.product_qty > (usedLotsQty[lot.name]?.total || 0);
});

// Check if the input lot/serial name is already used in another order
const isLotNameUsed = (itemValue) => {
const totalQty = existingLots.find((lt) => lt.name == itemValue)?.product_qty || 0;
const usedQty = usedLotsQty[itemValue]
? usedLotsQty[itemValue].total - usedLotsQty[itemValue].currentOrderCount
: 0;
return usedQty ? usedQty >= totalQty : false;
};

const existingLotsName = existingLots.map((l) => l.name);
const payload = await makeAwaitable(this.dialog, EditListPopup, {
title: _t("Lot/Serial Number(s) Required"),
Expand All @@ -1893,6 +1925,7 @@ export class PosStore extends Reactive {
options: existingLotsName,
customInput: canCreateLots,
uniqueValues: product.tracking === "serial",
isLotNameUsed: isLotNameUsed,
});
if (payload) {
// Segregate the old and new packlot lines
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ export class EditListPopup extends Component {
options: { type: Array, optional: true },
customInput: { type: Boolean, optional: true },
uniqueValues: { type: Boolean, optional: true },
isLotNameUsed: { type: Function, optional: true },
};
static defaultProps = {
options: [],
customInput: true,
uniqueValues: true,
isLotNameUsed: () => false,
};

/**
Expand Down Expand Up @@ -121,6 +123,7 @@ export class EditListPopup extends Component {
}
hasValidValue(itemId, text) {
return (
!this.props.isLotNameUsed(text) &&
(this.props.customInput || this.props.options.includes(text)) &&
(!this.props.uniqueValues ||
!this.state.array.some((elem) => elem._id !== itemId && elem.text === text))
Expand Down Expand Up @@ -186,6 +189,7 @@ export class EditListPopup extends Component {
const itemValue = item.text.trim();
const isValidValue =
itemValue !== "" &&
!this.props.isLotNameUsed(itemValue) &&
(this.props.customInput || this.props.options.includes(itemValue));
if (!isValidValue) {
return false;
Expand Down
8 changes: 8 additions & 0 deletions addons/point_of_sale/static/tests/tours/ticket_screen_tour.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,5 +269,13 @@ registry.category("web_tour.tours").add("LotTour", {
inLeftSide({
trigger: ".info-list:contains('SN 3')",
}),

// Verify if the serial number can be reused for the current order
Chrome.createFloatingOrder(),
ProductScreen.clickDisplayedProduct("Product A"),
ProductScreen.enterLastLotNumber("3"),
inLeftSide({
trigger: ".info-list:not(:contains('SN 3'))",
}),
].flat(),
});

0 comments on commit 876b733

Please sign in to comment.