Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions main/services/inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof getDatabase>,
options: StockChangeOptions,
Expand Down Expand Up @@ -213,6 +224,10 @@ export function adjustProductStock(
}
}
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
Expand All @@ -230,15 +245,15 @@ export function adjustProductStock(
normalizedQuantityDelta,
INVENTORY_QUANTITY_TOLERANCE,
normalizedQuantityDelta,
INVENTORY_QUANTITY_PRECISION,
targetPrecision,
updatedAt,
...stockIdArgs,
normalizedQuantityDelta,
INVENTORY_QUANTITY_TOLERANCE,
normalizedQuantityDelta,
INVENTORY_QUANTITY_TOLERANCE,
normalizedQuantityDelta,
INVENTORY_QUANTITY_PRECISION,
targetPrecision,
);
if (result.changes !== 1) {
if (!current) {
Expand All @@ -252,9 +267,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 (
Expand Down
63 changes: 63 additions & 0 deletions tests/inventory-ledger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const {
closeDatabase,
now,
validateInventoryLedgerReplacement,
validateInventoryLedgerDatabase,
} = require('../main/db');
const { adjustProductStock } = require('../main/services/inventory');
const {
Expand Down Expand Up @@ -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,
Expand Down
Loading