u
This commit is contained in:
@@ -133,32 +133,134 @@ async function allocatePk(transaction, tableName) {
|
||||
return pk;
|
||||
}
|
||||
|
||||
const VERSANDPOSITION_TYPE = 2;
|
||||
const ZAHLUNG_TYPE_ZAHLUNG = 10;
|
||||
const NIST_READONLY_NICHT_AENDERBAR = 2;
|
||||
const NIST_EXTERNE_RECHNUNG_KEINE = 2;
|
||||
|
||||
function parseImportSetting(order) {
|
||||
return toNumber(order.settings?.importSetting, 0);
|
||||
}
|
||||
|
||||
function parseInvoiceSetting(order) {
|
||||
return toNumber(order.settings?.invoiceSetting, 0);
|
||||
}
|
||||
|
||||
/** PosOrderSettingsService: importSetting=0 → NichtAenderbar. */
|
||||
function resolveNIstReadOnly(order) {
|
||||
return parseImportSetting(order) === 0 ? NIST_READONLY_NICHT_AENDERBAR : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* PosOrderSettingsService: importSetting=0 → KeineRechnungKeineAusgabe;
|
||||
* importSetting 2–5 or invoiceSetting Create flag → Wawi invoice.
|
||||
*/
|
||||
function resolveNIstExterneRechnung(order) {
|
||||
const importSetting = parseImportSetting(order);
|
||||
const invoiceSetting = parseInvoiceSetting(order);
|
||||
if (importSetting >= 2 && importSetting <= 5) return 0;
|
||||
if (invoiceSetting & 1) return 0;
|
||||
if (importSetting === 0) return NIST_EXTERNE_RECHNUNG_KEINE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function isVersandposition(item) {
|
||||
return toNumber(item.type, 0) === VERSANDPOSITION_TYPE;
|
||||
}
|
||||
|
||||
function hasVersandposition(items) {
|
||||
return items.some(isVersandposition);
|
||||
}
|
||||
|
||||
function hasNonReturnSaleItems(items) {
|
||||
return items.some((item) => toNumber(item.isReturn, 0) === 0);
|
||||
}
|
||||
|
||||
function shouldInjectSelbstabholerShipping(items) {
|
||||
if (hasVersandposition(items)) return false;
|
||||
return hasNonReturnSaleItems(items);
|
||||
}
|
||||
|
||||
async function lookupVersandArt(transaction, shippingName) {
|
||||
const name = String(shippingName ?? '').trim() || 'Selbstabholer';
|
||||
const tryLookup = async (cName) => {
|
||||
const result = await new sql.Request(transaction)
|
||||
.input('cName', sql.NVarChar, cName)
|
||||
.query(`
|
||||
SELECT TOP 1 kVersandArt, cName, fPrice, fMwSt
|
||||
FROM dbo.tVersandArt
|
||||
WHERE cName = @cName
|
||||
`);
|
||||
return result.recordset[0] ?? null;
|
||||
};
|
||||
|
||||
let row = await tryLookup(name);
|
||||
if (!row && name !== 'Selbstabholer') {
|
||||
row = await tryLookup('Selbstabholer');
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
function syntheticShippingItem(versandArt) {
|
||||
const vat = toNumber(versandArt.fMwSt, 19);
|
||||
const gross = toNumber(versandArt.fPrice, 0);
|
||||
const net = gross / (1 + vat / 100);
|
||||
return {
|
||||
type: String(VERSANDPOSITION_TYPE),
|
||||
quantity: '1',
|
||||
name: versandArt.cName,
|
||||
priceGross: String(gross),
|
||||
priceNet: String(net),
|
||||
vat: String(vat),
|
||||
isReturn: '0',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a Zahlungsart by name; creates it on the fly (including its PK
|
||||
* from dbo.tpk) when it does not exist yet.
|
||||
*/
|
||||
async function resolveZahlungsart(transaction, name, cache) {
|
||||
const key = String(name || 'Bar');
|
||||
const cached = cache.get(key.toLowerCase());
|
||||
const lookupName = String(name || 'Bar');
|
||||
const cacheKey = lookupName.toLowerCase();
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const existing = await new sql.Request(transaction)
|
||||
.input('cName', sql.NVarChar, key)
|
||||
let row = null;
|
||||
const exact = await new sql.Request(transaction)
|
||||
.input('cName', sql.NVarChar, lookupName)
|
||||
.query('SELECT TOP 1 kZahlungsart, cName FROM dbo.tZahlungsart WHERE cName = @cName');
|
||||
row = exact.recordset[0] ?? null;
|
||||
|
||||
if (!row) {
|
||||
const lang = await new sql.Request(transaction)
|
||||
.input('cName', sql.NVarChar, lookupName)
|
||||
.query(`
|
||||
SELECT TOP 1 z.kZahlungsart, z.cName
|
||||
FROM dbo.tZahlungsArtSprache zs
|
||||
INNER JOIN dbo.tZahlungsart z ON z.kZahlungsart = zs.kZahlungsart
|
||||
WHERE zs.cName = @cName
|
||||
`);
|
||||
row = lang.recordset[0] ?? null;
|
||||
}
|
||||
|
||||
if (!row) {
|
||||
const ci = await new sql.Request(transaction)
|
||||
.input('cName', sql.NVarChar, lookupName)
|
||||
.query('SELECT TOP 1 kZahlungsart, cName FROM dbo.tZahlungsart WHERE UPPER(cName) = UPPER(@cName)');
|
||||
row = ci.recordset[0] ?? null;
|
||||
}
|
||||
|
||||
let zahlungsart;
|
||||
if (existing.recordset[0]) {
|
||||
zahlungsart = {
|
||||
kZahlungsart: existing.recordset[0].kZahlungsart,
|
||||
cName: existing.recordset[0].cName,
|
||||
};
|
||||
if (row) {
|
||||
zahlungsart = { kZahlungsart: row.kZahlungsart, cName: row.cName };
|
||||
} else {
|
||||
const kZahlungsart = await allocatePk(transaction, 'tZahlungsart');
|
||||
await new sql.Request(transaction)
|
||||
.input('kZahlungsart', sql.Int, kZahlungsart)
|
||||
.input('cName', sql.NVarChar, key)
|
||||
.input('cName', sql.NVarChar, lookupName)
|
||||
.query(`
|
||||
INSERT INTO dbo.tZahlungsart
|
||||
(kZahlungsart, cName, cPrtString, nLastschrift, cPrtStringVor, cPaymentOption, cKonto,
|
||||
@@ -166,10 +268,10 @@ async function resolveZahlungsart(transaction, name, cache) {
|
||||
nMatchingOptionen, nIstStandard, nAktiv)
|
||||
VALUES (@kZahlungsart, @cName, '', 0, '', '', '', 0, 0, 0, 0, 0, 0, 0, 1)
|
||||
`);
|
||||
zahlungsart = { kZahlungsart, cName: key };
|
||||
zahlungsart = { kZahlungsart, cName: lookupName };
|
||||
}
|
||||
|
||||
cache.set(key.toLowerCase(), zahlungsart);
|
||||
cache.set(cacheKey, zahlungsart);
|
||||
return zahlungsart;
|
||||
}
|
||||
|
||||
@@ -214,8 +316,8 @@ async function createCustomer(transaction, { customerNumber, address, defaults }
|
||||
.input('nDebitorennr', sql.Int, Number(a.debtorNumber) || 0)
|
||||
.query(`
|
||||
DECLARE @returnValue INT;
|
||||
DECLARE @p1 dbo.TYPE_spkundeInsert;
|
||||
INSERT INTO @p1
|
||||
DECLARE @kunde_daten dbo.TYPE_spkundeInsert;
|
||||
INSERT INTO @kunde_daten
|
||||
(kInetKunde, kKundenKategorie, cKundenNr, cFirma, cAnrede, cTitel, cVorname, cName,
|
||||
cStrasse, cPLZ, cOrt, cLand, cTel, cFax, cEMail, dErstellt, cMobil, fRabatt, cUSTID, cNewsletter,
|
||||
cZusatz, cEbayName, kBuyer, cAdressZusatz, cGeburtstag, cWWW, cSperre, cPostID, kKundenGruppe,
|
||||
@@ -229,7 +331,7 @@ async function createCustomer(transaction, { customerNumber, address, defaults }
|
||||
0, @kSprache, @cISO, @cBundesland, @cHerkunft, @cKassenKunde, N'', 0,
|
||||
@nDebitorennr, N'', 0, 0, 0, 0, 0,
|
||||
NULL, 0, 0, 0);
|
||||
EXEC @returnValue = Kunde.spKundeInsert @daten = @p1;
|
||||
EXEC @returnValue = Kunde.spKundeInsert @daten = @kunde_daten;
|
||||
SELECT @returnValue AS kKunde;
|
||||
`);
|
||||
|
||||
@@ -252,14 +354,6 @@ function resolveAuftragCKundenNr(order) {
|
||||
return isWalkInOrder(order) ? '0' : String(order.customerNumber || '');
|
||||
}
|
||||
|
||||
/** POS settings.invoiceSetting: 0 = external/POS invoice (nIstExterneRechnung=1). */
|
||||
function resolveNIstExterneRechnung(order) {
|
||||
const inv = order.settings?.invoiceSetting;
|
||||
if (inv === undefined || inv === null) return 1;
|
||||
if (inv === true || inv === 1 || inv === '1' || String(inv).toLowerCase() === 'true') return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
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"
|
||||
@@ -349,15 +443,19 @@ async function insertOrderItem(transaction, kAuftrag, item) {
|
||||
const discount = toNumber(item.discountPercent, 0);
|
||||
const kSteuerklasse = steuerklasseForVat(vat);
|
||||
const sku = String(item.sku || '').trim();
|
||||
const positionType = isVersandposition(item) ? VERSANDPOSITION_TYPE : toNumber(item.type, 0);
|
||||
|
||||
let kArtikel = null;
|
||||
if (sku) {
|
||||
if (sku && positionType !== VERSANDPOSITION_TYPE) {
|
||||
const result = await new sql.Request(transaction)
|
||||
.input('cArtNr', sql.NVarChar, sku)
|
||||
.query('SELECT TOP 1 kArtikel FROM dbo.tArtikel WHERE cArtNr = @cArtNr');
|
||||
kArtikel = result.recordset[0]?.kArtikel ?? null;
|
||||
}
|
||||
|
||||
const nType = positionType === VERSANDPOSITION_TYPE ? VERSANDPOSITION_TYPE : (kArtikel ? 1 : 0);
|
||||
const nReserviert = nType === VERSANDPOSITION_TYPE ? 0 : 1;
|
||||
|
||||
const result = await new sql.Request(transaction)
|
||||
.input('kArtikel', sql.Int, kArtikel)
|
||||
.input('kAuftrag', sql.Int, kAuftrag)
|
||||
@@ -368,7 +466,8 @@ async function insertOrderItem(transaction, kAuftrag, item) {
|
||||
.input('fVkNetto', sql.Float, priceNet)
|
||||
.input('fMwSt', sql.Float, vat)
|
||||
.input('kSteuerklasse', sql.Int, kSteuerklasse)
|
||||
.input('nType', sql.Int, kArtikel ? 1 : 0)
|
||||
.input('nType', sql.Int, nType)
|
||||
.input('nReserviert', sql.Int, nReserviert)
|
||||
.input('cEinheit', sql.NVarChar, item.unit || '')
|
||||
.input('fRabatt', sql.Float, discount)
|
||||
.query(`
|
||||
@@ -377,7 +476,7 @@ async function insertOrderItem(transaction, kAuftrag, item) {
|
||||
(kArtikel, kAuftrag, cArtNr, nReserviert, cName, cHinweis, fAnzahl, fVkNetto, fMwSt,
|
||||
cNameStandard, kSteuerklasse, nType, cEinheit, fFaktor, kSteuerschluessel, fRabatt)
|
||||
OUTPUT inserted.kAuftragPosition INTO @t
|
||||
VALUES (@kArtikel, @kAuftrag, @cArtNr, 1, @cName, @cHinweis, @fAnzahl, @fVkNetto, @fMwSt,
|
||||
VALUES (@kArtikel, @kAuftrag, @cArtNr, @nReserviert, @cName, @cHinweis, @fAnzahl, @fVkNetto, @fMwSt,
|
||||
@cName, @kSteuerklasse, @nType, @cEinheit, 1.0, 3, @fRabatt);
|
||||
SELECT kAuftragPosition FROM @t;
|
||||
`);
|
||||
@@ -486,11 +585,43 @@ function isOrderDelivered(order) {
|
||||
return deliver === true || String(deliver) === '1' || String(deliver).toLowerCase() === 'true';
|
||||
}
|
||||
|
||||
async function recalculateAuftragEckdaten(transaction, kAuftrag) {
|
||||
await new sql.Request(transaction).input('kAuftrag', sql.Int, kAuftrag).query(`
|
||||
DECLARE @eckdaten_calc Verkauf.TYPE_spAuftragEckdatenBerechnen;
|
||||
INSERT INTO @eckdaten_calc VALUES (@kAuftrag);
|
||||
EXEC Verkauf.spAuftragEckdatenBerechnen @auftrag = @eckdaten_calc;
|
||||
`);
|
||||
}
|
||||
|
||||
async function getOffenerAuftragswert(transaction, kAuftrag) {
|
||||
const result = await new sql.Request(transaction)
|
||||
.input('kAuftrag', sql.Int, kAuftrag)
|
||||
.query(`
|
||||
SELECT ROUND(tAuftragEckdaten.fOffenerWertOhneStorno, 2) AS fOffenerAuftragswert
|
||||
FROM Verkauf.tAuftrag
|
||||
LEFT JOIN Verkauf.tAuftragEckdaten ON tAuftragEckdaten.kAuftrag = tAuftrag.kAuftrag
|
||||
WHERE tAuftrag.kAuftrag = @kAuftrag
|
||||
`);
|
||||
const value = result.recordset[0]?.fOffenerAuftragswert;
|
||||
return value == null ? null : toNumber(value, 0);
|
||||
}
|
||||
|
||||
function isNewPayment(payment) {
|
||||
const paymentId = toNumber(payment.paymentId, 0);
|
||||
return paymentId <= 0;
|
||||
}
|
||||
|
||||
async function insertPayment(transaction, kAuftrag, payment, order, orderDate, zahlungsartCache) {
|
||||
const zahlungsart = await resolveZahlungsart(transaction, payment.paymentMethodName || order.paymentMethodName, zahlungsartCache);
|
||||
const kZahlung = await allocatePk(transaction, 'tZahlung');
|
||||
|
||||
await new sql.Request(transaction)
|
||||
await recalculateAuftragEckdaten(transaction, kAuftrag);
|
||||
const fOffenerWert = await getOffenerAuftragswert(transaction, kAuftrag);
|
||||
if (fOffenerWert == null) {
|
||||
throw new Error(`no open order amount for kAuftrag=${kAuftrag}`);
|
||||
}
|
||||
|
||||
const result = await new sql.Request(transaction)
|
||||
.input('kZahlung', sql.Int, kZahlung)
|
||||
.input('cName', sql.NVarChar, zahlungsart.cName)
|
||||
.input('dDatum', sql.DateTime, orderDate)
|
||||
@@ -499,13 +630,26 @@ async function insertPayment(transaction, kAuftrag, payment, order, orderDate, z
|
||||
.input('kBenutzer', sql.Int, config.kBenutzer)
|
||||
.input('kZahlungsart', sql.Int, zahlungsart.kZahlungsart)
|
||||
.input('cExternalTransactionId', sql.NVarChar, order.externalOrderNumber || '')
|
||||
.input('fOffenerWert', sql.Float, fOffenerWert)
|
||||
.query(`
|
||||
INSERT INTO dbo.tZahlung
|
||||
(kZahlung, cName, dDatum, fBetrag, kBestellung, kBenutzer, nAnzahlung, cHinweis, kZahlungsart,
|
||||
nKeinExport, cExternalTransactionId, nZuweisungstyp, nZahlungstyp, cZuweisungsinfo, nZuweisungswertung)
|
||||
VALUES (@kZahlung, @cName, @dDatum, @fBetrag, @kBestellung, @kBenutzer, 0, '', @kZahlungsart,
|
||||
0, @cExternalTransactionId, 0, 0, '', 0)
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM Verkauf.tAuftragEckdaten
|
||||
WHERE kAuftrag = @kBestellung
|
||||
AND ROUND(fOffenerWertOhneStorno, 2) = ROUND(@fOffenerWert, 2)
|
||||
)
|
||||
BEGIN
|
||||
INSERT INTO dbo.tZahlung
|
||||
(kZahlung, cName, dDatum, fBetrag, kBestellung, kBenutzer, nAnzahlung, cHinweis, kZahlungsart,
|
||||
nKeinExport, cExternalTransactionId, nZuweisungstyp, nZahlungstyp, cZuweisungsinfo, nZuweisungswertung)
|
||||
VALUES (@kZahlung, @cName, @dDatum, @fBetrag, @kBestellung, @kBenutzer, 0, '', @kZahlungsart,
|
||||
0, @cExternalTransactionId, 0, ${ZAHLUNG_TYPE_ZAHLUNG}, '', 0);
|
||||
END
|
||||
SELECT @@ROWCOUNT AS inserted;
|
||||
`);
|
||||
|
||||
if (!result.recordset[0]?.inserted) {
|
||||
throw new Error(`payment insert skipped: open amount changed for kAuftrag=${kAuftrag}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createOrder(order) {
|
||||
@@ -537,6 +681,16 @@ export async function createOrder(order) {
|
||||
const zahlungsart = await resolveZahlungsart(transaction, order.paymentMethodName, zahlungsartCache);
|
||||
const cAuftragsNr = await nextOrderNumber(transaction, orderDate);
|
||||
|
||||
const orderItems = [...(order.orderItems || [])];
|
||||
const versandArt = await lookupVersandArt(transaction, order.shippingName);
|
||||
let kVersandArt = versandArt?.kVersandArt ?? defaults.kVersandArt;
|
||||
if (shouldInjectSelbstabholerShipping(orderItems) && versandArt) {
|
||||
orderItems.push(syntheticShippingItem(versandArt));
|
||||
}
|
||||
|
||||
const nIstReadOnly = resolveNIstReadOnly(order);
|
||||
const nIstExterneRechnung = resolveNIstExterneRechnung(order);
|
||||
|
||||
const resultAuftrag = await new sql.Request(transaction)
|
||||
.input('cAuftragsNr', sql.NVarChar, cAuftragsNr)
|
||||
.input('dErstellt', sql.DateTime, orderDate)
|
||||
@@ -549,11 +703,12 @@ export async function createOrder(order) {
|
||||
.input('kShop', sql.Int, getActiveShopId() || null)
|
||||
.input('cKundenNr', sql.NVarChar, resolveAuftragCKundenNr(order))
|
||||
.input('cVersandlandISO', sql.NVarChar, (order.shippingAddress?.countryIso || 'DE').toUpperCase())
|
||||
.input('kVersandArt', sql.Int, defaults.kVersandArt)
|
||||
.input('kVersandArt', sql.Int, kVersandArt)
|
||||
.input('kZahlungsart', sql.Int, zahlungsart.kZahlungsart)
|
||||
.input('kKundengruppe', sql.Int, kKundengruppe)
|
||||
.input('cExterneAuftragsnummer', sql.NVarChar, order.externalOrderNumber || '')
|
||||
.input('nIstExterneRechnung', sql.Int, resolveNIstExterneRechnung(order))
|
||||
.input('nIstExterneRechnung', sql.Int, nIstExterneRechnung)
|
||||
.input('nIstReadOnly', sql.Int, nIstReadOnly)
|
||||
.query(`
|
||||
DECLARE @t TABLE ([kAuftrag] INT);
|
||||
INSERT INTO Verkauf.tAuftrag
|
||||
@@ -565,7 +720,7 @@ export async function createOrder(order) {
|
||||
VALUES (@cAuftragsNr, @dErstellt, 0, @kBenutzer, @kKunde, @kBenutzer, 1, 1.0,
|
||||
@kFirmaHistory, @kSprache, @cWaehrung, 1.0, 0.0,
|
||||
@cWaehrung, @kPlattform, @kShop, @cKundenNr, @cVersandlandISO, @kVersandArt, @kZahlungsart, @kKundengruppe,
|
||||
@cExterneAuftragsnummer, @nIstExterneRechnung, 'Y', 2, 0, 10);
|
||||
@cExterneAuftragsnummer, @nIstExterneRechnung, 'Y', @nIstReadOnly, 0, 10);
|
||||
SELECT kAuftrag FROM @t;
|
||||
`);
|
||||
|
||||
@@ -578,27 +733,30 @@ export async function createOrder(order) {
|
||||
await insertOrderAddress(transaction, kAuftrag, kKunde, order.billingAddress, 1);
|
||||
|
||||
const deliveredItems = [];
|
||||
for (const item of order.orderItems || []) {
|
||||
for (const item of orderItems) {
|
||||
const kAuftragPosition = await insertOrderItem(transaction, kAuftrag, item);
|
||||
const kPosAuftragPosition = Number.parseInt(item.externalId, 10);
|
||||
await insertPosOrderPositionMapping(transaction, kAuftragPosition, kPosAuftragPosition);
|
||||
if (kAuftragPosition != null) {
|
||||
if (Number.isInteger(kPosAuftragPosition)) {
|
||||
await insertPosOrderPositionMapping(transaction, kAuftragPosition, kPosAuftragPosition);
|
||||
}
|
||||
if (kAuftragPosition != null && !isVersandposition(item)) {
|
||||
deliveredItems.push({ kAuftragPosition, quantity: toNumber(item.quantity, 1) });
|
||||
}
|
||||
}
|
||||
|
||||
for (const payment of order.payments || []) {
|
||||
if (!isNewPayment(payment)) continue;
|
||||
await insertPayment(transaction, kAuftrag, payment, order, orderDate, zahlungsartCache);
|
||||
}
|
||||
|
||||
if (isOrderDelivered(order)) {
|
||||
await deliverOrder(transaction, config.kBenutzer, kAuftrag, defaults.kVersandArt, deliveredItems);
|
||||
await deliverOrder(transaction, config.kBenutzer, kAuftrag, kVersandArt, deliveredItems);
|
||||
}
|
||||
|
||||
await new sql.Request(transaction).input('kAuftrag', sql.Int, kAuftrag).query(`
|
||||
DECLARE @p1 Verkauf.TYPE_spAuftragEckdatenBerechnen;
|
||||
INSERT INTO @p1 VALUES (@kAuftrag);
|
||||
EXEC Verkauf.spAuftragEckdatenBerechnen @auftrag = @p1;
|
||||
DECLARE @eckdaten_calc Verkauf.TYPE_spAuftragEckdatenBerechnen;
|
||||
INSERT INTO @eckdaten_calc VALUES (@kAuftrag);
|
||||
EXEC Verkauf.spAuftragEckdatenBerechnen @auftrag = @eckdaten_calc;
|
||||
`);
|
||||
|
||||
await transaction.commit();
|
||||
|
||||
Reference in New Issue
Block a user