Compare commits

..

2 Commits

Author SHA1 Message Date
seb
fe0d2e43f4 u 2026-08-23 04:12:14 +02:00
seb
5caedbe345 u 2026-08-23 03:53:08 +02:00
3 changed files with 94 additions and 32 deletions

View File

@@ -1,7 +1,7 @@
import { openSession, discardSession, closeSession } from './session.js'; import { openSession, discardSession, closeSession } from './session.js';
import { getOutgoingWarehouse, getWarehousePlace } from './warehouse.js'; import { getOutgoingWarehouse, getWarehousePlace } from './warehouse.js';
import { reservePositions } from './reserve.js'; import { reservePositions } from './reserve.js';
import { bookStockShortfallsAndRereserve } from './stock-shortage.js'; import { bookStockShortfallsAndRereserve, bookReturnPositions } from './stock-shortage.js';
import { commitPicklists } from './commit.js'; import { commitPicklists } from './commit.js';
import { deliverPicklists } from './deliver.js'; import { deliverPicklists } from './deliver.js';
import { logger } from '../../logger.js'; import { logger } from '../../logger.js';
@@ -29,17 +29,28 @@ export async function deliverOrder(transaction, kBenutzer, kAuftrag, kVersandArt
const kWarenLager = await getOutgoingWarehouse(transaction); const kWarenLager = await getOutgoingWarehouse(transaction);
const kWarenLagerPlatz = await getWarehousePlace(transaction, kWarenLager); const kWarenLagerPlatz = await getWarehousePlace(transaction, kWarenLager);
logger.info(`deliverOrder: kWarenLager=${kWarenLager} kWarenLagerPlatz=${kWarenLagerPlatz}`); logger.info(`deliverOrder: kWarenLager=${kWarenLager} kWarenLagerPlatz=${kWarenLagerPlatz}`);
const positiveItems = deliveredItems.filter((i) => i.quantity > 0);
const returnItems = deliveredItems.filter((i) => i.quantity < 0);
// 1. Process returned items: book them back into warehouse stock via Wareneingang
if (returnItems.length) {
await bookReturnPositions(transaction, kBenutzer, kWarenLagerPlatz, returnItems);
}
// 2. Process positive items: standard JTL picklist/delivery fulfillment
if (positiveItems.length) {
const kSessionId = await openSession(transaction, kBenutzer); const kSessionId = await openSession(transaction, kBenutzer);
try { try {
await reservePositions(transaction, kBenutzer, kSessionId, kWarenLager, deliveredItems); await reservePositions(transaction, kBenutzer, kSessionId, kWarenLager, positiveItems);
await bookStockShortfallsAndRereserve( await bookStockShortfallsAndRereserve(
transaction, transaction,
kBenutzer, kBenutzer,
kSessionId, kSessionId,
kWarenLager, kWarenLager,
kWarenLagerPlatz, kWarenLagerPlatz,
deliveredItems, positiveItems,
); );
await commitPicklists(transaction, kBenutzer, kSessionId, kAuftrag); await commitPicklists(transaction, kBenutzer, kSessionId, kAuftrag);
await deliverPicklists(transaction, kBenutzer, kSessionId, kAuftrag, kVersandArt); await deliverPicklists(transaction, kBenutzer, kSessionId, kAuftrag, kVersandArt);
@@ -48,11 +59,6 @@ export async function deliverOrder(transaction, kBenutzer, kAuftrag, kVersandArt
logger.error(`deliverOrder: FAILED kAuftrag=${kAuftrag} kSessionId=${kSessionId}: ${err.message}`); logger.error(`deliverOrder: FAILED kAuftrag=${kAuftrag} kSessionId=${kSessionId}: ${err.message}`);
throw err; throw err;
} finally { } finally {
// Safe to run unconditionally (success or error): spPicklistenVerwerfen only
// ever removes not-yet-delivered (nStatus < 10) Picklisten for this session.
// Swallow cleanup errors so they never mask an earlier, more relevant error
// (the outer transaction rollback in create-order.js is what actually matters
// on failure).
try { try {
await discardSession(transaction, kBenutzer, kSessionId); await discardSession(transaction, kBenutzer, kSessionId);
await closeSession(transaction, kSessionId); await closeSession(transaction, kSessionId);
@@ -61,3 +67,4 @@ export async function deliverOrder(transaction, kBenutzer, kAuftrag, kVersandArt
} }
} }
} }
}

View File

@@ -21,7 +21,7 @@ async function getReservedQuantity(transaction, kSessionId, kAuftragPosition) {
return reserved; return reserved;
} }
async function getPositionArtikel(transaction, kAuftragPosition) { export async function getPositionArtikel(transaction, kAuftragPosition) {
const result = await new sql.Request(transaction) const result = await new sql.Request(transaction)
.input('kAuftragPosition', sql.Int, kAuftragPosition) .input('kAuftragPosition', sql.Int, kAuftragPosition)
.query(` .query(`
@@ -35,14 +35,22 @@ async function getPositionArtikel(transaction, kAuftragPosition) {
return row?.kArtikel ?? 0; return row?.kArtikel ?? 0;
} }
async function bookWareneingang(transaction, kBenutzer, kWarenLagerPlatz, kArtikel, fehlmenge) { export async function bookWareneingang(
transaction,
kBenutzer,
kWarenLagerPlatz,
kArtikel,
fehlmenge,
comment = POS_SHORTAGE_COMMENT,
buchungsart = BUCHUNGSART_WARENEINGANG,
) {
await new sql.Request(transaction) await new sql.Request(transaction)
.input('kArtikel', sql.Int, kArtikel) .input('kArtikel', sql.Int, kArtikel)
.input('kWarenLagerPlatz', sql.Int, kWarenLagerPlatz) .input('kWarenLagerPlatz', sql.Int, kWarenLagerPlatz)
.input('kBenutzer', sql.Int, kBenutzer) .input('kBenutzer', sql.Int, kBenutzer)
.input('fAnzahl', sql.Float, fehlmenge) .input('fAnzahl', sql.Float, fehlmenge)
.input('cKommentar', sql.NVarChar, POS_SHORTAGE_COMMENT) .input('cKommentar', sql.NVarChar, comment)
.input('kBuchungsart', sql.Int, BUCHUNGSART_WARENEINGANG) .input('kBuchungsart', sql.Int, buchungsart)
.query(` .query(`
DECLARE @kWarenlagerEingang INT; DECLARE @kWarenlagerEingang INT;
EXEC dbo.spWarenlagerEingangSchreiben EXEC dbo.spWarenlagerEingangSchreiben
@@ -69,6 +77,37 @@ async function bookWareneingang(transaction, kBenutzer, kWarenLagerPlatz, kArtik
`); `);
} }
export async function bookReturnPositions(
transaction,
kBenutzer,
kWarenLagerPlatz,
returnPositions,
) {
logger.info(`returnBooking: processing ${returnPositions.length} return position(s) at kWarenLagerPlatz=${kWarenLagerPlatz}`);
for (const { kAuftragPosition, quantity } of returnPositions) {
if (!kAuftragPosition || quantity >= 0) {
continue;
}
const returnQty = Math.abs(quantity);
const kArtikel = await getPositionArtikel(transaction, kAuftragPosition);
logger.info(`returnBooking: kBestellPos=${kAuftragPosition} kArtikel=${kArtikel} returnQty=${returnQty}`);
if (!kArtikel) {
logger.info(`returnBooking: kBestellPos=${kAuftragPosition} no kArtikel (free position / Pfand?), skip return booking`);
continue;
}
await bookWareneingang(
transaction,
kBenutzer,
kWarenLagerPlatz,
kArtikel,
returnQty,
'Korrekturbuchung erstellt durch POS-Abgleich (Retoure)',
BUCHUNGSART_WARENEINGANG,
);
logger.info(`returnBooking: successfully booked return of ${returnQty}x kArtikel=${kArtikel} into kWarenLagerPlatz=${kWarenLagerPlatz}`);
}
}
/** /**
* PosStockPositionService.FehlbestandEinbuchen — book missing stock, then re-reserve. * PosStockPositionService.FehlbestandEinbuchen — book missing stock, then re-reserve.
*/ */

View File

@@ -36,6 +36,21 @@ SELECT TOP (@limit)
FROM dbo.tkategorieartikel ka FROM dbo.tkategorieartikel ka
WHERE ka.kArtikel = a.kArtikel WHERE ka.kArtikel = a.kArtikel
) AS categoryIds, ) AS categoryIds,
(
SELECT COUNT(*)
FROM dbo.tKategorieArtikel ka2
INNER JOIN dbo.tArtikel a2 ON a2.kArtikel = ka2.kArtikel AND a2.cAktiv = 'Y'
INNER JOIN dbo.tArtikelBeschreibung ab2 ON ab2.kArtikel = a2.kArtikel AND ab2.kSprache = @languageId
WHERE ka2.kKategorie = (
SELECT TOP 1 ka1.kKategorie
FROM dbo.tKategorieArtikel ka1
WHERE ka1.kArtikel = a.kArtikel
)
AND (
ab2.cName < ab.cName
OR (ab2.cName = ab.cName AND a2.kArtikel < a.kArtikel)
)
) AS sort,
a.nIstVater AS isParent, a.nIstVater AS isParent,
a.kVaterArtikel AS parentArticleId, a.kVaterArtikel AS parentArticleId,
CASE WHEN a.kStueckliste <> 0 THEN '1' ELSE '0' END AS isCompositeProduct, CASE WHEN a.kStueckliste <> 0 THEN '1' ELSE '0' END AS isCompositeProduct,
@@ -204,6 +219,7 @@ export async function getProductList({ cursor = 0, limit = 20 } = {}) {
price: basePrice, price: basePrice,
created_at: formatDateTime(product.createdAt), created_at: formatDateTime(product.createdAt),
lastChanged: String(product.lastChanged), lastChanged: String(product.lastChanged),
sort: String(product.sort ?? 0),
categories_id: categoryIds[0] ?? '0', categories_id: categoryIds[0] ?? '0',
categories: categoryIds.map((categoryId) => ({ categoryId })), categories: categoryIds.map((categoryId) => ({ categoryId })),
prices, prices,