From 8803024049de124e32ee6dab99fbaa255b81084b Mon Sep 17 00:00:00 2001 From: khaira777 <777gurkirat@gmail.com> Date: Sun, 18 Oct 2026 14:31:43 -0400 Subject: [PATCH] fix(inventory): preserve ledger chain continuity for legacy stock quantities (#1217) --- main/services/inventory.ts | 23 ++++++++++--- tests/inventory-ledger.test.ts | 63 ++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/main/services/inventory.ts b/main/services/inventory.ts index eb42b513c..56c62e96d 100644 --- a/main/services/inventory.ts +++ b/main/services/inventory.ts @@ -128,6 +128,17 @@ export function resolveInventoryDeduction( return null; } +function getDecimalPlaces(value: number): number { + if (!Number.isFinite(value) || Math.floor(value) === value) return 0; + const str = value.toString(); + if (str.includes('e-')) { + const [base, exp] = str.split('e-'); + const baseDecimals = base.includes('.') ? base.split('.')[1].length : 0; + return baseDecimals + Number(exp); + } + return str.includes('.') ? str.split('.')[1].length : 0; +} + export function adjustProductStock( db: ReturnType, options: StockChangeOptions, @@ -195,6 +206,10 @@ export function adjustProductStock( const readStock = db.prepare(`SELECT stock_quantity FROM ${stockTable} WHERE ${stockKey}`); const current = readStock.get(...stockIdArgs) as { stock_quantity: number | null } | undefined; const stockBefore = Number(current?.stock_quantity ?? 0); + const targetPrecision = Math.max( + INVENTORY_QUANTITY_PRECISION, + Math.min(14, getDecimalPlaces(stockBefore)), + ); const update = db.prepare(` UPDATE ${stockTable} SET stock_quantity = CASE @@ -212,7 +227,7 @@ export function adjustProductStock( normalizedQuantityDelta, INVENTORY_QUANTITY_TOLERANCE, normalizedQuantityDelta, - INVENTORY_QUANTITY_PRECISION, + targetPrecision, updatedAt, ...stockIdArgs, normalizedQuantityDelta, @@ -220,7 +235,7 @@ export function adjustProductStock( normalizedQuantityDelta, INVENTORY_QUANTITY_TOLERANCE, normalizedQuantityDelta, - INVENTORY_QUANTITY_PRECISION, + targetPrecision, ); if (result.changes !== 1) { if (!current) { @@ -231,9 +246,7 @@ export function adjustProductStock( const updatedStock = readStock.get(...stockIdArgs) as { stock_quantity: number }; const stockAfter = Number(updatedStock.stock_quantity); - const effectiveQuantityDelta = Number( - (stockAfter - stockBefore).toFixed(INVENTORY_QUANTITY_PRECISION), - ); + const effectiveQuantityDelta = Number((stockAfter - stockBefore).toFixed(targetPrecision)); db.prepare(` INSERT INTO inventory_movements ( diff --git a/tests/inventory-ledger.test.ts b/tests/inventory-ledger.test.ts index 84092afe8..10a37a7e1 100644 --- a/tests/inventory-ledger.test.ts +++ b/tests/inventory-ledger.test.ts @@ -25,6 +25,7 @@ const { closeDatabase, now, validateInventoryLedgerReplacement, + validateInventoryLedgerDatabase, } = require('../main/db'); const { adjustProductStock } = require('../main/services/inventory'); const { @@ -456,6 +457,68 @@ async function main() { 'movement delta reflects the clamped change (-2e-8)', ); + // ── Legacy stock precision test: >8 decimal places ─────────────────────── + const legacyStock = 10.123456789012; + db.prepare(` + INSERT INTO products (id, category_id, name, price, track_inventory, stock_quantity, created_at, updated_at) + VALUES ('p-legacy-precision', 'cat-ledger', 'Legacy Precision Item', 100, 1, ?, ?, ?) + `).run(legacyStock, now(), now()); + db.prepare(` + INSERT INTO inventory_movements ( + product_id, quantity_delta, movement_type, reference_type, stock_after, actor_user_id, created_at + ) VALUES ('p-legacy-precision', ?, 'adjustment', 'opening_balance', ?, ?, ?) + `).run(legacyStock, legacyStock, owner.userId, now()); + + assertEqual( + validateInventoryLedgerDatabase(db), + null, + 'legacy stock with >8 decimals validates before adjustment', + ); + + // Negative adjustment + adjustProductStock(db, { + productId: 'p-legacy-precision', + quantityDelta: -0.5, + movementType: 'adjustment', + actorUserId: owner.userId, + reason: 'Legacy minus 0.5 adjustment', + }); + assertEqual( + validateInventoryLedgerDatabase(db), + null, + 'adjustProductStock preserves ledger chain continuity for legacy stock after decrease', + ); + const legacyAfterDec = db + .prepare('SELECT stock_quantity FROM products WHERE id = ?') + .get('p-legacy-precision') as { stock_quantity: number }; + assertEqual( + legacyAfterDec.stock_quantity, + 9.623456789012, + 'stock_quantity retains legacy decimal precision after decrease', + ); + + // Positive adjustment + adjustProductStock(db, { + productId: 'p-legacy-precision', + quantityDelta: 0.25, + movementType: 'adjustment', + actorUserId: owner.userId, + reason: 'Legacy plus 0.25 adjustment', + }); + assertEqual( + validateInventoryLedgerDatabase(db), + null, + 'adjustProductStock preserves ledger chain continuity for legacy stock after increase', + ); + const legacyAfterInc = db + .prepare('SELECT stock_quantity FROM products WHERE id = ?') + .get('p-legacy-precision') as { stock_quantity: number }; + assertEqual( + legacyAfterInc.stock_quantity, + 9.873456789012, + 'stock_quantity retains legacy decimal precision after increase', + ); + const manualIncrease = await api(baseUrl, `/api/products/${productId}/stock`, { method: 'POST', headers: owner.authHeader,