This commit is contained in:
seb
2026-07-14 23:30:16 +02:00
parent 245fb047d7
commit 262fa63f1b
109 changed files with 21566 additions and 367 deletions

View File

@@ -182,7 +182,7 @@ async function nextCustomerNumber(transaction) {
* the search tables). Columns of the table type are named explicitly because
* their order differs between JTL versions.
*/
async function createCustomer(transaction, { customerNumber, address, isPosCustomer, defaults }) {
async function createCustomer(transaction, { customerNumber, address, defaults }) {
const a = address || {};
const iso = (a.countryIso || 'DE').toUpperCase();
const kKundengruppe = Number(a.customerGroupId) || defaults.kKundengruppe;
@@ -209,8 +209,8 @@ async function createCustomer(transaction, { customerNumber, address, isPosCusto
.input('kSprache', sql.Int, config.kSprache)
.input('cISO', sql.NVarChar, iso)
.input('cBundesland', sql.NVarChar, a.state || '')
.input('cHerkunft', sql.NVarChar, isPosCustomer ? 'Kasse' : 'POS-API')
.input('cKassenKunde', sql.Char(1), isPosCustomer ? 'Y' : 'N')
.input('cHerkunft', sql.NVarChar, 'Kasse')
.input('cKassenKunde', sql.Char(1), 'Y')
.input('nDebitorennr', sql.Int, Number(a.debtorNumber) || 0)
.query(`
DECLARE @returnValue INT;
@@ -240,46 +240,55 @@ async function createCustomer(transaction, { customerNumber, address, isPosCusto
return { kKunde, kKundengruppe };
}
function isWalkInOrder(order) {
const customerNumber = String(order.customerNumber || '').trim();
if (!customerNumber || customerNumber === '0') return true;
const billing = order.billingAddress || {};
return (billing.lastName || '') === 'Laufkunde' && !(billing.firstName || '').trim();
}
async function lookupKassenkunde(transaction, defaults) {
const result = await new sql.Request(transaction).query(
"SELECT TOP 1 kKunde, kKundenGruppe FROM dbo.tKunde WHERE cKassenKunde = 'Y' ORDER BY kKunde"
);
if (result.recordset[0]) {
return {
kKunde: result.recordset[0].kKunde,
kKundengruppe: result.recordset[0].kKundenGruppe || defaults.kKundengruppe,
};
}
return null;
}
/**
* Resolves the customer by cKundenNr; creates the Kunde on the fly when it
* does not exist. Orders without a customer number use (or create) the
* Kassenkunde (walk-in customer).
* does not exist. Walk-in orders use (or create) the Kassenkunde.
*/
async function resolveCustomer(transaction, order, defaults) {
const customerNumber = String(order.customerNumber || '').trim();
if (customerNumber) {
const result = await new sql.Request(transaction)
.input('cKundenNr', sql.NVarChar, customerNumber)
.query('SELECT TOP 1 kKunde, kKundenGruppe FROM dbo.tKunde WHERE cKundenNr = @cKundenNr');
if (result.recordset[0]) {
return {
kKunde: result.recordset[0].kKunde,
kKundengruppe: result.recordset[0].kKundenGruppe || defaults.kKundengruppe,
};
}
if (isWalkInOrder(order)) {
const kassenKunde = await lookupKassenkunde(transaction, defaults);
if (kassenKunde) return kassenKunde;
return createCustomer(transaction, {
customerNumber,
customerNumber: await nextCustomerNumber(transaction),
address: order.billingAddress,
isPosCustomer: false,
defaults,
});
}
const kassenKunde = await new sql.Request(transaction).query(
"SELECT TOP 1 kKunde, kKundenGruppe FROM dbo.tKunde WHERE cKassenKunde = 'Y' ORDER BY kKunde"
);
if (kassenKunde.recordset[0]) {
const customerNumber = String(order.customerNumber || '').trim();
const result = await new sql.Request(transaction)
.input('cKundenNr', sql.NVarChar, customerNumber)
.query('SELECT TOP 1 kKunde, kKundenGruppe FROM dbo.tKunde WHERE cKundenNr = @cKundenNr');
if (result.recordset[0]) {
return {
kKunde: kassenKunde.recordset[0].kKunde,
kKundengruppe: kassenKunde.recordset[0].kKundenGruppe || defaults.kKundengruppe,
kKunde: result.recordset[0].kKunde,
kKundengruppe: result.recordset[0].kKundenGruppe || defaults.kKundengruppe,
};
}
return createCustomer(transaction, {
customerNumber: await nextCustomerNumber(transaction),
customerNumber,
address: order.billingAddress,
isPosCustomer: true,
defaults,
});
}
@@ -364,11 +373,41 @@ async function insertOrderItem(transaction, kAuftrag, item) {
}
/**
* Links a JTL-Wawi order (and its positions) back to the originating
* POS order/position ids via Pos.tAuftragMapping / Pos.tAuftragPositionMapping,
* so the POS system can recognise orders it already pushed into JTL-Wawi.
* JTL PosOrderCreationService.CheckIfOrderExists — skip re-import when the POS
* order id is already mapped to a Wawi order for this subshop.
*/
async function insertPosOrderMapping(transaction, kAuftrag, kPosAuftrag) {
async function findExistingPosOrderMapping(kPosAuftrag) {
const kShopSubShop = getActiveShopSubshopId();
if (!Number.isInteger(kPosAuftrag) || !kShopSubShop) {
return null;
}
const result = await getPool()
.request()
.input('kPosAuftrag', sql.Int, kPosAuftrag)
.input('kShopSubShop', sql.Int, kShopSubShop)
.query(`
SELECT TOP 1 m.kAuftrag, a.cAuftragsNr
FROM Pos.tAuftragMapping m
LEFT JOIN Verkauf.tAuftrag a ON a.kAuftrag = m.kAuftrag
WHERE m.kPosAuftrag = @kPosAuftrag
AND m.kShopSubShop = @kShopSubShop
AND m.kAuftrag IS NOT NULL
ORDER BY m.kAuftrag DESC
`);
const row = result.recordset[0];
if (!row?.kAuftrag) {
return null;
}
return row;
}
/**
* Links a JTL-Wawi order back to the originating POS order id.
* MERGE matches JTL CreateOrUpdate outcome for the final committed row.
*/
async function upsertPosOrderMapping(transaction, kAuftrag, kPosAuftrag) {
const kShopSubShop = getActiveShopSubshopId();
if (!Number.isInteger(kPosAuftrag) || !kShopSubShop) {
return;
@@ -378,8 +417,15 @@ async function insertPosOrderMapping(transaction, kAuftrag, kPosAuftrag) {
.input('kPosAuftrag', sql.Int, kPosAuftrag)
.input('kShopSubShop', sql.Int, kShopSubShop)
.query(`
INSERT INTO Pos.tAuftragMapping (kAuftrag, kPosAuftrag, kShopSubShop)
VALUES (@kAuftrag, @kPosAuftrag, @kShopSubShop)
MERGE INTO Pos.tAuftragMapping WITH (HOLDLOCK) AS Target
USING (SELECT @kAuftrag AS kAuftrag, @kPosAuftrag AS kPosAuftrag, @kShopSubShop AS kShopSubShop) AS Source
ON Target.kPosAuftrag = Source.kPosAuftrag
AND Target.kShopSubShop = Source.kShopSubShop
WHEN MATCHED THEN
UPDATE SET Target.kAuftrag = Source.kAuftrag
WHEN NOT MATCHED BY TARGET THEN
INSERT (kAuftrag, kPosAuftrag, kShopSubShop)
VALUES (Source.kAuftrag, Source.kPosAuftrag, Source.kShopSubShop);
`);
}
@@ -441,6 +487,17 @@ async function insertPayment(transaction, kAuftrag, payment, order, orderDate, z
}
export async function createOrder(order) {
const kPosAuftrag = Number.parseInt(order.externalId, 10);
if (Number.isInteger(kPosAuftrag) && kPosAuftrag > 0) {
const existing = await findExistingPosOrderMapping(kPosAuftrag);
if (existing) {
return {
orderId: String(existing.kAuftrag),
orderNumber: existing.cAuftragsNr || '',
};
}
}
const defaults = await getDefaults();
const transaction = new sql.Transaction(getPool());
await transaction.begin(sql.ISOLATION_LEVEL.READ_COMMITTED);
@@ -490,7 +547,7 @@ export async function createOrder(order) {
const kAuftrag = resultAuftrag.recordset[0].kAuftrag;
const kPosAuftrag = Number.parseInt(order.externalId, 10);
await insertPosOrderMapping(transaction, kAuftrag, kPosAuftrag);
await upsertPosOrderMapping(transaction, kAuftrag, kPosAuftrag);
await insertOrderAddress(transaction, kAuftrag, kKunde, order.shippingAddress, 0);
await insertOrderAddress(transaction, kAuftrag, kKunde, order.billingAddress, 1);