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 order shipping ledger & stock adjustment ---'); const tempDir = mkdtempSync(join(tmpdir(), 'test-order-shipping-')); 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 { // 1. Setup company and user const googleAuthId = await store.google_auth.add('shipping-test@example.com'); const company = await store.company.create({ name: 'Shipping Test Corp', iso639: 'de', taxrate: 19, reducedtaxrate: 7, googleAuthId, }); await store.google_auth.setSelectedCompany(googleAuthId, company.id); // Setup warehouse const warehouse = await store.warehouse.create(googleAuthId, { name: 'Hauptlager', }); assert(warehouse.id, 'Warehouse should have id'); // Setup units await store.unit.upsert(googleAuthId, { si: 'stk', basesi: 'stk', basesifactor: 1, symbol: 'Stk', name: 'Stück', }); // Setup customer group & customer entity const groups = await store.customergroup.list(googleAuthId); const customer = await store.entity.create(googleAuthId, { customergroupId: groups[0].id, name: 'Max Mustermann', }); // 2. Setup tracked article and untracked article const trackedArt = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'Tracked Article', track_stock: 1, }); const untrackedArt = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'Untracked Service Item', track_stock: 0, }); // Initial stock for tracked article: +10 await store.stock.adjust(googleAuthId, { sku: trackedArt.sku, warehouseId: warehouse.id, amount: 10, text: 'Initial stock intake', }); const artBefore = await store.article.getBySku(googleAuthId, trackedArt.sku); const stockBefore = artBefore.article.warehouses.reduce((sum, w) => sum + w.stock, 0); assert.equal(stockBefore, 10, 'Initial stock should be 10'); // 3. Create sales order with tracked and untracked items const order = await store.order.create(googleAuthId, { number: 'O-2026-001', date: new Date().toISOString(), entityId: customer.id, lines: [ { sku: trackedArt.sku, name: 'Tracked Article', pu: 1, amount: 3, net: 50, taxrate: 19, }, { sku: untrackedArt.sku, name: 'Untracked Service Item', pu: 1, amount: 2, net: 20, taxrate: 19, }, ], }); assert.equal(order.lines.length, 2); // 4. Create shipping with state 'open' (not shipped yet) console.log('4. Creating shipping with state "open"...'); const shippingOpen = await store.shipping.create(googleAuthId, { manifestId: order.id, type: 'parcel', state: 'open', }); assert.equal(shippingOpen.state, 'open'); // Assert stock is still 10 const artStill10 = await store.article.getBySku(googleAuthId, trackedArt.sku); assert.equal(artStill10.article.warehouses.reduce((sum, w) => sum + w.stock, 0), 10, 'Stock should still be 10 when shipping is open'); // 5. Update shipping to state 'shipped' console.log('5. Updating shipping to state "shipped"...'); const shippingShipped = await store.shipping.update(googleAuthId, shippingOpen.id, { state: 'shipped', warehouseId: warehouse.id, }); assert.equal(shippingShipped.state, 'shipped'); // Assert stock is now 7 (10 - 3) const artAfter = await store.article.getBySku(googleAuthId, trackedArt.sku); assert.equal(artAfter.article.warehouses.reduce((sum, w) => sum + w.stock, 0), 7, 'Stock should be reduced to 7'); // Assert ledger entries const ledgerRows = await store.ledger.listBySku(googleAuthId, trackedArt.sku); assert.equal(ledgerRows.length, 2, 'Should have 2 ledger entries for tracked article (adjust + shipping)'); const shippingLedger = ledgerRows.find((l) => l.transaction_type === 'SHIPPING'); assert(shippingLedger, 'Shipping ledger entry should exist'); assert.equal(shippingLedger.amount, -3, 'Shipping ledger entry should be -3'); // Assert untracked article has 0 warehouse ledger entries const untrackedLedger = await store.ledger.listBySku(googleAuthId, untrackedArt.sku); assert.equal(untrackedLedger.length, 0, 'Untracked article should not have warehouse ledger entries'); // Assert article history: order goes into history, shipment does NOT const history = await store.article.getHistoryBySku(googleAuthId, trackedArt.sku); console.log('Tracked article history:', history.map((h) => h.text)); const orderHistory = history.find((h) => h.text === 'Auftrag O-2026-001 (Kunde: "Max Mustermann", Menge: 3, € 59,50)'); assert(orderHistory, 'Article history should contain order creation entry with formatted gross price'); assert(orderHistory.meta, 'Article history entry should have meta'); const parsedMeta = JSON.parse(orderHistory.meta); assert.equal(parsedMeta.type, 'order'); assert.equal(parsedMeta.orderId, order.id); assert.equal(parsedMeta.gross, 59.5); const shippingHistory = history.find((h) => h.text.includes('Versand Auftrag')); assert(!shippingHistory, 'Article history should NOT report the shipment'); // Untracked article also has the order in history, but not the shipment const untrackedHistory = await store.article.getHistoryBySku(googleAuthId, untrackedArt.sku); console.log('Untracked article history:', untrackedHistory.map((h) => h.text)); assert( untrackedHistory.some((h) => h.text === 'Auftrag O-2026-001 (Kunde: "Max Mustermann", Menge: 2, € 23,80)'), 'Untracked article history should contain order creation entry with formatted gross price', ); assert( !untrackedHistory.some((h) => h.text.includes('Versand Auftrag')), 'Untracked article history should NOT report the shipment', ); // Test order import with historical date preserves date in article history const historicalDate = '2023-04-15T10:30:00.000Z'; await store.order.create(googleAuthId, { number: 'O-HIST-001', date: historicalDate, createdAt: historicalDate, lines: [{ sku: trackedArt.sku, amount: 1, net: 10, taxrate: 19 }], }); const histWithOld = await store.article.getHistoryBySku(googleAuthId, trackedArt.sku); const oldEntry = histWithOld.find((h) => h.text.startsWith('Auftrag O-HIST-001')); assert(oldEntry, 'Historical order entry should exist in article history'); assert.equal(new Date(oldEntry.date).toISOString(), historicalDate, 'History entry should preserve historical order date'); const oldMeta = JSON.parse(oldEntry.meta); assert.equal(oldMeta.orderNumber, 'O-HIST-001'); // 6. Test immutability: once shipped, cannot be deleted console.log('6. Testing cannot delete shipped shipping...'); await assert.rejects( async () => { await store.shipping.delete(googleAuthId, shippingShipped.id); }, { message: 'Shipped shipping cannot be deleted' }, ); // 7. Test immutability: once shipped, cannot be undone console.log('7. Testing cannot undo shipped shipping...'); await assert.rejects( async () => { await store.shipping.update(googleAuthId, shippingShipped.id, { state: 'open' }); }, { message: 'Shipped shipping cannot be undone' }, ); // 8. Test order cannot be shipped again console.log('8. Testing order cannot be shipped twice...'); await assert.rejects( async () => { await store.shipping.create(googleAuthId, { manifestId: order.id, type: 'express', state: 'shipped', }); }, { message: 'Order already shipped' }, ); // 9. Test direct creation with state 'shipped' console.log('9. Testing direct creation with state "shipped"...'); const order2 = await store.order.create(googleAuthId, { number: 'O-2026-002', date: new Date().toISOString(), entityId: customer.id, lines: [ { sku: trackedArt.sku, name: 'Tracked Article', pu: 1, amount: 2, net: 50, taxrate: 19, }, ], }); const directShipped = await store.shipping.create(googleAuthId, { manifestId: order2.id, type: 'parcel', state: 'shipped', }); assert.equal(directShipped.state, 'shipped'); const artFinal = await store.article.getBySku(googleAuthId, trackedArt.sku); assert.equal(artFinal.article.warehouses.reduce((sum, w) => sum + w.stock, 0), 5, 'Stock should now be 5 (7 - 2)'); console.log('✓ All order shipping ledger tests passed successfully!'); } finally { try { rmSync(tempDir, { recursive: true, force: true }); } catch {} } } run().catch((err) => { console.error('Test failed:', err); process.exit(1); });