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 { getOutgoingWarehouse, getWarehousePlace } from './warehouse.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 { deliverPicklists } from './deliver.js';
import { logger } from '../../logger.js';
@@ -29,17 +29,28 @@ export async function deliverOrder(transaction, kBenutzer, kAuftrag, kVersandArt
const kWarenLager = await getOutgoingWarehouse(transaction);
const kWarenLagerPlatz = await getWarehousePlace(transaction, kWarenLager);
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);
try {
await reservePositions(transaction, kBenutzer, kSessionId, kWarenLager, deliveredItems);
await reservePositions(transaction, kBenutzer, kSessionId, kWarenLager, positiveItems);
await bookStockShortfallsAndRereserve(
transaction,
kBenutzer,
kSessionId,
kWarenLager,
kWarenLagerPlatz,
deliveredItems,
positiveItems,
);
await commitPicklists(transaction, kBenutzer, kSessionId, kAuftrag);
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}`);
throw err;
} 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 {
await discardSession(transaction, kBenutzer, 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;
}
async function getPositionArtikel(transaction, kAuftragPosition) {
export async function getPositionArtikel(transaction, kAuftragPosition) {
const result = await new sql.Request(transaction)
.input('kAuftragPosition', sql.Int, kAuftragPosition)
.query(`
@@ -35,14 +35,22 @@ async function getPositionArtikel(transaction, kAuftragPosition) {
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)
.input('kArtikel', sql.Int, kArtikel)
.input('kWarenLagerPlatz', sql.Int, kWarenLagerPlatz)
.input('kBenutzer', sql.Int, kBenutzer)
.input('fAnzahl', sql.Float, fehlmenge)
.input('cKommentar', sql.NVarChar, POS_SHORTAGE_COMMENT)
.input('kBuchungsart', sql.Int, BUCHUNGSART_WARENEINGANG)
.input('cKommentar', sql.NVarChar, comment)
.input('kBuchungsart', sql.Int, buchungsart)
.query(`
DECLARE @kWarenlagerEingang INT;
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.
*/

View File

@@ -36,6 +36,21 @@ SELECT TOP (@limit)
FROM dbo.tkategorieartikel ka
WHERE ka.kArtikel = a.kArtikel
) 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.kVaterArtikel AS parentArticleId,
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,
created_at: formatDateTime(product.createdAt),
lastChanged: String(product.lastChanged),
sort: String(product.sort ?? 0),
categories_id: categoryIds[0] ?? '0',
categories: categoryIds.map((categoryId) => ({ categoryId })),
prices,