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);
|
||||
});
|
||||
@@ -5,12 +5,10 @@ import { join } from 'node:path';
|
||||
import { createHash } from 'node:crypto';
|
||||
import LibDashStore from '../src/index.js';
|
||||
import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js';
|
||||
import { INIT_HASH, computePrevHash } from '../src/store/ledger/add.js';
|
||||
import { computePrevHash, INIT_HASH } from '../src/store/ledger/add.js';
|
||||
|
||||
async function run() {
|
||||
console.log('--- Testing store.ledger (add, created_at, list queries) ---');
|
||||
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'test-ledger-'));
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'dash-ledger-test-'));
|
||||
const dbFile = join(tempDir, 'test.db');
|
||||
const storageDir = join(tempDir, 'storage');
|
||||
|
||||
@@ -18,50 +16,85 @@ async function run() {
|
||||
filename: dbFile,
|
||||
storageDir,
|
||||
});
|
||||
|
||||
await provider.initialize();
|
||||
const store = await new LibDashStore(provider);
|
||||
|
||||
try {
|
||||
// 1. Setup user and company
|
||||
const googleAuthId = await store.google_auth.add('ledger-test@example.com');
|
||||
console.log('--- Testing store.ledger (acceptance, warehouse, shipment, guards) ---');
|
||||
const googleAuthId = await store.google_auth.add('ledger-tester@example.com');
|
||||
|
||||
const company = await store.company.create({
|
||||
name: 'Ledger Test Corp',
|
||||
name: 'Ledger Test GmbH',
|
||||
iso639: 'de',
|
||||
taxrate: 19,
|
||||
reducedtaxrate: 7,
|
||||
googleAuthId,
|
||||
});
|
||||
|
||||
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
|
||||
|
||||
await store.unit.upsert(googleAuthId, {
|
||||
si: 'stk',
|
||||
basesi: 'stk',
|
||||
basesifactor: 1,
|
||||
symbol: 'Stk',
|
||||
name: 'Stück',
|
||||
const supplier = await store.supplier.create(googleAuthId, {
|
||||
name: 'Supplier X',
|
||||
});
|
||||
const supplier = await store.supplier.create(googleAuthId, { name: 'Supplier X' });
|
||||
|
||||
// Create an article with track_stock = false initially
|
||||
const article = await store.article.createUnderParent({
|
||||
googleAuthId,
|
||||
parentId: company.rootCategoryId,
|
||||
name: 'Ledger Linked Article',
|
||||
track_stock: false,
|
||||
});
|
||||
assert.equal(article.track_stock, false, 'Newly created article must have track_stock disabled');
|
||||
|
||||
await assert.rejects(
|
||||
async () => store.article.setTrackStockBySku(googleAuthId, article.sku, true),
|
||||
/At least one warehouse is required to enable stock tracking/,
|
||||
'Should reject enabling stock tracking when no warehouse exists',
|
||||
);
|
||||
|
||||
await store.articlesupplier.create(googleAuthId, article.sku, {
|
||||
entityId: supplier.id,
|
||||
sku: 'SKU-ABC',
|
||||
pu: 1,
|
||||
});
|
||||
|
||||
// Create a delivery manifest with lines
|
||||
const customer1 = await store.entity.create(googleAuthId, {
|
||||
name: 'Customer Quote Test',
|
||||
isCustomer: true,
|
||||
customergroupId: company.rootCategoryId,
|
||||
});
|
||||
|
||||
// Create a quote and verify ledger bookings are rejected
|
||||
const quote = await store.quote.create(googleAuthId, {
|
||||
number: 'Q-1',
|
||||
entityId: customer1.id,
|
||||
date: new Date().toISOString(),
|
||||
lines: [{ sku: 'SKU-ABC', name: 'Quote item', pu: 1, amount: 2, net: 10, taxrate: 19 }],
|
||||
});
|
||||
await assert.rejects(
|
||||
async () => store.ledger.add(googleAuthId, {
|
||||
manifestarticle_id: quote.lines[0].id,
|
||||
transaction_type: 'ACCEPTANCE',
|
||||
amount: 2,
|
||||
transaction_text: 'Should fail for quote',
|
||||
}),
|
||||
/Cannot book ledger entries for quote/,
|
||||
'Should reject booking ledger entries for quote manifest',
|
||||
);
|
||||
|
||||
// Create an order and verify ledger bookings are rejected
|
||||
const order = await store.order.create(googleAuthId, {
|
||||
number: 'O-1',
|
||||
entityId: customer1.id,
|
||||
date: new Date().toISOString(),
|
||||
lines: [{ sku: 'SKU-ABC', name: 'Order item', pu: 1, amount: 2, net: 10, taxrate: 19 }],
|
||||
});
|
||||
await assert.rejects(
|
||||
async () => store.ledger.add(googleAuthId, {
|
||||
manifestarticle_id: order.lines[0].id,
|
||||
transaction_type: 'ACCEPTANCE',
|
||||
amount: 2,
|
||||
transaction_text: 'Should fail for order',
|
||||
}),
|
||||
/Cannot book ledger entries for order/,
|
||||
'Should reject booking ledger entries for order manifest',
|
||||
);
|
||||
|
||||
// 1. Create a delivery manifest with lines
|
||||
const delivery = await store.delivery.create(googleAuthId, {
|
||||
supplierId: supplier.id,
|
||||
externalNumber: 'DEL-1',
|
||||
@@ -78,7 +111,7 @@ async function run() {
|
||||
},
|
||||
{
|
||||
sku: 'SKU-ABC',
|
||||
name: 'Ledger Item 1 Batch 2',
|
||||
name: 'Ledger Item 2',
|
||||
pu: 1,
|
||||
amount: 10,
|
||||
net: 20,
|
||||
@@ -90,278 +123,311 @@ async function run() {
|
||||
|
||||
const manifestarticleId1 = delivery.lines[0].id;
|
||||
const manifestarticleId2 = delivery.lines[1].id;
|
||||
assert(manifestarticleId1, 'manifestarticle line 1 should have an id');
|
||||
assert(manifestarticleId2, 'manifestarticle line 2 should have an id');
|
||||
|
||||
// 2. Test first row (genesis row)
|
||||
console.log('1. Inserting genesis ledger row...');
|
||||
// 2. Book line 1 for untracked article (acceptance only, warehouse_id is null)
|
||||
console.log('1. Booking acceptance for untracked article line 1...');
|
||||
const row1 = await store.ledger.add(googleAuthId, {
|
||||
manifestarticle_id: manifestarticleId1,
|
||||
transaction_type: 'GOODS_RECEIPT',
|
||||
transaction_type: 'ACCEPTANCE',
|
||||
amount: 5,
|
||||
transaction_text: 'Initial delivery movement',
|
||||
transaction_text: `Lieferung ${delivery.number}`,
|
||||
});
|
||||
|
||||
assert.equal(row1.id, 1);
|
||||
assert.equal(row1.manifestarticle_id, manifestarticleId1);
|
||||
assert.equal(row1.transaction_type, 'GOODS_RECEIPT');
|
||||
assert.equal(row1.warehouse_id, null);
|
||||
assert.equal(row1.transaction_type, 'ACCEPTANCE');
|
||||
assert.equal(row1.amount, 5);
|
||||
assert.equal(row1.transaction_text, 'Initial delivery movement');
|
||||
assert.equal(row1.hash_version, 1);
|
||||
assert.equal(row1.prev_hash, INIT_HASH);
|
||||
assert(row1.created_at, 'created_at should exist');
|
||||
assert(!Number.isNaN(new Date(row1.created_at).getTime()), 'created_at should be valid date string');
|
||||
console.log('✓ Row 1 created_at:', row1.created_at);
|
||||
|
||||
// Verify manual hash computation of row 1 (including created_at)
|
||||
// Attempting to book line 1 again must be rejected
|
||||
await assert.rejects(
|
||||
async () => store.ledger.add(googleAuthId, {
|
||||
manifestarticle_id: manifestarticleId1,
|
||||
transaction_type: 'ACCEPTANCE',
|
||||
amount: 5,
|
||||
transaction_text: 'Double booking attempt',
|
||||
}),
|
||||
/Delivery line already booked on ledger/,
|
||||
'Should reject booking an already booked delivery line',
|
||||
);
|
||||
|
||||
// 3. Create warehouse and enable tracking on the article
|
||||
console.log('2. Creating warehouse and enabling stock tracking...');
|
||||
const wh1 = await store.warehouse.create(googleAuthId, { name: 'Hauptlager' });
|
||||
await store.article.setTrackStockBySku(googleAuthId, article.sku, true);
|
||||
|
||||
// Even though tracking is now enabled, line 1 is already accepted so it cannot be booked again
|
||||
await assert.rejects(
|
||||
async () => store.ledger.add(googleAuthId, {
|
||||
manifestarticle_id: manifestarticleId1,
|
||||
warehouse_id: wh1.id,
|
||||
transaction_type: 'ACCEPTANCE',
|
||||
amount: 5,
|
||||
transaction_text: 'Late booking attempt after tracking enabled',
|
||||
}),
|
||||
/Delivery line already booked on ledger/,
|
||||
'Cannot book a delivery line later once stock tracking is enabled if already accepted',
|
||||
);
|
||||
|
||||
// 4. Book line 2 (now tracked): books ACCEPTANCE (row 2) + WAREHOUSE (row 3) in one transaction
|
||||
console.log('3. Booking line 2 with tracking enabled (dual entries: ACCEPTANCE + WAREHOUSE)...');
|
||||
const expectedHash2 = computePrevHash(row1);
|
||||
const manualHash2 = createHash('sha512')
|
||||
.update(
|
||||
JSON.stringify({
|
||||
id: 1,
|
||||
manifestarticle_id: manifestarticleId1,
|
||||
warehouse_id: null,
|
||||
transaction_type: 'GOODS_RECEIPT',
|
||||
amount: 5,
|
||||
transaction_text: 'Initial delivery movement',
|
||||
hash_version: 1,
|
||||
prev_hash: INIT_HASH,
|
||||
created_at: new Date(row1.created_at).toISOString(),
|
||||
}),
|
||||
'utf8',
|
||||
)
|
||||
.digest('hex');
|
||||
assert.equal(expectedHash2, manualHash2);
|
||||
assert.equal(manualHash2.length, 128);
|
||||
|
||||
// 3. Test second row (line 1, different transaction type)
|
||||
console.log('2. Inserting second ledger row...');
|
||||
const row2 = await store.ledger.add(googleAuthId, {
|
||||
manifestarticleId: manifestarticleId1,
|
||||
transactionType: 'INVENTORY_ADJUST',
|
||||
amount: -1,
|
||||
transactionText: 'Damaged item written off',
|
||||
manifestarticle_id: manifestarticleId2,
|
||||
warehouse_id: wh1.id,
|
||||
transaction_type: 'ACCEPTANCE',
|
||||
amount: 10,
|
||||
transaction_text: `Lieferung ${delivery.number}`,
|
||||
});
|
||||
|
||||
assert.equal(row2.id, 2);
|
||||
assert.equal(row2.hash_version, 1);
|
||||
assert.equal(row2.manifestarticle_id, manifestarticleId2);
|
||||
assert.equal(row2.warehouse_id, null);
|
||||
assert.equal(row2.transaction_type, 'ACCEPTANCE');
|
||||
assert.equal(row2.amount, 10);
|
||||
assert.equal(row2.prev_hash, expectedHash2);
|
||||
assert(row2.created_at, 'row 2 created_at should exist');
|
||||
console.log('✓ Row 2 prev_hash matches SHA-512 of Row 1 with created_at');
|
||||
|
||||
// 4. Test third row (line 2, same SKU SKU-ABC)
|
||||
console.log('3. Inserting third ledger row on line 2...');
|
||||
const expectedHash3 = computePrevHash(row2);
|
||||
const row3 = await store.ledger.add(googleAuthId, {
|
||||
manifestarticle_id: manifestarticleId2,
|
||||
transaction_type: 'GOODS_RECEIPT',
|
||||
amount: 10,
|
||||
transaction_text: 'Second line goods receipt',
|
||||
});
|
||||
// Query ledger lines for manifestarticle 2 to verify row 3 (WAREHOUSE entry)
|
||||
const line2Entries = await store.ledger.listByManifestarticleId(googleAuthId, manifestarticleId2);
|
||||
assert.equal(line2Entries.length, 2, 'Line 2 must have 2 ledger entries (ACCEPTANCE + WAREHOUSE)');
|
||||
assert.equal(line2Entries[0].transaction_type, 'ACCEPTANCE');
|
||||
assert.equal(line2Entries[0].warehouse_id, null);
|
||||
assert.equal(line2Entries[1].transaction_type, 'WAREHOUSE');
|
||||
assert.equal(line2Entries[1].warehouse_id, wh1.id);
|
||||
assert.equal(line2Entries[1].amount, 10);
|
||||
assert.equal(line2Entries[1].id, 3);
|
||||
assert.equal(line2Entries[1].prev_hash, computePrevHash(line2Entries[0]));
|
||||
|
||||
assert.equal(row3.id, 3);
|
||||
assert.equal(row3.hash_version, 1);
|
||||
assert.equal(row3.prev_hash, expectedHash3);
|
||||
// 5. Check warehouse stock for article
|
||||
console.log('4. Checking warehouse stock calculation...');
|
||||
const artDetail = await store.article.getBySku(googleAuthId, article.sku);
|
||||
const whStock = artDetail.article.warehouses.find((w) => w.id === wh1.id)?.stock;
|
||||
assert.equal(whStock, 10, 'Warehouse stock must be 10 (from line 2 WAREHOUSE ledger entry only)');
|
||||
|
||||
// 5. Test query by manifestarticle_id
|
||||
console.log('4. Testing listByManifestarticleId...');
|
||||
const listLine1All = await store.ledger.listByManifestarticleId(googleAuthId, manifestarticleId1);
|
||||
assert.equal(listLine1All.length, 2);
|
||||
assert.equal(listLine1All[0].id, 1);
|
||||
assert.equal(listLine1All[1].id, 2);
|
||||
assert.equal(listLine1All[0].sku, 'SKU-ABC');
|
||||
// 6. Check deliverysummary is_booked
|
||||
const updatedDelivery = await store.delivery.getById(googleAuthId, delivery.id);
|
||||
assert.equal(updatedDelivery.ledger.isBooked, true, 'Delivery should be fully booked');
|
||||
assert.equal(updatedDelivery.ledger.completelyOnLedgerPositions, 2);
|
||||
|
||||
const listLine1GoodsReceipt = await store.ledger.listByManifestarticleId(
|
||||
googleAuthId,
|
||||
manifestarticleId1,
|
||||
'GOODS_RECEIPT',
|
||||
);
|
||||
assert.equal(listLine1GoodsReceipt.length, 1);
|
||||
assert.equal(listLine1GoodsReceipt[0].id, 1);
|
||||
|
||||
const listLine1Adjust = await store.ledger.listByManifestarticleId(
|
||||
googleAuthId,
|
||||
manifestarticleId1,
|
||||
{ transaction_type: 'INVENTORY_ADJUST' },
|
||||
);
|
||||
assert.equal(listLine1Adjust.length, 1);
|
||||
assert.equal(listLine1Adjust[0].id, 2);
|
||||
|
||||
const listLine1None = await store.ledger.listByManifestarticleId(
|
||||
googleAuthId,
|
||||
manifestarticleId1,
|
||||
'NON_EXISTENT',
|
||||
);
|
||||
assert.equal(listLine1None.length, 0);
|
||||
|
||||
// 6. Test query by SKU
|
||||
console.log('5. Testing listBySku...');
|
||||
const listSkuAll = await store.ledger.listBySku(googleAuthId, 'SKU-ABC');
|
||||
assert.equal(listSkuAll.length, 3);
|
||||
assert.equal(listSkuAll[0].id, 1);
|
||||
assert.equal(listSkuAll[1].id, 2);
|
||||
assert.equal(listSkuAll[2].id, 3);
|
||||
|
||||
const listSkuGoodsReceipt = await store.ledger.listBySku(
|
||||
googleAuthId,
|
||||
'SKU-ABC',
|
||||
'GOODS_RECEIPT',
|
||||
);
|
||||
assert.equal(listSkuGoodsReceipt.length, 2);
|
||||
assert.equal(listSkuGoodsReceipt[0].id, 1);
|
||||
assert.equal(listSkuGoodsReceipt[1].id, 3);
|
||||
|
||||
const listSkuAdjust = await store.ledger.listBySku(
|
||||
googleAuthId,
|
||||
'SKU-ABC',
|
||||
'INVENTORY_ADJUST',
|
||||
);
|
||||
assert.equal(listSkuAdjust.length, 1);
|
||||
assert.equal(listSkuAdjust[0].id, 2);
|
||||
|
||||
const listUnknownSku = await store.ledger.listBySku(googleAuthId, 'UNKNOWN-SKU');
|
||||
assert.equal(listUnknownSku.length, 0);
|
||||
|
||||
// 8. Test article history created from ledger
|
||||
console.log('7. Testing article history entries from ledger rows...');
|
||||
// 7. Check article history
|
||||
console.log('5. Verifying article history...');
|
||||
const history = await store.article.getHistoryBySku(googleAuthId, article.sku);
|
||||
const historyTexts = history.map((h) => h.text);
|
||||
console.log('Article history texts:', historyTexts);
|
||||
console.log('History entries:', historyTexts);
|
||||
|
||||
assert(
|
||||
historyTexts.some((t) => t.includes('Initial delivery movement') && t.includes('Lieferant: "Supplier X"') && t.includes('EK brutto: 11.90') && t.includes('Menge: 5')),
|
||||
'History should include initial delivery movement with supplier, EK brutto, and amount',
|
||||
historyTexts.some((t) => t.includes(`Lieferung ${delivery.number}`) && t.includes('Lieferant: "Supplier X"') && t.includes('EK brutto: 11.90') && t.includes('Menge: 5')),
|
||||
'History must include acceptance of line 1 with Lieferung number, supplier, EK brutto, amount',
|
||||
);
|
||||
assert(historyTexts.includes('Damaged item written off'), 'History should include undamaged adjustment memo as is');
|
||||
assert(
|
||||
historyTexts.some((t) => t.includes('Second line goods receipt') && t.includes('Lieferant: "Supplier X"') && t.includes('EK brutto: 23.80') && t.includes('Menge: 10')),
|
||||
'History should include second line goods receipt with details',
|
||||
historyTexts.some((t) => t.includes(`Lieferung ${delivery.number}`) && t.includes('Lieferant: "Supplier X"') && t.includes('EK brutto: 23.80') && t.includes('Menge: 10')),
|
||||
'History must include acceptance of line 2',
|
||||
);
|
||||
assert(
|
||||
historyTexts.some((t) => t.includes(`Lieferung ${delivery.number}`) && t.includes('Lager: "Hauptlager"') && t.includes('Menge: 10')),
|
||||
'History must include warehouse entry for tracked line 2',
|
||||
);
|
||||
|
||||
// 9. Test warehouse CRUD, sort, reorder, and linking with ledger
|
||||
console.log('8. Testing warehouse CRUD, sort, reorder, and ledger association...');
|
||||
const wh1 = await store.warehouse.create(googleAuthId, { name: 'Hauptlager' });
|
||||
assert.equal(wh1.name, 'Hauptlager');
|
||||
assert.equal(wh1.sort, 0);
|
||||
assert(wh1.id > 0);
|
||||
// 8. Test store.delivery.book on a fresh delivery
|
||||
console.log('6. Testing store.delivery.book batch booking...');
|
||||
const delivery2 = await store.delivery.create(googleAuthId, {
|
||||
supplierId: supplier.id,
|
||||
externalNumber: 'DEL-2',
|
||||
date: new Date().toISOString(),
|
||||
lines: [
|
||||
{ sku: 'SKU-ABC', name: 'Batch Item 1', pu: 1, amount: 4, net: 15, taxrate: 19 },
|
||||
],
|
||||
});
|
||||
const bookedDelivery2 = await store.delivery.book(googleAuthId, { id: delivery2.id, warehouseId: wh1.id });
|
||||
assert.equal(bookedDelivery2.ledger.isBooked, true);
|
||||
assert.equal(bookedDelivery2.ledger.completelyOnLedgerPositions, 1);
|
||||
|
||||
const wh2 = await store.warehouse.create(googleAuthId, { name: 'Filiale Nord' });
|
||||
assert.equal(wh2.name, 'Filiale Nord');
|
||||
assert.equal(wh2.sort, 1);
|
||||
|
||||
const whList = await store.warehouse.list(googleAuthId);
|
||||
assert.equal(whList.length, 2);
|
||||
assert.equal(whList[0].id, wh1.id);
|
||||
assert.equal(whList[0].sort, 0);
|
||||
assert.equal(whList[1].id, wh2.id);
|
||||
assert.equal(whList[1].sort, 1);
|
||||
|
||||
const whGet = await store.warehouse.getById(googleAuthId, wh1.id);
|
||||
assert.equal(whGet.name, 'Hauptlager');
|
||||
assert.equal(whGet.sort, 0);
|
||||
|
||||
const whUpdated = await store.warehouse.update(googleAuthId, wh2.id, { name: 'Filiale Süd', sort: 5 });
|
||||
assert.equal(whUpdated.name, 'Filiale Süd');
|
||||
assert.equal(whUpdated.sort, 5);
|
||||
|
||||
// Test warehouse order
|
||||
await store.warehouse.order(googleAuthId, { warehouseIds: [wh2.id, wh1.id] });
|
||||
const whListReordered = await store.warehouse.list(googleAuthId);
|
||||
assert.equal(whListReordered[0].id, wh2.id);
|
||||
assert.equal(whListReordered[0].sort, 0);
|
||||
assert.equal(whListReordered[1].id, wh1.id);
|
||||
assert.equal(whListReordered[1].sort, 1);
|
||||
|
||||
// Test invalid warehouse order
|
||||
// Double booking delivery2 must reject
|
||||
await assert.rejects(
|
||||
async () => store.warehouse.order(googleAuthId, { warehouseIds: [wh1.id] }),
|
||||
/Invalid warehouse order/,
|
||||
async () => store.delivery.book(googleAuthId, { id: delivery2.id }),
|
||||
/Delivery line(s)? already (accepted|booked) on ledger/,
|
||||
'Should reject booking already booked delivery',
|
||||
);
|
||||
|
||||
// Verify article detail includes warehouses with sort
|
||||
const articleDetail = await store.article.getBySku(googleAuthId, article.sku);
|
||||
assert(Array.isArray(articleDetail.article.warehouses), 'Article should have warehouses array');
|
||||
assert.equal(articleDetail.article.warehouses.length, 2);
|
||||
assert.equal(articleDetail.article.warehouses[0].id, wh2.id);
|
||||
assert.equal(articleDetail.article.warehouses[0].sort, 0);
|
||||
assert.equal(articleDetail.article.warehouses[1].id, wh1.id);
|
||||
assert.equal(articleDetail.article.warehouses[1].sort, 1);
|
||||
// 9. Test shipment booking
|
||||
console.log('7. Testing shipment booking (SHIPMENT + WAREHOUSE dual entries)...');
|
||||
const customer = await store.entity.create(googleAuthId, {
|
||||
name: 'Customer Y',
|
||||
isCustomer: true,
|
||||
customergroupId: company.rootCategoryId,
|
||||
});
|
||||
|
||||
// Enable stock tracking now that warehouses exist
|
||||
await store.article.setTrackStockBySku(googleAuthId, article.sku, true);
|
||||
const order2 = await store.order.create(googleAuthId, {
|
||||
number: 'O-2',
|
||||
entityId: customer.id,
|
||||
date: new Date().toISOString(),
|
||||
lines: [
|
||||
{ sku: 'SKU-ABC', name: 'Ordered Item', pu: 1, amount: 3, net: 25, taxrate: 19 },
|
||||
],
|
||||
});
|
||||
|
||||
// Add ledger row with warehouse
|
||||
const rowWithWh = await store.ledger.add(googleAuthId, {
|
||||
manifestarticle_id: manifestarticleId1,
|
||||
const shipment = await store.shipment.create(googleAuthId, {
|
||||
orderId: order2.id,
|
||||
lines: [{ sku: 'SKU-ABC', amount: 3 }],
|
||||
});
|
||||
|
||||
const shipmentLineId = shipment.lines[0].id;
|
||||
const shipRow = await store.ledger.add(googleAuthId, {
|
||||
manifestarticle_id: shipmentLineId,
|
||||
warehouse_id: wh1.id,
|
||||
transaction_type: 'DELIVERY',
|
||||
amount: 25,
|
||||
transaction_text: 'Delivered directly to Hauptlager',
|
||||
transaction_type: 'SHIPMENT',
|
||||
amount: 3,
|
||||
transaction_text: `Lieferschein ${shipment.number}`,
|
||||
});
|
||||
assert.equal(rowWithWh.warehouse_id, wh1.id);
|
||||
assert.equal(rowWithWh.amount, 25);
|
||||
|
||||
// Filter by warehouse in listBySku and listByManifestarticleId
|
||||
const whRowsBySku = await store.ledger.listBySku(googleAuthId, 'SKU-ABC', { warehouse_id: wh1.id });
|
||||
assert.equal(whRowsBySku.length, 1);
|
||||
assert.equal(whRowsBySku[0].id, rowWithWh.id);
|
||||
assert.equal(whRowsBySku[0].warehouse_id, wh1.id);
|
||||
assert.equal(shipRow.transaction_type, 'SHIPMENT');
|
||||
assert.equal(shipRow.warehouse_id, null);
|
||||
assert.equal(shipRow.amount, -3);
|
||||
|
||||
const whRowsByLine = await store.ledger.listByManifestarticleId(googleAuthId, manifestarticleId1, { warehouse_id: wh1.id });
|
||||
assert.equal(whRowsByLine.length, 1);
|
||||
assert.equal(whRowsByLine[0].id, rowWithWh.id);
|
||||
const shipEntries = await store.ledger.listByManifestarticleId(googleAuthId, shipmentLineId);
|
||||
assert.equal(shipEntries.length, 2, 'Shipment line must have 2 entries (SHIPMENT + WAREHOUSE)');
|
||||
assert.equal(shipEntries[0].transaction_type, 'SHIPMENT');
|
||||
assert.equal(shipEntries[0].warehouse_id, null);
|
||||
assert.equal(shipEntries[0].amount, -3);
|
||||
assert.equal(shipEntries[1].transaction_type, 'WAREHOUSE');
|
||||
assert.equal(shipEntries[1].warehouse_id, wh1.id);
|
||||
assert.equal(shipEntries[1].amount, -3);
|
||||
|
||||
// Verify warehouse is recorded in article history
|
||||
const historyWithWh = await store.article.getHistoryBySku(googleAuthId, article.sku);
|
||||
// Stock should now be: 10 (line 2) + 4 (delivery2) - 3 (shipment) = 11
|
||||
const artDetailAfterShip = await store.article.getBySku(googleAuthId, article.sku);
|
||||
const whStockAfterShip = artDetailAfterShip.article.warehouses.find((w) => w.id === wh1.id)?.stock;
|
||||
assert.equal(whStockAfterShip, 11, 'Warehouse stock must be 11 (10 + 4 - 3)');
|
||||
|
||||
// Verify shipment history
|
||||
const historyAfterShip = await store.article.getHistoryBySku(googleAuthId, article.sku);
|
||||
const shipHistoryTexts = historyAfterShip.map((h) => h.text);
|
||||
assert(
|
||||
historyWithWh.some((h) => h.text.includes('Lager: "Hauptlager"') && h.text.includes('Lieferant: "Supplier X"')),
|
||||
'History should include warehouse name (Lager: "Hauptlager")',
|
||||
shipHistoryTexts.some((t) => t.includes(`Lieferschein ${shipment.number}`) && t.includes('Menge: 3')),
|
||||
'Article history should record outgoing shipment',
|
||||
);
|
||||
|
||||
// Trying to delete wh1 (used in ledger) must fail
|
||||
await assert.rejects(
|
||||
async () => store.warehouse.delete(googleAuthId, wh1.id),
|
||||
/Warehouse in use in ledger/,
|
||||
'Should reject deleting warehouse referenced in ledger',
|
||||
);
|
||||
|
||||
// Deleting wh2 (not used in ledger) must succeed
|
||||
await store.warehouse.delete(googleAuthId, wh2.id);
|
||||
const whListAfterDelete = await store.warehouse.list(googleAuthId);
|
||||
assert.equal(whListAfterDelete.length, 1);
|
||||
assert.equal(whListAfterDelete[0].id, wh1.id);
|
||||
|
||||
// Test last warehouse guard with track_stock articles
|
||||
const freshCompany = await store.company.create({
|
||||
name: 'Warehouse Test Co',
|
||||
iso639: 'de',
|
||||
taxrate: 19,
|
||||
reducedtaxrate: 7,
|
||||
googleAuthId,
|
||||
// 8. Test store.stock.accept with deliverymanifestitem
|
||||
console.log('8. Testing store.stock.accept (positive acceptance & negative return)...');
|
||||
const delivery3 = await store.delivery.create(googleAuthId, {
|
||||
supplierId: supplier.id,
|
||||
externalNumber: 'DEL-3',
|
||||
date: new Date().toISOString(),
|
||||
lines: [
|
||||
{ sku: 'SKU-ABC', name: 'Positive Delivery Line', pu: 1, amount: 6, net: 10, taxrate: 19 },
|
||||
{ sku: 'SKU-ABC', name: 'Negative Delivery Line (Return)', pu: 1, amount: -2, net: 10, taxrate: 19 },
|
||||
],
|
||||
});
|
||||
await store.google_auth.setSelectedCompany(googleAuthId, freshCompany.id);
|
||||
const testWh = await store.warehouse.create(googleAuthId, { name: 'Only Warehouse' });
|
||||
const testArt = await store.article.createUnderParent({
|
||||
googleAuthId,
|
||||
parentId: freshCompany.rootCategoryId,
|
||||
name: 'Tracked Article',
|
||||
track_stock: true,
|
||||
|
||||
const posDeliveryLineId = delivery3.lines[0].id;
|
||||
const negDeliveryLineId = delivery3.lines[1].id;
|
||||
|
||||
const acceptPosResult = await store.stock.accept(googleAuthId, {
|
||||
manifestarticleId: posDeliveryLineId,
|
||||
warehouseId: wh1.id,
|
||||
});
|
||||
assert.equal(testArt.track_stock, true);
|
||||
assert.equal(acceptPosResult.document_ledger.transaction_type, 'ACCEPTANCE');
|
||||
assert.equal(acceptPosResult.document_ledger.amount, 6);
|
||||
assert.equal(acceptPosResult.document_ledger.warehouse_id, null);
|
||||
assert.equal(acceptPosResult.adjust_ledger.amount, 6);
|
||||
assert.equal(acceptPosResult.adjust_ledger.warehouse_id, wh1.id);
|
||||
|
||||
// Negative delivery line: return of a delivery (outgoing)
|
||||
const acceptNegResult = await store.stock.accept(googleAuthId, {
|
||||
manifestarticleId: negDeliveryLineId,
|
||||
warehouseId: wh1.id,
|
||||
});
|
||||
assert.equal(acceptNegResult.document_ledger.transaction_type, 'RETURN');
|
||||
assert.equal(acceptNegResult.document_ledger.amount, -2);
|
||||
assert.equal(acceptNegResult.document_ledger.warehouse_id, null);
|
||||
assert.equal(acceptNegResult.adjust_ledger.amount, -2);
|
||||
assert.equal(acceptNegResult.adjust_ledger.warehouse_id, wh1.id);
|
||||
|
||||
// 9. Test store.stock.ship with shipmentmanifestitem
|
||||
console.log('9. Testing store.stock.ship (positive shipment & negative retoure)...');
|
||||
const order3 = await store.order.create(googleAuthId, {
|
||||
number: 'O-3',
|
||||
entityId: customer.id,
|
||||
date: new Date().toISOString(),
|
||||
lines: [
|
||||
{ sku: 'SKU-ABC', name: 'Shipment Item Normal', pu: 1, amount: 5, net: 10, taxrate: 19 },
|
||||
],
|
||||
});
|
||||
|
||||
const shipment3 = await store.shipment.create(googleAuthId, {
|
||||
orderId: order3.id,
|
||||
lines: [
|
||||
{ sku: 'SKU-ABC', amount: 4 },
|
||||
{ sku: 'SKU-ABC', amount: -1 },
|
||||
],
|
||||
});
|
||||
|
||||
const posShipLineId = shipment3.lines[0].id;
|
||||
const negShipLineId = shipment3.lines[1].id;
|
||||
|
||||
const shipPosResult = await store.stock.ship(googleAuthId, {
|
||||
manifestarticleId: posShipLineId,
|
||||
warehouseId: wh1.id,
|
||||
});
|
||||
assert.equal(shipPosResult.document_ledger.transaction_type, 'SHIPMENT');
|
||||
assert.equal(shipPosResult.document_ledger.amount, -4);
|
||||
assert.equal(shipPosResult.adjust_ledger.amount, -4);
|
||||
|
||||
// Negative shipment line: retoure of a shipment (incoming)
|
||||
const shipNegResult = await store.stock.ship(googleAuthId, {
|
||||
manifestarticleId: negShipLineId,
|
||||
warehouseId: wh1.id,
|
||||
});
|
||||
assert.equal(shipNegResult.document_ledger.transaction_type, 'RETOURE');
|
||||
assert.equal(shipNegResult.document_ledger.amount, 1);
|
||||
assert.equal(shipNegResult.adjust_ledger.amount, 1);
|
||||
|
||||
// 10. Test store.stock.adjust
|
||||
console.log('10. Testing store.stock.adjust failure on untracked article...');
|
||||
const untrackedArt = await store.article.createUnderParent({
|
||||
googleAuthId,
|
||||
parentId: company.rootCategoryId,
|
||||
name: 'Pure Untracked Article',
|
||||
track_stock: false,
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
async () => store.warehouse.delete(googleAuthId, testWh.id),
|
||||
/Cannot delete the last warehouse while articles have stock tracking enabled/,
|
||||
'Should reject deleting last warehouse when articles have track_stock enabled',
|
||||
async () => store.stock.adjust(googleAuthId, {
|
||||
sku: untrackedArt.sku,
|
||||
warehouseId: wh1.id,
|
||||
amount: 5,
|
||||
}),
|
||||
/Stock tracking is disabled for article/,
|
||||
'Adjust must fail when article has no stock tracking',
|
||||
);
|
||||
// Disable track_stock on the article, then deleting the last warehouse must succeed
|
||||
await store.article.setTrackStockBySku(googleAuthId, testArt.sku, false);
|
||||
await store.warehouse.delete(googleAuthId, testWh.id);
|
||||
const remainingWh = await store.warehouse.list(googleAuthId);
|
||||
assert.equal(remainingWh.length, 0);
|
||||
|
||||
// Switch back to original company
|
||||
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
|
||||
// 11. Test before stock tracking is disabled, adjust to 0 has to be ledgerized
|
||||
console.log('11. Testing zero-adjustment before stock tracking is disabled...');
|
||||
const artBeforeDisable = await store.article.getBySku(googleAuthId, article.sku);
|
||||
const stockBeforeDisable = artBeforeDisable.article.warehouses.find((w) => w.id === wh1.id)?.stock;
|
||||
console.log('Current stock before disabling tracking:', stockBeforeDisable);
|
||||
assert(stockBeforeDisable > 0, 'Article must have non-zero stock before disabling');
|
||||
|
||||
console.log('✓ Warehouse CRUD and ledger enforcement passed');
|
||||
// Disable stock tracking
|
||||
await store.article.setTrackStockBySku(googleAuthId, article.sku, false);
|
||||
|
||||
const artAfterDisable = await store.article.getBySku(googleAuthId, article.sku);
|
||||
assert.equal(artAfterDisable.article.track_stock, false, 'Article track_stock must now be false');
|
||||
const stockAfterDisable = artAfterDisable.article.warehouses.find((w) => w.id === wh1.id)?.stock;
|
||||
assert.equal(stockAfterDisable, 0, 'Warehouse stock must be adjusted to 0 on the ledger');
|
||||
|
||||
// Calling adjust now must fail
|
||||
await assert.rejects(
|
||||
async () => store.stock.adjust(googleAuthId, {
|
||||
sku: article.sku,
|
||||
warehouseId: wh1.id,
|
||||
amount: 1,
|
||||
}),
|
||||
/Stock tracking is disabled for article/,
|
||||
'Subsequent adjust must fail once tracking is disabled',
|
||||
);
|
||||
|
||||
console.log('✓ All ledger tests passed successfully!');
|
||||
} finally {
|
||||
|
||||
@@ -140,16 +140,22 @@ async function run() {
|
||||
|
||||
// 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.equal(ledgerRows.length, 3, 'Should have 3 ledger entries for tracked article (adjust + shipment doc + warehouse)');
|
||||
const shipmentDocLedger = ledgerRows.find((l) => l.transaction_type === 'SHIPMENT' && l.warehouse_id == null);
|
||||
assert(shipmentDocLedger, 'Shipment document ledger entry should exist with warehouse_id null');
|
||||
assert.equal(shipmentDocLedger.amount, -3, 'Shipment document ledger entry should be -3');
|
||||
const shipmentWhLedger = ledgerRows.find((l) => l.transaction_type === 'WAREHOUSE' && l.warehouse_id === warehouse.id);
|
||||
assert(shipmentWhLedger, 'Shipment warehouse ledger entry should exist with warehouse_id set');
|
||||
assert.equal(shipmentWhLedger.amount, -3, 'Shipment warehouse ledger entry should be -3');
|
||||
|
||||
// Assert untracked article has 0 warehouse ledger entries
|
||||
// Assert untracked article has 1 document ledger entry and 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.equal(untrackedLedger.length, 1, 'Untracked article should have 1 shipment document ledger entry');
|
||||
assert.equal(untrackedLedger[0].transaction_type, 'SHIPMENT');
|
||||
assert.equal(untrackedLedger[0].warehouse_id, null, 'Untracked article should not have warehouse ledger entry');
|
||||
assert.equal(untrackedLedger[0].amount, -2);
|
||||
|
||||
// Assert article history: order goes into history, shipment does NOT
|
||||
// Assert article history: order goes into history, and shipment produces history
|
||||
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)');
|
||||
@@ -159,10 +165,10 @@ async function run() {
|
||||
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');
|
||||
const shipmentHistory = history.find((h) => h.text.startsWith(`Lieferschein ${shipment.number}`) && h.text.includes('Kunde: "Max Mustermann"'));
|
||||
assert(shipmentHistory, 'Article history should report the shipment document');
|
||||
|
||||
// Untracked article also has the order in history, but not the shipment
|
||||
// Untracked article also has the order in history and shipment in history
|
||||
const untrackedHistory = await store.article.getHistoryBySku(googleAuthId, untrackedArt.sku);
|
||||
console.log('Untracked article history:', untrackedHistory.map((h) => h.text));
|
||||
assert(
|
||||
@@ -170,8 +176,8 @@ async function run() {
|
||||
'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',
|
||||
untrackedHistory.some((h) => h.text.startsWith(`Lieferschein ${shipment.number}`)),
|
||||
'Untracked article history should report the shipment',
|
||||
);
|
||||
|
||||
// Test order import with historical date preserves date in article history
|
||||
|
||||
Reference in New Issue
Block a user