u
This commit is contained in:
@@ -142,13 +142,25 @@ async function run() {
|
|||||||
const untrackedLedger = await store.ledger.listBySku(googleAuthId, untrackedArt.sku);
|
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, 0, 'Untracked article should not have warehouse ledger entries');
|
||||||
|
|
||||||
// Assert article history
|
// Assert article history: order goes into history, shipment does NOT
|
||||||
const history = await store.article.getHistoryBySku(googleAuthId, trackedArt.sku);
|
const history = await store.article.getHistoryBySku(googleAuthId, trackedArt.sku);
|
||||||
console.log('Tracked article history:', history.map((h) => h.text));
|
console.log('Tracked article history:', history.map((h) => h.text));
|
||||||
const shippingHistory = history.find((h) => h.text.includes('Versand Auftrag O-2026-001'));
|
const orderHistory = history.find((h) => h.text === 'Auftrag O-2026-001 (Kunde: "Max Mustermann", Menge: 3)');
|
||||||
assert(shippingHistory, 'Article history should contain shipping entry');
|
assert(orderHistory, 'Article history should contain order creation entry');
|
||||||
assert(shippingHistory.text.includes('Max Mustermann'), 'Article history should mention customer');
|
const shippingHistory = history.find((h) => h.text.includes('Versand Auftrag'));
|
||||||
assert(shippingHistory.text.includes('Hauptlager'), 'Article history should mention warehouse');
|
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)'),
|
||||||
|
'Untracked article history should contain order creation entry',
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
!untrackedHistory.some((h) => h.text.includes('Versand Auftrag')),
|
||||||
|
'Untracked article history should NOT report the shipment',
|
||||||
|
);
|
||||||
|
|
||||||
// 6. Test immutability: once shipped, cannot be deleted
|
// 6. Test immutability: once shipped, cannot be deleted
|
||||||
console.log('6. Testing cannot delete shipped shipping...');
|
console.log('6. Testing cannot delete shipped shipping...');
|
||||||
|
|||||||
@@ -231,32 +231,18 @@ export default function add(provider) {
|
|||||||
transactionText?.trim() ||
|
transactionText?.trim() ||
|
||||||
(manifestArticle.manifestNumber ? `Lieferung ${manifestArticle.manifestNumber}` : 'Lieferung');
|
(manifestArticle.manifestNumber ? `Lieferung ${manifestArticle.manifestNumber}` : 'Lieferung');
|
||||||
historyText = `${base}${suffix}`;
|
historyText = `${base}${suffix}`;
|
||||||
} else if (isShipping) {
|
|
||||||
const details = [];
|
|
||||||
if (manifestArticle.supplierName) {
|
|
||||||
details.push(`Kunde: "${manifestArticle.supplierName}"`);
|
|
||||||
}
|
|
||||||
if (warehouseName) {
|
|
||||||
details.push(`Lager: "${warehouseName}"`);
|
|
||||||
}
|
|
||||||
if (amount != null && Number.isFinite(Number(amount))) {
|
|
||||||
details.push(`Menge: ${amount}`);
|
|
||||||
}
|
|
||||||
const suffix = details.length ? ` (${details.join(', ')})` : '';
|
|
||||||
const base =
|
|
||||||
transactionText?.trim() ||
|
|
||||||
(manifestArticle.manifestNumber ? `Versand Auftrag ${manifestArticle.manifestNumber}` : 'Versand');
|
|
||||||
historyText = `${base}${suffix}`;
|
|
||||||
} else if (warehouseName && !transactionText.includes(warehouseName)) {
|
} else if (warehouseName && !transactionText.includes(warehouseName)) {
|
||||||
historyText = `${transactionText} (Lager: "${warehouseName}")`;
|
historyText = `${transactionText} (Lager: "${warehouseName}")`;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const match of articleMatches.rows) {
|
if (!isShipping) {
|
||||||
await runner.query(queries.INSERT_ARTICLE_HISTORY, {
|
for (const match of articleMatches.rows) {
|
||||||
articleId: match.articleId,
|
await runner.query(queries.INSERT_ARTICLE_HISTORY, {
|
||||||
companyuserId,
|
articleId: match.articleId,
|
||||||
text: historyText,
|
companyuserId,
|
||||||
});
|
text: historyText,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ import {
|
|||||||
CREATE_MANIFEST as SQLITE_CREATE_MANIFEST,
|
CREATE_MANIFEST as SQLITE_CREATE_MANIFEST,
|
||||||
ORDER_TYPE as SQLITE_ORDER_TYPE,
|
ORDER_TYPE as SQLITE_ORDER_TYPE,
|
||||||
} from '../../providers/sqlite3/queries/manifestDoc.js';
|
} from '../../providers/sqlite3/queries/manifestDoc.js';
|
||||||
|
import {
|
||||||
|
FIND_ARTICLES_FOR_HISTORY as MSSQL_FIND_ARTICLES,
|
||||||
|
INSERT_ARTICLE_HISTORY as MSSQL_INSERT_HISTORY,
|
||||||
|
} from '../../providers/mssql/queries/ledger.js';
|
||||||
|
import {
|
||||||
|
FIND_ARTICLES_FOR_HISTORY as SQLITE_FIND_ARTICLES,
|
||||||
|
INSERT_ARTICLE_HISTORY as SQLITE_INSERT_HISTORY,
|
||||||
|
} from '../../providers/sqlite3/queries/ledger.js';
|
||||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
import taxEnsure from '../tax/ensure.js';
|
import taxEnsure from '../tax/ensure.js';
|
||||||
import { insertLines, normalizeLines, parseDate } from '../manifestLines.js';
|
import { insertLines, normalizeLines, parseDate } from '../manifestLines.js';
|
||||||
@@ -19,9 +27,11 @@ export default function create(provider) {
|
|||||||
|
|
||||||
const createManifestSql = isSqlite ? SQLITE_CREATE_MANIFEST : MSSQL_CREATE_MANIFEST;
|
const createManifestSql = isSqlite ? SQLITE_CREATE_MANIFEST : MSSQL_CREATE_MANIFEST;
|
||||||
const orderType = isSqlite ? SQLITE_ORDER_TYPE : MSSQL_ORDER_TYPE;
|
const orderType = isSqlite ? SQLITE_ORDER_TYPE : MSSQL_ORDER_TYPE;
|
||||||
|
const findArticlesSql = isSqlite ? SQLITE_FIND_ARTICLES : MSSQL_FIND_ARTICLES;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new order for the user's selected company with its lines.
|
* Creates a new order for the user's selected company with its lines and records article history.
|
||||||
*
|
*
|
||||||
* @param {number} googleAuthId - The id of the Google auth user.
|
* @param {number} googleAuthId - The id of the Google auth user.
|
||||||
* @param {object} body - The order input.
|
* @param {object} body - The order input.
|
||||||
@@ -55,7 +65,7 @@ export default function create(provider) {
|
|||||||
const lines = normalizeLines(body.lines ?? []);
|
const lines = normalizeLines(body.lines ?? []);
|
||||||
|
|
||||||
const runCreate = async (runner) => {
|
const runCreate = async (runner) => {
|
||||||
const { companyId } = await access(googleAuthId, runner);
|
const { companyId, companyuserId } = await access(googleAuthId, runner);
|
||||||
|
|
||||||
const entityId = await assertCustomerEntity(
|
const entityId = await assertCustomerEntity(
|
||||||
runner,
|
runner,
|
||||||
@@ -81,6 +91,41 @@ export default function create(provider) {
|
|||||||
ensureTax,
|
ensureTax,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Record article history for all order items
|
||||||
|
let customerName = null;
|
||||||
|
if (entityId) {
|
||||||
|
const entRes = await runner.query('SELECT name FROM entity WHERE id = @entityId', { entityId });
|
||||||
|
customerName = entRes.rows[0]?.name ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const sku = line.sku ? String(line.sku).trim() : '';
|
||||||
|
if (!sku) continue;
|
||||||
|
|
||||||
|
const articleMatches = await runner.query(findArticlesSql, {
|
||||||
|
sku,
|
||||||
|
companyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const details = [];
|
||||||
|
if (customerName) {
|
||||||
|
details.push(`Kunde: "${customerName}"`);
|
||||||
|
}
|
||||||
|
if (line.amount != null && Number.isFinite(Number(line.amount))) {
|
||||||
|
details.push(`Menge: ${line.amount}`);
|
||||||
|
}
|
||||||
|
const suffix = details.length ? ` (${details.join(', ')})` : '';
|
||||||
|
const historyText = `Auftrag ${number}${suffix}`;
|
||||||
|
|
||||||
|
for (const match of articleMatches.rows) {
|
||||||
|
await runner.query(insertHistorySql, {
|
||||||
|
articleId: match.articleId,
|
||||||
|
companyuserId,
|
||||||
|
text: historyText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const doc = await loadDoc(runner, {
|
const doc = await loadDoc(runner, {
|
||||||
type: orderType,
|
type: orderType,
|
||||||
companyId,
|
companyId,
|
||||||
@@ -99,4 +144,3 @@ export default function create(provider) {
|
|||||||
return runCreate(makeRunner(provider));
|
return runCreate(makeRunner(provider));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { queriesFor, requireId, requireText } from '../logistics/helpers.js';
|
import { queriesFor, requireId, requireText } from '../logistics/helpers.js';
|
||||||
|
import {
|
||||||
|
FIND_ARTICLES_FOR_HISTORY as MSSQL_FIND_ARTICLES,
|
||||||
|
INSERT_ARTICLE_HISTORY as MSSQL_INSERT_HISTORY,
|
||||||
|
} from '../../providers/mssql/queries/ledger.js';
|
||||||
|
import {
|
||||||
|
FIND_ARTICLES_FOR_HISTORY as SQLITE_FIND_ARTICLES,
|
||||||
|
INSERT_ARTICLE_HISTORY as SQLITE_INSERT_HISTORY,
|
||||||
|
} from '../../providers/sqlite3/queries/ledger.js';
|
||||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
import nextNumber from '../numbers/nextNumber.js';
|
import nextNumber from '../numbers/nextNumber.js';
|
||||||
import ledgerAdd from '../ledger/add.js';
|
import ledgerAdd from '../ledger/add.js';
|
||||||
@@ -19,12 +27,15 @@ export default function create(provider) {
|
|||||||
const allocateNumber = nextNumber(provider);
|
const allocateNumber = nextNumber(provider);
|
||||||
const addLedger = ledgerAdd(provider);
|
const addLedger = ledgerAdd(provider);
|
||||||
const q = queriesFor(provider);
|
const q = queriesFor(provider);
|
||||||
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
|
const findArticlesSql = isSqlite ? SQLITE_FIND_ARTICLES : MSSQL_FIND_ARTICLES;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a shipping for a manifest (order). The number is allocated from the
|
* Creates a shipping for a manifest (order). The number is allocated from the
|
||||||
* `shipping` numbers sequence (own prefix + counter). If state is 'shipped',
|
* `shipping` numbers sequence (own prefix + counter). If state is 'shipped',
|
||||||
* adjusts stock by appending negative ledger movements for tracked order lines,
|
* adjusts stock by appending negative ledger movements for tracked order lines,
|
||||||
* writes article history, and ensures the order was not already shipped.
|
* writes article history for all items, and ensures the order was not already shipped.
|
||||||
*
|
*
|
||||||
* @param {number} googleAuthId - The id of the Google auth user.
|
* @param {number} googleAuthId - The id of the Google auth user.
|
||||||
* @param {object} input - Shipping input.
|
* @param {object} input - Shipping input.
|
||||||
@@ -46,7 +57,7 @@ export default function create(provider) {
|
|||||||
const isShipped = state.trim().toLowerCase() === 'shipped';
|
const isShipped = state.trim().toLowerCase() === 'shipped';
|
||||||
|
|
||||||
const run = async (runner, innerTx) => {
|
const run = async (runner, innerTx) => {
|
||||||
const { companyId } = await access(googleAuthId, runner);
|
const { companyId, companyuserId } = await access(googleAuthId, runner);
|
||||||
|
|
||||||
// Verify manifest exists and belongs to company
|
// Verify manifest exists and belongs to company
|
||||||
const manifestRes = await runner.query(q.GET_MANIFEST_HEADER_BY_ID, {
|
const manifestRes = await runner.query(q.GET_MANIFEST_HEADER_BY_ID, {
|
||||||
@@ -91,8 +102,9 @@ export default function create(provider) {
|
|||||||
});
|
});
|
||||||
const lines = linesRes.rows ?? [];
|
const lines = linesRes.rows ?? [];
|
||||||
|
|
||||||
// Check which lines need warehouse stock deduction
|
// Check which lines need warehouse stock deduction vs untracked history
|
||||||
const trackedLines = [];
|
const trackedLines = [];
|
||||||
|
const untrackedLines = [];
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const sku = line.sku ? String(line.sku).trim() : '';
|
const sku = line.sku ? String(line.sku).trim() : '';
|
||||||
const amount = Number(line.amount);
|
const amount = Number(line.amount);
|
||||||
@@ -107,11 +119,24 @@ export default function create(provider) {
|
|||||||
const isUntracked = (trackRes.rows ?? []).some(
|
const isUntracked = (trackRes.rows ?? []).some(
|
||||||
(r) => Number(r.track_stock ?? 1) === 0
|
(r) => Number(r.track_stock ?? 1) === 0
|
||||||
);
|
);
|
||||||
if (!isUntracked && (trackRes.rows ?? []).length > 0) {
|
if (isUntracked) {
|
||||||
|
untrackedLines.push({ ...line, amount });
|
||||||
|
} else if ((trackRes.rows ?? []).length > 0) {
|
||||||
trackedLines.push({ ...line, amount });
|
trackedLines.push({ ...line, amount });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const manifestNumber = manifest.number || String(manifestId);
|
||||||
|
|
||||||
|
let customerName = null;
|
||||||
|
if (manifest.entityId) {
|
||||||
|
const entRes = await runner.query('SELECT name FROM entity WHERE id = @entityId', {
|
||||||
|
entityId: manifest.entityId,
|
||||||
|
});
|
||||||
|
customerName = entRes.rows[0]?.name ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Book warehouse ledger movements for tracked lines
|
||||||
if (trackedLines.length > 0) {
|
if (trackedLines.length > 0) {
|
||||||
let warehouseId = null;
|
let warehouseId = null;
|
||||||
if (input.warehouseId != null) {
|
if (input.warehouseId != null) {
|
||||||
@@ -134,7 +159,6 @@ export default function create(provider) {
|
|||||||
warehouseId = defaultWh.rows[0].id;
|
warehouseId = defaultWh.rows[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const manifestNumber = manifest.number || String(manifestId);
|
|
||||||
for (const line of trackedLines) {
|
for (const line of trackedLines) {
|
||||||
await addLedger(
|
await addLedger(
|
||||||
googleAuthId,
|
googleAuthId,
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { queriesFor, requireId, requireText } from '../logistics/helpers.js';
|
import { queriesFor, requireId, requireText } from '../logistics/helpers.js';
|
||||||
|
import {
|
||||||
|
FIND_ARTICLES_FOR_HISTORY as MSSQL_FIND_ARTICLES,
|
||||||
|
INSERT_ARTICLE_HISTORY as MSSQL_INSERT_HISTORY,
|
||||||
|
} from '../../providers/mssql/queries/ledger.js';
|
||||||
|
import {
|
||||||
|
FIND_ARTICLES_FOR_HISTORY as SQLITE_FIND_ARTICLES,
|
||||||
|
INSERT_ARTICLE_HISTORY as SQLITE_INSERT_HISTORY,
|
||||||
|
} from '../../providers/sqlite3/queries/ledger.js';
|
||||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||||
import ledgerAdd from '../ledger/add.js';
|
import ledgerAdd from '../ledger/add.js';
|
||||||
import makeRunner from '../runner.js';
|
import makeRunner from '../runner.js';
|
||||||
@@ -24,6 +32,9 @@ export default function update(provider) {
|
|||||||
const access = resolveSelectedCompanyAccess(provider);
|
const access = resolveSelectedCompanyAccess(provider);
|
||||||
const addLedger = ledgerAdd(provider);
|
const addLedger = ledgerAdd(provider);
|
||||||
const q = queriesFor(provider);
|
const q = queriesFor(provider);
|
||||||
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
|
const findArticlesSql = isSqlite ? SQLITE_FIND_ARTICLES : MSSQL_FIND_ARTICLES;
|
||||||
|
const insertHistorySql = isSqlite ? SQLITE_INSERT_HISTORY : MSSQL_INSERT_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a shipping record.
|
* Updates a shipping record.
|
||||||
@@ -42,7 +53,7 @@ export default function update(provider) {
|
|||||||
const shippingId = requireId(id, 'Shipping');
|
const shippingId = requireId(id, 'Shipping');
|
||||||
|
|
||||||
const run = async (runner, innerTx) => {
|
const run = async (runner, innerTx) => {
|
||||||
const { companyId } = await access(googleAuthId, runner);
|
const { companyId, companyuserId } = await access(googleAuthId, runner);
|
||||||
|
|
||||||
const check = await runner.query(q.GET_SHIPPING_BY_ID, {
|
const check = await runner.query(q.GET_SHIPPING_BY_ID, {
|
||||||
id: shippingId,
|
id: shippingId,
|
||||||
@@ -98,6 +109,7 @@ export default function update(provider) {
|
|||||||
});
|
});
|
||||||
const lines = linesRes.rows ?? [];
|
const lines = linesRes.rows ?? [];
|
||||||
const trackedLines = [];
|
const trackedLines = [];
|
||||||
|
const untrackedLines = [];
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const sku = line.sku ? String(line.sku).trim() : '';
|
const sku = line.sku ? String(line.sku).trim() : '';
|
||||||
const amount = Number(line.amount);
|
const amount = Number(line.amount);
|
||||||
@@ -110,11 +122,23 @@ export default function update(provider) {
|
|||||||
const isUntracked = (trackRes.rows ?? []).some(
|
const isUntracked = (trackRes.rows ?? []).some(
|
||||||
(r) => Number(r.track_stock ?? 1) === 0
|
(r) => Number(r.track_stock ?? 1) === 0
|
||||||
);
|
);
|
||||||
if (!isUntracked && (trackRes.rows ?? []).length > 0) {
|
if (isUntracked) {
|
||||||
|
untrackedLines.push({ ...line, amount });
|
||||||
|
} else if ((trackRes.rows ?? []).length > 0) {
|
||||||
trackedLines.push({ ...line, amount });
|
trackedLines.push({ ...line, amount });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const manifestNumber = manifest.number || String(manifestId);
|
||||||
|
|
||||||
|
let customerName = null;
|
||||||
|
if (manifest.entityId) {
|
||||||
|
const entRes = await runner.query('SELECT name FROM entity WHERE id = @entityId', {
|
||||||
|
entityId: manifest.entityId,
|
||||||
|
});
|
||||||
|
customerName = entRes.rows[0]?.name ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
if (trackedLines.length > 0) {
|
if (trackedLines.length > 0) {
|
||||||
let warehouseId = null;
|
let warehouseId = null;
|
||||||
if (patch.warehouseId != null) {
|
if (patch.warehouseId != null) {
|
||||||
@@ -137,7 +161,6 @@ export default function update(provider) {
|
|||||||
warehouseId = defaultWh.rows[0].id;
|
warehouseId = defaultWh.rows[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const manifestNumber = manifest.number || String(manifestId);
|
|
||||||
for (const line of trackedLines) {
|
for (const line of trackedLines) {
|
||||||
await addLedger(
|
await addLedger(
|
||||||
googleAuthId,
|
googleAuthId,
|
||||||
|
|||||||
Reference in New Issue
Block a user