Fas 2 steg 5: Scan-to-scan-diff + konfliktlösning, samt buggfix depleted-transaktion i reconciliation

This commit is contained in:
Sven (AAMOS AI)
2026-08-07 01:30:31 +07:00
parent 218bc44d08
commit 4a4f448e1c
26 changed files with 10590 additions and 13 deletions
+53
View File
@@ -4,6 +4,7 @@ import { eq, inArray } from "drizzle-orm";
import { buildServer } from "../src/server.js";
import { loadConfig } from "../src/config.js";
import { createDatabase, closeDatabase, schema } from "@app/database";
import { computeBalance } from "@app/inventory-engine";
describe("quick reconciliation", () => {
const testDb = createDatabase(process.env.TEST_DATABASE_URL!);
@@ -123,4 +124,56 @@ describe("quick reconciliation", () => {
expect(body.quantity).toBe(0.5);
expect(body.verifiedByUser).toBe(true);
});
it("transaction invariant holds for exists/depleted/uncertain", async () => {
const start = await app.inject({
method: "POST",
url: "/v1/reconciliations/start",
headers: { authorization: `Bearer ${token}` },
payload: {},
});
const { candidates } = JSON.parse(start.body) as { candidates: Array<{ itemId: string }> };
const itemId = candidates[0]!.itemId;
async function assertBalanceMatches(itemId: string) {
const [item] = await testDb.db
.select({ quantity: schema.inventoryItems.quantity })
.from(schema.inventoryItems)
.where(eq(schema.inventoryItems.id, itemId))
.limit(1);
const txs = await testDb.db
.select({ type: schema.inventoryTransactions.type, quantityDelta: schema.inventoryTransactions.quantityDelta, unit: schema.inventoryTransactions.unit })
.from(schema.inventoryTransactions)
.where(eq(schema.inventoryTransactions.inventoryItemId, itemId));
const balance = computeBalance(txs.map((tx) => ({ type: tx.type, quantityDelta: tx.quantityDelta, unit: tx.unit })));
expect(balance.balance).toBeCloseTo(item!.quantity, 5);
}
// exists med justering
await app.inject({
method: "POST",
url: `/v1/reconciliations/items/${itemId}/resolve`,
headers: { authorization: `Bearer ${token}` },
payload: { action: "exists", quantity: 0.7 },
});
await assertBalanceMatches(itemId);
// depleted ska alltid skriva transaktion
await app.inject({
method: "POST",
url: `/v1/reconciliations/items/${itemId}/resolve`,
headers: { authorization: `Bearer ${token}` },
payload: { action: "depleted" },
});
await assertBalanceMatches(itemId);
// uncertain med justering
await app.inject({
method: "POST",
url: `/v1/reconciliations/items/${itemId}/resolve`,
headers: { authorization: `Bearer ${token}` },
payload: { action: "uncertain", quantity: 0.2 },
});
await assertBalanceMatches(itemId);
});
});