feat(M09-D2): 完成商品订单与库存占用生命周期
This commit is contained in:
@@ -167,6 +167,26 @@ function execute(sql, params) {
|
||||
&& item.inventoryId === String(params[1]) && item.requestId === String(params[2]));
|
||||
return [row ? [{ ...row }] : [], []];
|
||||
}
|
||||
if (sql.includes('AS reservedQuantity') && sql.includes('inventorySaleDelta')) {
|
||||
const inventoryIds = new Set(params.slice(4).map(String));
|
||||
const balances = new Map();
|
||||
for (const item of state.ledgers) {
|
||||
if (item.tenantId !== String(params[0]) || item.storeId !== String(params[1])
|
||||
|| item.businessType !== String(params[2]) || item.businessId !== String(params[3])
|
||||
|| !inventoryIds.has(item.inventoryId)
|
||||
|| !['DEDUCT', 'RETURN'].includes(item.operation)) continue;
|
||||
const metadata = JSON.parse(item.metadata);
|
||||
const fallback = item.operation === 'DEDUCT'
|
||||
? -Number(metadata.inventoryReservationDelta ?? item.lockedDelta)
|
||||
: Number(item.availableDelta);
|
||||
balances.set(item.inventoryId,
|
||||
(balances.get(item.inventoryId) ?? 0)
|
||||
+ Number(metadata.inventorySaleDelta ?? fallback));
|
||||
}
|
||||
return [[...balances.entries()].map(([inventoryId, reservedQuantity]) => ({
|
||||
inventoryId, reservedQuantity
|
||||
})), []];
|
||||
}
|
||||
if (sql.includes('AS reservedQuantity') && sql.includes('business_type = ?')) {
|
||||
const inventoryIds = new Set(params.slice(4).map(String));
|
||||
const balances = new Map();
|
||||
@@ -376,6 +396,51 @@ assert.deepEqual(
|
||||
[8, 0]
|
||||
);
|
||||
|
||||
const returned = await service.returnMany({
|
||||
...batchBase, requestId: 'order-return-1', reason: '退款成功回补',
|
||||
items: [{ skuId: '5', quantity: 1 }]
|
||||
});
|
||||
assert.equal(returned.idempotent, false);
|
||||
assert.equal(findStock('7', '11', '5').availableQuantity, 9);
|
||||
const returnedReplay = await service.returnMany({
|
||||
...batchBase, requestId: 'order-return-1', reason: '退款成功回补',
|
||||
items: [{ skuId: '5', quantity: 1 }]
|
||||
});
|
||||
assert.equal(returnedReplay.idempotent, true);
|
||||
assert.equal(findStock('7', '11', '5').availableQuantity, 9);
|
||||
await assert.rejects(
|
||||
() => service.returnMany({
|
||||
...batchBase, requestId: 'order-return-1', reason: '退款成功回补',
|
||||
items: [{ skuId: '5', quantity: 2 }]
|
||||
}),
|
||||
(error) => error instanceof InventoryError
|
||||
&& error.code === 'INVENTORY_IDEMPOTENCY_CONFLICT'
|
||||
);
|
||||
await assert.rejects(
|
||||
() => service.returnMany({
|
||||
...batchBase, requestId: 'foreign-order-return', businessId: 'order-foreign',
|
||||
reason: '跨订单退款不得回补', items: [{ skuId: '5', quantity: 1 }]
|
||||
}),
|
||||
(error) => error instanceof InventoryError
|
||||
&& error.code === 'INVENTORY_INSUFFICIENT_DEDUCTED'
|
||||
);
|
||||
await assert.rejects(
|
||||
() => service.returnMany({
|
||||
...batchBase, requestId: 'order-return-too-many', reason: '超额退款不得回补',
|
||||
items: [{ skuId: '5', quantity: 2 }]
|
||||
}),
|
||||
(error) => error instanceof InventoryError
|
||||
&& error.code === 'INVENTORY_INSUFFICIENT_DEDUCTED'
|
||||
);
|
||||
await assert.rejects(
|
||||
() => service.returnMany({
|
||||
...batchBase, requestId: 'order-return-case', businessId: 'ORDER-500',
|
||||
reason: '业务号大小写敏感', items: [{ skuId: '5', quantity: 1 }]
|
||||
}),
|
||||
(error) => error instanceof InventoryError
|
||||
&& error.code === 'INVENTORY_INSUFFICIENT_DEDUCTED'
|
||||
);
|
||||
|
||||
const beforeFailedBatch = structuredClone(state.stocks);
|
||||
await assert.rejects(
|
||||
() => service.lockMany({
|
||||
@@ -443,4 +508,4 @@ assert.ok(history.items.length >= 7);
|
||||
assert.ok(history.items.every((item) => item.metadata.idempotencyFingerprint));
|
||||
assert.equal(state.audits.length, state.ledgers.length);
|
||||
|
||||
console.log('PASS: M09-D1 inventory policy, inbound/adjust/stocktake/loss, ordered batch locks, idempotency, immutable ledger, scope and audit work.');
|
||||
console.log('PASS: M09-D1 inventory policy, batch reservation/sale return lifecycle, idempotency, immutable ledger, scope and audit work.');
|
||||
|
||||
Reference in New Issue
Block a user