This commit is contained in:
seb
2026-09-30 17:35:22 +02:00
parent 280a45a11f
commit 894f72eba5
11 changed files with 802 additions and 10 deletions

View File

@@ -0,0 +1,146 @@
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import LibDashStore from '../src/index.js';
import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js';
async function run() {
console.log('--- Testing Stückliste Stock Reporting and Adjustment ---');
const tempDir = mkdtempSync(join(tmpdir(), 'test-stueckliste-'));
const dbFile = join(tempDir, 'test.db');
const storageDir = join(tempDir, 'storage');
const provider = new BetterSqlite3Provider({
filename: dbFile,
storageDir,
});
await provider.initialize();
const store = await new LibDashStore(provider);
try {
const googleAuthId = await store.google_auth.add('stueckliste-stock@example.com');
const company = await store.company.create({
name: 'Stückliste Stock Corp',
iso639: 'de',
taxrate: 19,
reducedtaxrate: 7,
googleAuthId,
});
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
// Create 2 warehouses
const wh1 = await store.warehouse.create(googleAuthId, { name: 'Hauptlager' });
const wh2 = await store.warehouse.create(googleAuthId, { name: 'Filiale' });
// Create 2 component articles
const comp1 = await store.article.createUnderParent({
googleAuthId,
parentId: company.rootCategoryId,
name: 'Component Article 1',
});
const comp2 = await store.article.createUnderParent({
googleAuthId,
parentId: company.rootCategoryId,
name: 'Component Article 2',
});
// Create a bundle article
const bundle = await store.article.createUnderParent({
googleAuthId,
parentId: company.rootCategoryId,
name: 'Stückliste Bundle',
});
// Configure Stückliste: 1x comp1 + 2x comp2
await store.articlelist.setItemsByArticleSku(googleAuthId, bundle.sku, [
{ sku: comp1.sku, count: 1, sort: 0 },
{ sku: comp2.sku, count: 2, sort: 1 },
]);
// Initial check: all have 0 stock
const initialBundle = await store.article.getBySku(googleAuthId, bundle.sku);
assert.equal(initialBundle.article.warehouses.find((w) => w.id === wh1.id).stock, 0);
assert.equal(initialBundle.article.warehouses.find((w) => w.id === wh2.id).stock, 0);
// Book 10x comp1 and 10x comp2 into Hauptlager
await store.stock.adjust(googleAuthId, {
lines: [
{ sku: comp1.sku, warehouseId: wh1.id, amount: 10, text: 'Initial comp1' },
{ sku: comp2.sku, warehouseId: wh1.id, amount: 10, text: 'Initial comp2' },
],
});
// Check bundle stock in Hauptlager: min(10/1, 10/2) = 5
const afterCompStock = await store.article.getBySku(googleAuthId, bundle.sku);
const wh1BundleStock = afterCompStock.article.warehouses.find((w) => w.id === wh1.id).stock;
const wh2BundleStock = afterCompStock.article.warehouses.find((w) => w.id === wh2.id).stock;
assert.equal(wh1BundleStock, 5, 'Bundle stock in Hauptlager should be 5 (min(10/1, 10/2))');
assert.equal(wh2BundleStock, 0, 'Bundle stock in Filiale should be 0');
// Book in 3x Stückliste bundle into Filiale
const adjustResult = await store.stock.adjust(googleAuthId, {
sku: bundle.sku,
warehouseId: wh2.id,
amount: 3,
text: 'Production bundle book-in',
});
// Verify adjust result lines
assert.equal(adjustResult.lines.length, 2, 'Should expand into 2 component lines');
const comp1Line = adjustResult.lines.find((l) => l.sku === comp1.sku);
const comp2Line = adjustResult.lines.find((l) => l.sku === comp2.sku);
assert.equal(comp1Line.amount, 3, 'Component 1 should get 3 * 1 = 3');
assert.equal(comp2Line.amount, 6, 'Component 2 should get 3 * 2 = 6');
// Verify bundle article itself has NO entries in the ledger
const bundleLedger = await store.ledger.listBySku(googleAuthId, bundle.sku);
assert.equal(bundleLedger.length, 0, 'Bundle article must have NO ledger entries');
// Verify component articles have their ledger entries
const comp1Ledger = await store.ledger.listBySku(googleAuthId, comp1.sku, { warehouse_id: wh2.id });
const comp2Ledger = await store.ledger.listBySku(googleAuthId, comp2.sku, { warehouse_id: wh2.id });
assert.equal(comp1Ledger.length, 1);
assert.equal(comp1Ledger[0].amount, 3);
assert.equal(comp2Ledger.length, 1);
assert.equal(comp2Ledger[0].amount, 6);
// Verify bundle stock report in Filiale is now 3 (min(3/1, 6/2) = 3)
const afterBundleBookIn = await store.article.getBySku(googleAuthId, bundle.sku);
const wh2AfterBookIn = afterBundleBookIn.article.warehouses.find((w) => w.id === wh2.id).stock;
assert.equal(wh2AfterBookIn, 3, 'Bundle stock in Filiale should be 3');
// Test booking out 1x bundle from Filiale
await store.stock.adjust(googleAuthId, {
sku: bundle.sku,
warehouseId: wh2.id,
amount: -1,
text: 'Sale bundle book-out',
});
const afterRemoval = await store.article.getBySku(googleAuthId, bundle.sku);
const wh2AfterRemoval = afterRemoval.article.warehouses.find((w) => w.id === wh2.id).stock;
assert.equal(wh2AfterRemoval, 2, 'Bundle stock in Filiale should be 2 after -1 removal');
// Verify components stock after removal in Filiale: comp1 = 2, comp2 = 4
const comp1After = await store.article.getBySku(googleAuthId, comp1.sku);
const comp2After = await store.article.getBySku(googleAuthId, comp2.sku);
assert.equal(comp1After.article.warehouses.find((w) => w.id === wh2.id).stock, 2);
assert.equal(comp2After.article.warehouses.find((w) => w.id === wh2.id).stock, 4);
console.log('✓ All Stückliste stock reporting and adjustment tests passed successfully!');
} finally {
try {
rmSync(tempDir, { recursive: true, force: true });
} catch {
// ignore
}
}
}
run().catch((err) => {
console.error('Test failed:', err);
process.exit(1);
});