u
This commit is contained in:
231
scripts/test-delivery-summary.js
Normal file
231
scripts/test-delivery-summary.js
Normal file
@@ -0,0 +1,231 @@
|
||||
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 Delivery Summary, Ledger Sync & Filtering in dashdbschema ---');
|
||||
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'test-delivery-summary-'));
|
||||
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('delivery-summary-test@example.com');
|
||||
const company = await store.company.create({
|
||||
name: 'Delivery Summary Corp',
|
||||
iso639: 'de',
|
||||
taxrate: 19,
|
||||
reducedtaxrate: 7,
|
||||
googleAuthId,
|
||||
});
|
||||
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
|
||||
|
||||
const warehouse = await store.warehouse.create(googleAuthId, { name: 'Hauptlager' });
|
||||
await store.unit.upsert(googleAuthId, {
|
||||
si: 'stk',
|
||||
basesi: 'stk',
|
||||
basesifactor: 1,
|
||||
symbol: 'Stk',
|
||||
name: 'Stück',
|
||||
});
|
||||
|
||||
const supplierAlpha = await store.supplier.create(googleAuthId, {
|
||||
name: 'Alpha Supplier GmbH',
|
||||
});
|
||||
const supplierBeta = await store.supplier.create(googleAuthId, {
|
||||
name: 'Beta Logistics Inc',
|
||||
});
|
||||
|
||||
// 1. Create articles:
|
||||
// Art1: track_stock = 1 (Stock item)
|
||||
const artStock1 = await store.article.createUnderParent({
|
||||
googleAuthId,
|
||||
parentId: company.rootCategoryId,
|
||||
name: 'Stock Item 1',
|
||||
track_stock: 1,
|
||||
});
|
||||
|
||||
// Art2: track_stock = 1 (Stock item)
|
||||
const artStock2 = await store.article.createUnderParent({
|
||||
googleAuthId,
|
||||
parentId: company.rootCategoryId,
|
||||
name: 'Stock Item 2',
|
||||
track_stock: 1,
|
||||
});
|
||||
|
||||
// Art3: track_stock = 0 (Service / Non-stock item)
|
||||
const artService = await store.article.createUnderParent({
|
||||
googleAuthId,
|
||||
parentId: company.rootCategoryId,
|
||||
name: 'Shipping Freight Fee',
|
||||
track_stock: 0,
|
||||
});
|
||||
|
||||
// 2. Create Delivery 1:
|
||||
// Has 2 lines: artStock1 (qty 10), artService (qty 1)
|
||||
const delivery1 = await store.delivery.create(googleAuthId, {
|
||||
date: '2026-08-01T10:00:00Z',
|
||||
externalNumber: 'DEL-ALPHA-1001',
|
||||
supplierId: supplierAlpha.id,
|
||||
lines: [
|
||||
{ sku: artStock1.sku, name: 'Stock Item 1', amount: 10, net: 50, taxrate: 19 },
|
||||
{ sku: artService.sku, name: 'Shipping Freight Fee', amount: 1, net: 10, taxrate: 19 },
|
||||
],
|
||||
});
|
||||
|
||||
console.log('Delivery 1 created with id:', delivery1.id);
|
||||
|
||||
// Verify deliverysummary row for delivery 1:
|
||||
// positions = 2, total_quantity = 11, stock_positions = 1, booked_positions = 0, unbooked_positions = 1, is_booked = 0
|
||||
let sum1 = provider.db.prepare('SELECT * FROM deliverysummary WHERE manifest_id = ?').get(delivery1.id);
|
||||
assert(sum1, 'deliverysummary row for delivery 1 must exist');
|
||||
assert.equal(sum1.positions, 2, 'Total positions should be 2');
|
||||
assert.equal(sum1.stock_positions, 1, 'Stock positions should be 1');
|
||||
assert.equal(sum1.booked_positions, 0, 'Booked positions should initially be 0');
|
||||
assert.equal(sum1.unbooked_positions, 1, 'Unbooked positions should initially be 1');
|
||||
assert.equal(sum1.stock_quantity, 10, 'Stock quantity should be 10');
|
||||
assert.equal(sum1.booked_quantity, 0, 'Booked quantity should initially be 0');
|
||||
assert.equal(sum1.unbooked_quantity, 10, 'Unbooked quantity should initially be 10');
|
||||
assert.equal(sum1.is_booked, 0, 'is_booked should be 0');
|
||||
assert.equal(sum1.supplier_name, 'Alpha Supplier GmbH');
|
||||
|
||||
// 3. Create Delivery 2:
|
||||
// Has 1 line: artStock2 (qty 5), Supplier: Beta
|
||||
const delivery2 = await store.delivery.create(googleAuthId, {
|
||||
date: '2026-10-01T10:00:00Z',
|
||||
externalNumber: 'DEL-BETA-2002',
|
||||
supplierId: supplierBeta.id,
|
||||
lines: [
|
||||
{ sku: artStock2.sku, name: 'Stock Item 2', amount: 5, net: 20, taxrate: 19 },
|
||||
],
|
||||
});
|
||||
|
||||
let sum2 = provider.db.prepare('SELECT * FROM deliverysummary WHERE manifest_id = ?').get(delivery2.id);
|
||||
assert(sum2, 'deliverysummary row for delivery 2 must exist');
|
||||
assert.equal(sum2.stock_positions, 1);
|
||||
assert.equal(sum2.unbooked_positions, 1);
|
||||
assert.equal(sum2.is_booked, 0);
|
||||
|
||||
// 4. Book Delivery 1 partially:
|
||||
// Book 4 units of artStock1 in warehouse
|
||||
const lineStock1 = delivery1.lines.find((l) => l.sku === artStock1.sku);
|
||||
assert(lineStock1, 'lineStock1 must exist');
|
||||
|
||||
await store.ledger.add(googleAuthId, {
|
||||
manifestarticle_id: lineStock1.id,
|
||||
warehouse_id: warehouse.id,
|
||||
transaction_type: 'DELIVERY',
|
||||
amount: 4,
|
||||
transaction_text: 'Partial goods receipt',
|
||||
});
|
||||
|
||||
sum1 = provider.db.prepare('SELECT * FROM deliverysummary WHERE manifest_id = ?').get(delivery1.id);
|
||||
assert.equal(sum1.booked_positions, 0, 'Booked positions should still be 0 since 4 < 10');
|
||||
assert.equal(sum1.unbooked_positions, 1);
|
||||
assert.equal(sum1.booked_quantity, 4);
|
||||
assert.equal(sum1.unbooked_quantity, 6);
|
||||
assert.equal(sum1.is_booked, 0);
|
||||
console.log('✓ Partial booking properly updated deliverysummary');
|
||||
|
||||
// 5. Book remaining 6 units of artStock1
|
||||
await store.ledger.add(googleAuthId, {
|
||||
manifestarticle_id: lineStock1.id,
|
||||
warehouse_id: warehouse.id,
|
||||
transaction_type: 'DELIVERY',
|
||||
amount: 6,
|
||||
transaction_text: 'Final goods receipt',
|
||||
});
|
||||
|
||||
sum1 = provider.db.prepare('SELECT * FROM deliverysummary WHERE manifest_id = ?').get(delivery1.id);
|
||||
assert.equal(sum1.booked_positions, 1, 'Booked positions should now be 1');
|
||||
assert.equal(sum1.unbooked_positions, 0, 'Unbooked positions should now be 0');
|
||||
assert.equal(sum1.booked_quantity, 10);
|
||||
assert.equal(sum1.unbooked_quantity, 0);
|
||||
assert.equal(sum1.is_booked, 1, 'Delivery 1 should now be fully booked (is_booked = 1)');
|
||||
console.log('✓ Full booking properly updated deliverysummary (is_booked = 1)');
|
||||
|
||||
// 6. Test windowed / paged list:
|
||||
// Both deliveries present
|
||||
const pagedAll = await store.delivery.list(googleAuthId, { offset: 0, limit: 10 });
|
||||
assert.equal(pagedAll.total, 2);
|
||||
assert.equal(pagedAll.unfilteredTotal, 2);
|
||||
assert.equal(pagedAll.deliveries.length, 2);
|
||||
assert.equal(pagedAll.offset, 0);
|
||||
assert.equal(pagedAll.limit, 10);
|
||||
|
||||
// Filter onlyUnbooked (Delivery 1 is fully booked, Delivery 2 is unbooked)
|
||||
const pagedUnbooked = await store.delivery.list(googleAuthId, { onlyUnbooked: true });
|
||||
assert.equal(pagedUnbooked.total, 1, 'Only 1 delivery is unbooked');
|
||||
assert.equal(pagedUnbooked.unfilteredTotal, 2, 'Unfiltered total remains 2');
|
||||
assert.equal(pagedUnbooked.deliveries[0].id, delivery2.id);
|
||||
console.log('✓ onlyUnbooked filter returned only unbooked deliveries');
|
||||
|
||||
// Search filter: 'Beta'
|
||||
const searchBeta = await store.delivery.list(googleAuthId, { search: 'Beta' });
|
||||
assert.equal(searchBeta.total, 1);
|
||||
assert.equal(searchBeta.unfilteredTotal, 2);
|
||||
assert.equal(searchBeta.deliveries[0].id, delivery2.id);
|
||||
|
||||
// Search filter: 'ALPHA-1001'
|
||||
const searchNum = await store.delivery.list(googleAuthId, { search: 'ALPHA-1001' });
|
||||
assert.equal(searchNum.total, 1);
|
||||
assert.equal(searchNum.deliveries[0].id, delivery1.id);
|
||||
|
||||
// Test unpaged backwards compatibility
|
||||
const unpaged = await store.delivery.list(googleAuthId);
|
||||
assert(Array.isArray(unpaged), 'Unpaged list should return Array');
|
||||
assert.equal(unpaged.length, 2);
|
||||
const d1 = unpaged.find((d) => d.id === delivery1.id);
|
||||
assert(d1.ledger, 'd1 should contain ledger summary object');
|
||||
assert.equal(d1.ledger.totalPositions, 1);
|
||||
assert.equal(d1.ledger.bookedPositions, 1);
|
||||
assert.equal(d1.ledger.unbookedPositions, 0);
|
||||
assert.equal(d1.ledger.isBooked, true);
|
||||
|
||||
const d2 = unpaged.find((d) => d.id === delivery2.id);
|
||||
assert.equal(d2.ledger.unbookedPositions, 1);
|
||||
assert.equal(d2.ledger.isBooked, false);
|
||||
console.log('✓ Unpaged list returns enriched deliveries with ledger summary');
|
||||
|
||||
// 7. Test rebuildSummaries
|
||||
provider.db.prepare('DELETE FROM deliverysummary').run();
|
||||
const countEmpty = provider.db.prepare('SELECT COUNT(*) AS cnt FROM deliverysummary').get().cnt;
|
||||
assert.equal(countEmpty, 0);
|
||||
|
||||
await store.delivery.rebuildSummaries(googleAuthId);
|
||||
const countRebuilt = provider.db.prepare('SELECT COUNT(*) AS cnt FROM deliverysummary').get().cnt;
|
||||
assert.equal(countRebuilt, 2, 'Rebuilt deliverysummary should have 2 rows');
|
||||
|
||||
const rebuiltD1 = provider.db.prepare('SELECT * FROM deliverysummary WHERE manifest_id = ?').get(delivery1.id);
|
||||
assert.equal(rebuiltD1.is_booked, 1);
|
||||
assert.equal(rebuiltD1.booked_quantity, 10);
|
||||
assert.equal(rebuiltD1.unbooked_quantity, 0);
|
||||
|
||||
const rebuiltD2 = provider.db.prepare('SELECT * FROM deliverysummary WHERE manifest_id = ?').get(delivery2.id);
|
||||
assert.equal(rebuiltD2.is_booked, 0);
|
||||
assert.equal(rebuiltD2.unbooked_positions, 1);
|
||||
console.log('✓ rebuildSummaries restored all deliverysummary rows accurately');
|
||||
|
||||
console.log('--- ALL DELIVERY SUMMARY & FILTER TESTS PASSED ---');
|
||||
} finally {
|
||||
try {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user