Upgrade auf 26.0.0
Unbedingt die aktuellen Installationsvoraussetzungen vorab prüfen!
Diese Anleitung berücksichtigt immer nur den Sprung von der vorhergehenden Version zu der gerade beschriebenen Version. Bei Upgrades über mehrere Versionen hinweg müssen alle Änderungen der Zwischenversionen ebenfalls beachtet werden! Siehe genereller Ablauf von Upgrades.
Breaking Changes
- Spalten im MultiTable werden nun auch übersetzt, wenn
allowEdittrue, aber spaltenweisefalseist (INV-659) - Die Erstellung der Freigabeliste wurde im Workflow um einen Fehlerstatus erweitert. Zusätzlich wurde die Fehlerausgabe optimiert (INV-614)
- Die RMB-Logik im Workflow wurde erweitert (INV-685)
- Die komplette Logik in
ou.sp.ptpINV.userexit.invplus.validationAmountMultiTablewurde geändert. (INV-565) (Siehe auchou.sp.ptpINV.userexit.invplus.validationAmountMultiTable(INV-565))
Was wurde gemacht?
Zusammenfassung
null-Werte werden in der Lieferanteneinstellungen-Zusammenfassung nun als leere Strings angezeigt (INV-636)- Das Setzen der Positionszeilen wird nun nur noch einmalig im Callback
BeforeSendsignalDefaultausgeführt, nicht mehr doppelt bzw. nochmals inAfterSendsignalDefault(INV-635, INV-644) - Der Signaleingang vor dem Status 100 wurde zum Signalausgang gemacht (INV-640)
- Es wurde ein Bug behoben, der dazu führte, dass das Request Cover nicht angezeigt wurde (INV-665)
- Wenn an einer Mappe das Feld xtractState mit "no" gesetzt ist, landet diese nicht mehr in Status 15 (INV-671)
- Es wurde ein Bug behoben, der dazu führte, dass beim Export nach Dexpro mehrere Mappen die gleiche xtractProcessingId erhalten haben (INV-676)
- Positionszeilen: Für ROB und RMB wurde jeweils der Monitor in Status 15 auf read-only gestellt und mit entsprechenden Hinweisen versehen (INV-675). Außerdem wurde die "Zeile(n) kopieren"-Logik überarbeitet - standardmäßig wird die Positionszeile nun unterhalb der kopierten Zeile eingefügt (INV-677)
- Die korrekte Platzierung vom squeezeViewer Register wurde ins Update-Skript aufgenommen und erfordert keine manuelle Nachbesserung mehr (INV-670)
- Zugriffsprofile, Mappentypen, Ordner und Outbars werden zukünftig nicht mehr via XML-Import, sondern per Scripting angelegt. In diesem Zuge wurden auch die Unterordner des Ordners
ptpInvoiceErrorumbenannt (INV-629) - Im Zuge der zentralen DB-Zugangsdaten Definition wurde
ou.spc.ptpINV.settingsangepasst und nutzt fortan die DB-Credentials aus dem Library-Skript (LIB-516) - Am Register
documentsdes MappentypsptpInvoicewurde dieReihenfolge der AnhängeaufManuellgesetzt, sodass die korrekte Positionierung einer hinzugefügten Sicht-PDF nun über die otris-FunktionRegister.moveDocument()gesteuert wird. (INV-571) - Am Mappentyp
ptpRecipientwurde ein neues FeldpurchaseGroup('Einkäufergruppe') hinzugefügt, dessen Default-Wert aufptpPurchaseGroupgesetzt ist. (INV-686) - Im Zuge der RMB Erweiterung wurden einige Spalten sowohl in der Datenbank als auch in einigen Skripten hinzugefügt. (INV-683, INV-705)
- Für den neuen RMB-Workflow wurde der Job
ou.sp.ptpINV.job.goodsReceiptCheckimplementiert, der für alle RMB-Mappen im neuen Status32die Wareneingangsprüfung durchführt. (INV-688) - Es wurde ein Abgleich von Dexpro-Positionszeilen und den Bestelldaten in der Datenbank implementiert. (INV-693)
- Ist die gelieferte Menge einer Rechnung initial 0, wird für diese stattdessen die Rechnungsmenge gesetzt. (INV-730)
- Es wurde ein generisches ERP-Interface implementiert. (INV-657, INV-662, INV-663, INV-664)
- Der Datenbank-Provider wird fortan auch aus den DBConnection-Credentials der Library geliefert (LIB-519)
- Die
*.sqlDateien zur Einrichtung derptpData-Datenbank wurden entfernt und die Datenbankeinrichtung stattdessen in den Installer integriert. Außerdem werden nun Views standardmäßig gelöscht und neu erstellt, falls Änderungen an diesen vorgenommen wurden. (INV-668) - Die MSSQL-Tabellenspalten, die Sonderzeichen enthalten können, wurden von
VARCHARaufNVARCHARgeändert. Und alle Views, die diese Spalten verwenden, wurden entsprechend angepasst. (INV-735) - Die
*.sqlDateien zur Einrichtung derousp-Datenbank wurden entfernt und die Datenbankeinrichtung stattdessen in den Installer integriert (INV-668). - Für die RMB-Positionszeilen wurde eine neue Spalte
itemQuantityTotaleingeführt, in welcher nach dem Status 15 für die anschließende Wareneingangsprüfung die Rechnungsmenge der Position gespeichert wird. (INV-734) - In Status 15 werden die Feldberechtigungen nun analog zu Status 10 gesetzt. (INV-708)
- Es gibt einen neuen Button
markPositionim RMB-Monitor, der im Status 15 in die entsprechende Zeile im Datenextraktions-Viewer springt (INV-725) - Es gibt eine neue Sektion
unitsin den Invoice-Einstellungen, wo Regeln für die Normalisierung von Mengeneinheiten hinterlegt werden können. (INV-738) - Es gibt eine neue Einstellung "validateAgainstOrderQuantity" (INV-726)
- Es gibt einen neuen Button am RMB-Positions-MultiTable, der nur die unvollständigen Positionen filtert (INV-724)
- Der Button
appendAllOrderItemsam RMB-Monitor wurde ersetzt durch einen ButtonsyncPositions(INV-740) - Der Button
Datenbank aktualisierenwird nicht länger in Status 98, 99 und 100 angezeigt. (INV-667) - Es gibt eine neue Aktion "Rechungskreis ändern" am Mappentyp
ptpInvoice(INV-739) - Die Preiseinheit wird nun im Positions-Matcher als auch in den Berechnungen berücksichtigt (INV-749)
Manuell auszuführende Schritte
Umbennung der Unterordner des Ordners ptpInvoiceError (INV-629)
Die Unterordner wurden wie folgt umbenannt:
ptpInvoice10error->ptpInvoiceError10errorptpInvoice55->ptpInvoiceError55ptpInvoice90->ptpInvoiceError90ptpInvoice91->ptpInvoiceError91ptpInvoice95->ptpInvoiceError95
Die Umbenennung erfolgt automatisiert über das Update-Skript, es muss jedoch überprüft werden, ob die betroffenen Ordner im Customizing verwendet werden und die Stellen entsprechend angepasst werden. Achtung: Unterhalb des Ordners ptpInvoice bzw. ptpInvoice10 gibt es gleichnamige Ordner, die jedoch ihren Namen behalten.
MultiTable: Übersetzung bei spaltenweisem allowEdit false (INV-659)
Im MultiTable werden Spalten nun auch übersetzt, wenn allowEdit in den MultiTable-Optionen auf true, in der jeweiligen Spalte jedoch auf false gesetzt ist.
Bisher fand eine Übersetzung nur statt, wenn allowEdit in den MultiTable-Optionen false war. Dieses Verhalten führte dazu, dass Spalten, die explizit nicht editierbar waren, fälschlicherweise nicht übersetzt wurden.
Dieses Verhalten wurde angepasst: Spalten, die explizit nicht editierbar sind, werden jetzt unabhängig von der allowEdit-Einstellung der MultiTable-Optionen übersetzt.
Soll eine Spalte dennoch nicht übersetzt werden (z. B. wenn der Inhalt kein Übersetzungsstring ist, etwa bei Semikolon-getrennten Werten), kann dies verhindert werden, indem disableInternationalization in der jeweiligen Spalte auf true gesetzt wird.
Die Werte des eInvoiceType-Aufzählungstyps werden nun über ein Skript geladen
Der eInvoiceType war bislang ein Aufzählungstyp, der die Werte XRechnung, ZUGFeRD und Sonstige Rechnung enthielt.
Die Werte wurden nun durch ein Skript ersetzt, das die verfügbaren Typen automatisch lädt. Dadurch ist bei neu hinzukommenden Rechnungstypen oder sonstigen Änderungen der Typen kein „Mappen ändern“ mehr erforderlich.
Wer das Skript zum Laden der Typen verwenden möchte, muss einmalig „Mappen ändern“ ausführen.
Datenbank (ptpData)
Spaltentyp von VARCHAR zu NVARCHAR (INV-735)
Die MSSQL-Tabellenspalten, die Sonderzeichen enthalten können, wurden von VARCHAR auf NVARCHAR geändert.
Folgende Tabellen sind betroffen:
cfgStatusCodes, cfgVatCode, costcenter, costunit, impersonalAccount, invoiceHeader, invoiceItem, invoiceState, orderItem, paymentTerm, recipient, recipientCurrencies, vatcode, vendor, xtractCountry, xtractCurrencySymbol, xtractVatRates und recipientAddress.
orderItem
Im Zuge der RMB-Erweiterung (INV-683. INV-705) wurden die Spalten receiptRequired, quantityDelivered, quantityUnitDelivered, priceUnit, vatRate, vatCode, costUnit, costCenter und impersonalAccount in der Tabelle orderItem hinzugefügt.
invoiceItem
Im Zuge der RMB-Erweiterung (INV-683) wurden die Spalten itemReceiptRequired, itemQuantityDelivered, itemQuantityUnitDelivered, itemPriceUnit und itemOrderQuantityUnit in der Tabelle invoiceItem hinzugefügt.
Geänderte Views
Durch das Ausführen der Installation werden geänderte Views automatisch neu erstellt.
Dies kann verhindert werden, indem das Skript ou.spc.library.dbConnections über ein Customizing-Skript angepasst wird und bei der jeweiligen Verbindung dropViews auf false gesetzt wird. In diesem Fall müssen vor der Installation alle Views in der Datenbank entsprechend den Updates manuell angepasst werden.
Unabhängig davon gilt: Bei Customizing sollte unbedingt vorher ein Backup der Views erstellt werden.
Wenn dropViews auf true gesetzt ist, werden die alten View-Create-Statements, soweit möglich, im Loglevel info protokolliert.
Spaltentyp von VARCHAR zu NVARCHAR und dadurch entstandene View Änderungen (INV-735)
Da die MSSQL-Tabellenspalten, die Sonderzeichen enthalten können, von VARCHAR auf NVARCHAR geändert wurden, mussten auch die entsprechenden Views angepasst werden.
Folgende Views sind betroffen:
viewDexproOrderItem, viewDexproCostCenter, viewDexproCountry, viewDexproVendor, view_sfi_cost_center, view_sfi_country, view_sfi_currency_symbol, view_sfi_order_item, view_sfi_vat_rates und view_sfi_vendor.
viewActiveOrderItems
Im Zuge der RMB-Erweiterung (INV-683, INV-705) wurden auch hier die Spalten receiptRequired, quantityDelivered, quantityUnitDelivered, priceUnit, vatRate, vatCode, costUnit, costCenter und impersonalAccount hinzugefügt.
viewActiveOrderItemsNotAssigned
Im Zuge der RMB-Erweiterung (INV-683, INV-705) wurden auch hier die Spalten receiptRequired, quantityDelivered, quantityUnitDelivered, priceUnit, vatRate, vatCode, costUnit, costCenter und impersonalAccount hinzugefügt und es werden nun auch Bestellungen selektiert, bei denen die gelieferte Menge kleiner ist als die bestellte Menge. Außerdem wurde die Filterung, ob eine Bestellung schon verknüpft ist durch DISTINCT und NOT NULL in der WHERE-Bedingung der Subquery robuster gemacht.
Ausführen der Datenbank-Änderungen
Alle Anweisungen ab der aktuell installierten Version (z.B. v24.0.1) aus den Datenbankscripten (mariadb.sql / mssql.sql) ausführen.
Dieser Schritt ist nur bis Version v24.0.1 notwendig, da ab dieser Version Datenbankänderungen über die Invoice installation stattfinden.
Neuer RMB-Workflow: Kompatibilität mit alter Workflow-Version sicherstellen (INV-685)
Um sicherzustellen, dass RMBs, die sich noch in der alten Workflow-Version befinden, weiterhin mit den neuen Skripten kompatibel sind, müssen ein paar wenige Anpassungen in cust-Skripten gemacht werden (sofern zu den genannten spc-Skripten noch kein cust-Skript existiert, muss dieses angelegt werden):
ou.spc.ptpINV.filetype.property.dFROnFileViewScriptou.spc.ptpINV.workflow.action.incomingEventou.spc.ptpINV.workflow.decision.RMBou.spc.ptpINV.workflow.decision.autoIndexou.spc.ptpINV.lib.index.validation
Die notwendigen Kompatibilitäts-Anpassungen können untenstehendem Punkt Portalskripte -> SPC-Skripte entnommen werden und sollten wieder entfernt werden, sobald sich keine Mappen mehr im alten Workflow befinden.
Das Skript ou.spc.ptpINV.workflow.guard.backToIndex (und sein zugehöriges cust-Skript sofern vorhanden) sollte ebenfalls entfernt werden, sobald sich keine Mappen mehr im alten Workflow befinden.
Portalskripte
SP-Skripte
ou.sp.ptpINV.lib.xtract (INV-635, INV-644)
Die Funktion validateOrderData setzt nun die Positionszeilen nicht mehr. Dies wurde in das Callback BeforeSendsignalDefault verlagert (siehe SPC-Skripte).
Aktuell ist die validateOrderData-Funktion auch nur eine Hülle. Sie wird zukünftig die Positionszeilen aus der Datenextraktion gegen die Positionszeilen der Bestelldaten validieren.
ou.sp.ptpINV.userexit.invplus.validationAmountMultiTable (INV-565)
Hier wurden einige Funktionen geändert, da durch das Ticket eine größere Umstellung nötig war. Es haben sich sowohl Funktionsnamen, Eingabewerte als auch Rückgabewerte geändert. Falls diese Funktionalität abseits des Standards verwendet wird, muss dieser Fall genauer angeschaut werden. Am einfachsten wäre es, den alten Stand zu kopieren, umzubenennen und diesen im Customizing zu verwenden, falls die alte Logik wirklich noch benötigt wird. Falls es um die validate-Funktion geht, ist ein Umschreiben durchaus möglich.
ou.sp.ptpINV.userexit.invplus.locale (INV-565)
Das InvplusLocale-Interface hat sich verändert. Statt rmbError und rmbErrorWithinMargin gibt es nun netError und netErrorWithinMargin, da nur zwischen Netto (netError und netErrorWithinMargin) und Netto mit Steuern (error und errorWithinMargin) unterschieden wird. Dies war notwendig, da ROB und RMB angeglichen wurden und die vorherige Logik daher auch falsch war.
Außerdem wurden die jeweiligen Sprachdateien entsprechend angepasst und um fehlende Platzhalter wie {{amount}}, {{positionsAmount}} und {{currency}} ergänzt.
ou.sp.ptpINV.userexit.functions.multiTable (INV-565)
Mit ou.sp.ptpINV.userexit.functions.multiTable kam ein neuer User Exit hinzu, der sowohl für ROB als auch für RMB gedacht ist.
In diesen wurde die Funktion getItemTotalAmountFooter ausgelagert und angepasst, sodass sie sowohl für den ROB- als auch für den RMB-Monitor verwendet werden kann.
ou.sp.ptpINV.userexit.functions.robMultiTable (INV-565)
In ou.sp.ptpINV.userexit.functions.robMultiTable wurde die Funktion getItemTotalAmountFooter entfernt, da diese nun in ou.sp.ptpINV.userexit.functions.multiTable ausgelagert wurde.
ou.sp.ptpINV.userexit.functions.rmbMultiTable (INV-565, INV-705, INV-728)
Die veraltete (deprecated) Funktion getRMBAmountDifferences wurde so angepasst, dass diese wieder wie zuvor funktioniert, da sich in ou.sp.ptpINV.userexit.invplus.validationAmountMultiTable die grundlegende Logik geändert hat.
Beim Ausführen der Aktion „Bestellzeilen laden“ in RMBs wird geprüft, welche Bestellzeilen bereits in der MultiTable vorhanden sind, um diese im TableDialog nicht erneut zur Auswahl anzubieten.
In der bisherigen Implementierung erfolgt diese Prüfung ausschließlich anhand der Bestellposition. Diese Logik funktioniert nur korrekt, wenn dem RMB genau eine Bestellnummer zugeordnet ist.
Enthält ein RMB jedoch mehrere Bestellnummern, führt die Filterung zu einem fehlerhaften Verhalten: Bestellpositionen aus noch nicht hinzugefügten Bestellungen werden ebenfalls ausgeschlossen, sofern sie dieselbe Positionsnummer wie bereits vorhandene Bestellzeilen haben. Dadurch werden im Dialog weniger Bestellzeilen angezeigt, obwohl diese noch nicht ausgewählt wurden.
Ursache des Fehlers ist, dass die Bestellnummer bei der Identifikation bereits vorhandener Bestellzeilen nicht berücksichtigt wird und somit keine eindeutige Zuordnung möglich ist.
Änderung: Die Übergabe der bereits ausgewählten Bestellzeilen an den TableDialog der Aktion „Bestellzeilen laden“ wurde angepasst.
Beschreibung: Statt ausschließlich der Bestellpositionen (orderItemNumbers) wird nun eine Liste aus Bestellnummer und Bestellposition (orderItems - orderNumbers und orderItemNumbers) übergeben. Dadurch können bereits hinzugefügte Bestellzeilen eindeutig identifiziert werden, auch wenn ein RMB mehrere Bestellnummern enthält. Dies ist notwendig, um die korrekte Filterung im Dialog zu ermöglichen.
Das was für die Aktion „Bestellzeilen laden“ hinzugefügt wurde, wurde auch für die Dialoge „Bestellung auswählen“, welche bei Änderung bestimmter Tabellenzellen ausgelöst wird um eine passende Bestellposition zu finden.
Außerdem wurde die Spallte Steuersatz sowohl für den Dialog „Bestellzeilen laden“ als auch „Bestellung auswählen“ hinzugefügt.
Zudem wird der Rabatt beim Laden aus der Datenbank („Bestellzeilen laden“ oder durch Userexit „Bestellung auswählen“) nun standardmäßig auf 0 gesetzt.
ou.sp.ptpINV.lib.rmb.orderItems (INV-705)
In der FUnktion findByOrderNumbersNotAssigned wurde die Filterung, ob eine Bestellung schon verknüpft ist durch DISTINCT und NOT NULL in der WHERE-Bedingung robuster gemacht.
Die Funktion getOrderItemsWhereConditions wurde hinzugefügt, um leichter eine WHERE Bedinnung für die Tabelle orderItems oder z.B. die Views viewActiveOrderItems und viewActiveOrderItemsNotAssigned erstellen zu können.
Die Funktion syncVatCodes setzt für RMB-Positionszeilen den itemVatCode anhand von itemVatRate und recipient: bei genau einem Treffer wird dieser verwendet, bei mehreren Treffern wird ein einzelner Default bevorzugt, sonst wird kein Code gesetzt; nutzt ein Cache pro Steuersatz und berechnet die Zeilenbeträge neu.
ou.sp.ptpINV.lib.masterData.dexpro (LIB-519)
Der DBProvider wird fortan durchgereicht, damit der SelectBuilder bei Abfragen mit Offset oder Limit überall korrekt angesprochen werden. Dadurch ist die Bestimmung des Providers nun zuverlässig und konsistent zur Datenbankkonfiguration.
SPC-Skripte
Gadget_ou.spc.ptpVendorSettings.filetype.field.html.summary (INV-636)
Im Skript Gadget_ou.spc.ptpVendorSettings.filetype.field.html.summary wurde die Funktion getVendorData um einen null-Werte Check erweitert. Dieser ersetzt, falls vorhanden, null-Werte durch einen leeren String.
Vorher:
function getVendorData(recipient, vendorId) {
const { ptpConnections } = (0, ou_spc_ptpINV_settings_1.getSettings)();
const db = ptpConnections.getDatabaseConnection("ptpData");
try {
const rows = ou_sp_SelectBuilder_1.SelectBuilder.from("viewVendor", db)
.select("vendorId", "string")
.select("vendorName", "string")
.select("street", "string")
.select("zipcode", "string")
.select("city", "string")
.select("countrycode", "string")
.where(`recipient ='${recipient}'`)
.andWhere(`vendorId = '${vendorId}'`)
.orderBy("vendorId")
.execute();
return rows.length > 0 ? rows[0] : null;
} catch (error) {
logger.error(
`Error while getting vendor data for recipient ${recipient} (${vendorId}): ${error.message ?? error}`
);
throw error;
} finally {
if (db) {
db.close();
}
}
}
Nachher:
function getVendorData(recipient, vendorId) {
const { ptpConnections } = (0, ou_spc_ptpINV_settings_1.getSettings)();
const db = ptpConnections.getDatabaseConnection("ptpData");
try {
const rows = ou_sp_SelectBuilder_1.SelectBuilder.from("viewVendor", db)
.select("vendorId", "string")
.select("vendorName", "string")
.select("street", "string")
.select("zipcode", "string")
.select("city", "string")
.select("countrycode", "string")
.where(`recipient ='${recipient}'`)
.andWhere(`vendorId = '${vendorId}'`)
.orderBy("vendorId")
.execute();
/* Neuer Code */
return rows.length > 0
? Object.fromEntries(Object.entries(rows[0]).map(([key, value]) => [key, value ?? ""]))
: null;
/* Ende neuer Code */
} catch (error) {
logger.error(
`Error while getting vendor data for recipient ${recipient} (${vendorId}): ${error.message ?? error}`
);
throw error;
} finally {
if (db) {
db.close();
}
}
}
ou.spc.ptpINV.callbacks (INV-635, INV-644, INV-685)
Folgender Code wurde zum Teil von ptpINVCallbackRegistry.registerAfterSendsignalDefault in das Callback ptpINVCallbackRegistry.registerBeforeSendsignalDefault verschoben.
Folgender alter Code muss daher aus dem registerAfterSendsignalDefault entfernt werden:
if (docFile.orderNumber && docFile.docType.substring(1) === "MB") {
try {
(0, ou_spc_ptpINV_lib_1.fillPositionsByOrder)(docFile);
if (docFile.invoiceItems && JSON.parse(docFile.invoiceItems || "[]").length > 0) {
try {
logger.debug(`RMB validateNetAmountWithPositions: ${docFile.invoiceItems}`);
(0, ou_sp_ptpINV_lib_validation_rmb_1.validateNetAmountWithPositions)(docFile);
data.updateStatus("20", true);
} catch (error) {
logger.error(`RMB validateNetAmountWithPositions: ${error}`);
data.updateStatus("20", false);
}
}
} catch (error) {
logger.error(`Error while loading positions for ${docFile.getid()} (ordernumber was ${docFile.orderNumber}) ${error}`);
docFile.insertStatusEntry("Bestellabfrage", error);
}
}
In modifizierter Form muss er nun im registerBeforeSendsignalDefault nach folgenden Zeilen eingefügt werden:
(0, ou_sp_ptpINV_lib_xtract_1.getDataFromDocumentReaderJSON)(docFile);
if (!docFile.vendorId) {
return;
}
/* hier den folgenden Code einfügen */
if (docFile.orderNumber && docFile.docType.substring(1) === "MB") {
try {
(0, ou_spc_ptpINV_lib_1.fillPositionsByOrder)(docFile);
} catch (error) {
logger.error(`Error while loading positions for ${docFile.getid()} (ordernumber was ${docFile.orderNumber}) ${error}`);
docFile.insertStatusEntry("Bestellabfrage", error);
}
}
/* Ende des modifizierten Codes */
Außerdem kann der Import von ou.sp.ptpINV.lib.validation.rmb aus ou.spc.ptpINV.callbacks entfernt werden.
Am Ende der Funktion executeDirectly müssen schließlich noch folgende neue Callbacks hinzugefügt werden:
...
function executeDirectly() {
...
/* Neuer Code */
/** ************************************************************ */
// ou.sp.ptpINV.workflow.receivesignal.checkGoodsReceiptRMB
/** ************************************************************ */
ou_sp_ptpINV_lib_callbacks_1.ptpINVCallbackRegistry.registerBeforeReceivesignalCheckGoodsReceiptRMB(data => {
logger.debug("START callback Before ou.sp.ptpINV.workflow.receivesignal.checkGoodsReceiptRMB");
logger.debug("ENDE callback Before ou.sp.ptpINV.workflow.receivesignal.checkGoodsReceiptRMB");
});
ou_sp_ptpINV_lib_callbacks_1.ptpINVCallbackRegistry.registerAfterReceivesignalCheckGoodsReceiptRMB(data => {
logger.debug("START callback After ou.sp.ptpINV.workflow.receivesignal.checkGoodsReceiptRMB");
logger.debug("ENDE callback After ou.sp.ptpINV.workflow.receivesignal.checkGoodsReceiptRMB");
});
/** ************************************************************ */
// ou.sp.ptpINV.workflow.decision.goodsReceipt
/** ************************************************************ */
ou_sp_ptpINV_lib_callbacks_1.ptpINVCallbackRegistry.registerBeforeDecisionGoodsReceipt(data => {
logger.debug("START callback Before ou.sp.ptpINV.workflow.decision.goodsReceipt");
logger.debug("ENDE callback Before ou.sp.ptpINV.workflow.decision.goodsReceipt");
});
ou_sp_ptpINV_lib_callbacks_1.ptpINVCallbackRegistry.registerAfterDecisionGoodsReceipt(data => {
logger.debug("START callback After ou.sp.ptpINV.workflow.decision.goodsReceipt");
logger.debug("ENDE callback After ou.sp.ptpINV.workflow.decision.goodsReceipt");
});
/* Ende neuer Code */
}
...
Für die neue Dexpro-Logik muss eine If-Bedingung in der Callback-Funktion zu
registerBeforeSendsignalDefault angepasst werden (INV-693).
Dazu muss folgender Import hinzugefügt werden:
const ou_sp_ptpINV_lib_xtract_types_1 = require("ou.sp.ptpINV.lib.xtract.types");
Dann muss die If-Bedingung von angepasst werden:
Vorher:
if (!docFile.vendorId) {
return;
}
/* ab hier den Code ändern */
if (docFile.orderNumber && docFile.docType.substring(1) === "MB") {
/* Ende */
try {
(0, ou_spc_ptpINV_lib_1.fillPositionsByOrder)(docFile);
} catch (error) {
Nachher:
if (!docFile.vendorId) {
return;
}
/* Anfang neuer Code */
if (docFile.orderNumber && docFile.docType.substring(1) === "MB" && (0, ou_sp_ptpINV_lib_xtract_1.getXtractorType)() !== ou_sp_ptpINV_lib_xtract_types_1.XtractorType.Dexpro) {
/* Ende neuer Code */
try {
(0, ou_spc_ptpINV_lib_1.fillPositionsByOrder)(docFile);
} catch (error) {
ou.spc.ptpINV.lib.release (INV-614)
In diesem Skript muss ou.spc.ptpINV.lib.release.list.functions importiert werden via:
const ou_spc_ptpINV_lib_release_list_functions_1 = require("ou.spc.ptpINV.lib.release.list.functions");
und in folgenden Funktionen das Error-Handling erweitert werden:
getResponsibleUserFromFirstCostcenter
Vorher:
const getResponsibleUserFromFirstCostcenter = docFile => {
const rows = JSON.parse(docFile.invoiceItems || "[]");
if (!rows || rows.length <= 0) {
throw new Error("Es muss mindestens eine Positionszeile gefüllt sein");
}
const costCenter = rows[0].itemCostCenter;
if (!costCenter) {
throw new Error("In der ersten Positionszeile ist die Kostenstelle nicht gefüllt");
}
const costCenters = new FileResultset("cfgCostCenter", `costCenter ~ '${costCenter}'`);
if (costCenters.size() !== 1) {
throw new Error(`Es konnte keine (eindeutige) Kostenstellenkonfiguration zur KST ${costCenter} gefunden werden`);
}
const firstFile = costCenters.first();
const {
responsibleUser
} = firstFile;
if (!responsibleUser) {
throw new Error("Es konnte kein Kostenstellen Verantwortlicher gefunden werden.");
}
return responsibleUser;
};
exports.getResponsibleUserFromFirstCostcenter = getResponsibleUserFromFirstCostcenter;
Nachher:
const getResponsibleUserFromFirstCostcenter = docFile => {
const rows = JSON.parse(docFile.invoiceItems || "[]");
if (!rows || rows.length <= 0) {
/* geänderter Code */
throw new ou_spc_ptpINV_lib_release_list_functions_1.ReleaseError("Es muss mindestens eine Positionszeile gefüllt sein", ou_spc_ptpINV_lib_release_list_functions_1.ReleaseErrorLevel.Warning);
/* Ende geänderter Code */
}
const costCenter = rows[0].itemCostCenter;
if (!costCenter) {
/* geänderter Code */
throw new ou_spc_ptpINV_lib_release_list_functions_1.ReleaseError("In der ersten Positionszeile ist die Kostenstelle nicht gefüllt", ou_spc_ptpINV_lib_release_list_functions_1.ReleaseErrorLevel.Warning);
/* Ende geänderter Code */
}
const costCenters = new FileResultset("cfgCostCenter", `costCenter ~ '${costCenter}'`);
if (costCenters.size() !== 1) {
/* geänderter Code */
throw new ou_spc_ptpINV_lib_release_list_functions_1.ReleaseError(`Es konnte keine (eindeutige) Kostenstellenkonfiguration zur KST ${costCenter} gefunden werden`);
/* Ende geänderter Code */
}
const firstFile = costCenters.first();
const {
responsibleUser
} = firstFile;
if (!responsibleUser) {
/* geänderter Code */
throw new ou_spc_ptpINV_lib_release_list_functions_1.ReleaseError("Es konnte kein Kostenstellen Verantwortlicher gefunden werden.");
/* Ende geänderter Code */
}
return responsibleUser;
};
exports.getResponsibleUserFromFirstCostcenter = getResponsibleUserFromFirstCostcenter;
getReleaseRole
Vorher:
const getReleaseRole = (releaseUser, returnTemporaryReleaseRoleIfSet = true) => {
logger.debug(`Looking for cfgReleaseEmployee, employee = '${releaseUser}'`);
const employeeFiles = new FileResultset("cfgReleaseEmployee", `employee = '${releaseUser}'`);
// Es wurde keine Konfiguration für den Freigeber gefunden
if (employeeFiles.size() != 1) {
logger.error(`No release employee file found for user ${releaseUser}`);
throw new Error(`Es wurde keine Konfiguration für den Freigeber ${releaseUser} gefunden`);
}
const employee = employeeFiles.first();
let finalReleaseRole = employee.releaseRole;
if (returnTemporaryReleaseRoleIfSet === true && employee.temporalReleaseRole) {
logger.debug(`Found temporalRelease role: ${employee.temporalReleaseRole}, check for date`);
if (employee.temporalReleaseFrom || employee.temporalReleaseTo) {
const isTemporalReleaseActive = (0, ou_sp_MomentHelper_1.moment)().isBetween(employee.temporalReleaseFrom, employee.temporalReleaseTo, "days", "[]");
if (isTemporalReleaseActive) {
finalReleaseRole = employee.temporalReleaseRole;
logger.info(`Using temporary release role ${finalReleaseRole} for employee ${employee.employee} (from ${employee.temporalReleaseFrom} to ${employee.temporalReleaseTo}).`);
}
} else {
finalReleaseRole = employee.temporalReleaseRole;
}
}
logger.debug(`Found employee ${employee.employee}, releaseRole: ${finalReleaseRole}`);
logger.debug(`Looking for cfgReleaseRole, releaseRole = '${finalReleaseRole}'`);
const roleFiles = new FileResultset("cfgReleaseRole", `releaseRole = '${finalReleaseRole}'`);
if (roleFiles.size() != 1) {
logger.error(`No release role found for ${finalReleaseRole}`);
throw new Error(`Es wurde keine Freigaberolle zu ${finalReleaseRole} gefunden.`);
}
const firstRole = roleFiles.first();
logger.debug(`Found release role ${firstRole.releaseRole}, releaseAmount: ${firstRole.releaseAmount}`);
return firstRole;
};
exports.getReleaseRole = getReleaseRole;
Nachher:
const getReleaseRole = (releaseUser, returnTemporaryReleaseRoleIfSet = true) => {
logger.debug(`Looking for cfgReleaseEmployee, employee = '${releaseUser}'`);
const employeeFiles = new FileResultset("cfgReleaseEmployee", `employee = '${releaseUser}'`);
// Es wurde keine Konfiguration für den Freigeber gefunden
if (employeeFiles.size() != 1) {
logger.error(`No release employee file found for user ${releaseUser}`);
/* geänderter Code */
throw new ou_spc_ptpINV_lib_release_list_functions_1.ReleaseError(`Es wurde keine Konfiguration für den Freigeber ${releaseUser} gefunden`);
/* Ende geänderter Code */
}
const employee = employeeFiles.first();
let finalReleaseRole = employee.releaseRole;
if (returnTemporaryReleaseRoleIfSet === true && employee.temporalReleaseRole) {
logger.debug(`Found temporalRelease role: ${employee.temporalReleaseRole}, check for date`);
if (employee.temporalReleaseFrom || employee.temporalReleaseTo) {
const isTemporalReleaseActive = (0, ou_sp_MomentHelper_1.moment)().isBetween(employee.temporalReleaseFrom, employee.temporalReleaseTo, "days", "[]");
if (isTemporalReleaseActive) {
finalReleaseRole = employee.temporalReleaseRole;
logger.info(`Using temporary release role ${finalReleaseRole} for employee ${employee.employee} (from ${employee.temporalReleaseFrom} to ${employee.temporalReleaseTo}).`);
}
} else {
finalReleaseRole = employee.temporalReleaseRole;
}
}
logger.debug(`Found employee ${employee.employee}, releaseRole: ${finalReleaseRole}`);
logger.debug(`Looking for cfgReleaseRole, releaseRole = '${finalReleaseRole}'`);
const roleFiles = new FileResultset("cfgReleaseRole", `releaseRole = '${finalReleaseRole}'`);
if (roleFiles.size() != 1) {
logger.error(`No release role found for ${finalReleaseRole}`);
/* geänderter Code */
throw new ou_spc_ptpINV_lib_release_list_functions_1.ReleaseError(`Es wurde keine Freigaberolle zu ${finalReleaseRole} gefunden.`);
/* Ende geänderter Code */
}
const firstRole = roleFiles.first();
logger.debug(`Found release role ${firstRole.releaseRole}, releaseAmount: ${firstRole.releaseAmount}`);
return firstRole;
};
exports.getReleaseRole = getReleaseRole;
</details>
---
<details>
<summary>`ou.spc.ptpINV.lib.release.list` (INV-614)</summary>
Hier muss an der Funktion `createReleaseList()` das Error-Handling erweitert werden.
Vorher:
```javascript
const createReleaseList = options => {
const {
currentReleaseUser,
currentVerificationUser,
amount,
existingEntries,
minimumNumberOfReleaseUsers
} = options;
logger.info(`Creating release list for currentReleaseUser ${currentReleaseUser}, amount ${amount}`);
(0, ou_spc_ptpINV_lib_release_list_functions_1.validateCreateReleaseListOptions)(options);
const releaseSystemUser = context.findSystemUser(currentReleaseUser);
if (!releaseSystemUser) {
logger.error(`No user found for ${currentReleaseUser}`);
throw new Error(`Es wurde kein Benutzer '${currentReleaseUser}' gefunden.`);
}
}
Nachher:
const createReleaseList = options => {
const {
currentReleaseUser,
currentVerificationUser,
amount,
existingEntries,
minimumNumberOfReleaseUsers
} = options;
logger.info(`Creating release list for currentReleaseUser ${currentReleaseUser}, amount ${amount}`);
(0, ou_spc_ptpINV_lib_release_list_functions_1.validateCreateReleaseListOptions)(options);
const releaseSystemUser = context.findSystemUser(currentReleaseUser);
if (!releaseSystemUser) {
logger.error(`No user found for ${currentReleaseUser}`);
/* Geänderter Code */
throw new ou_spc_ptpINV_lib_release_list_functions_1.ReleaseError(`Es wurde kein Benutzer '${currentReleaseUser}' gefunden.`);
/* Ende geänderter Code */
}
}
ou.spc.ptpINV.lib.release.list.functions (INV-614)
Im Skript müssen an zwei Funktionen Error im Error-Handling durch ReleaseError ersetzt werden:
validateCreateReleaseListOptions()
Vorher:
function validateCreateReleaseListOptions(options) {
logger.debug(`Validating input: ${JSON.stringify(options)}`);
const {
currentReleaseUser,
amount,
currentVerificationUser
} = options;
if (!currentReleaseUser) {
logger.error("No release user passed");
throw new Error("Es wurde kein Freigeber übergeben");
}
if (!currentVerificationUser) {
logger.error("No verification user passed");
throw new Error("Es wurde kein fachlicher Prüfer übergeben");
}
if (!amount || amount <= 0) {
logger.error("No amount passed");
throw new Error("Es wurde kein Betrag übergeben");
}
}
Nachher:
function validateCreateReleaseListOptions(options) {
logger.debug(`Validating input: ${JSON.stringify(options)}`);
const {
currentReleaseUser,
amount,
currentVerificationUser
} = options;
if (!currentReleaseUser) {
logger.error("No release user passed");
/* geänderter Code */
throw new ReleaseError("Es wurde kein Freigeber übergeben");
/* Ende geänderter Code */
}
if (!currentVerificationUser) {
logger.error("No verification user passed");
/* geänderter Code */
throw new ReleaseError("Es wurde kein fachlicher Prüfer übergeben");
/* Ende geänderter Code */
}
if (!amount || amount <= 0) {
logger.error("No amount passed");
/* geänderter Code */
throw new ReleaseError("Es wurde kein Betrag übergeben");
/* Ende geänderter Code */
}
}
ensureMinNumberOfReleaseUsers()
Vorher:
function ensureMinNumberOfReleaseUsers(releaseList, minimumNumberOfReleaseUsers) {
const requiredReleaseUsers = releaseList.filter(entry => !entry.isVerification && entry.active && entry.required);
if (requiredReleaseUsers.length < minimumNumberOfReleaseUsers) {
throw new Error(`Mindestens ${minimumNumberOfReleaseUsers} Freigeber sind erforderlich. Es wurden nur ${requiredReleaseUsers.length} Freigeber im Freigabepfad gefunden.`);
}
}
Nachher:
function ensureMinNumberOfReleaseUsers(releaseList, minimumNumberOfReleaseUsers) {
const requiredReleaseUsers = releaseList.filter(entry => !entry.isVerification && entry.active && entry.required);
if (requiredReleaseUsers.length < minimumNumberOfReleaseUsers) {
/* geänderter Code */
throw new ReleaseError(`Mindestens ${minimumNumberOfReleaseUsers} Freigeber sind erforderlich. Es wurden nur ${requiredReleaseUsers.length} Freigeber im Freigabepfad gefunden.`);
/* Ende geänderter Code */
}
}
Außerdem muss die folgende Definition von ReleaseError ganz unten an den Programmcode angefügt werden:
var ReleaseErrorLevel;
(function (ReleaseErrorLevel) {
ReleaseErrorLevel["Warning"] = "warning";
ReleaseErrorLevel["Error"] = "error";
})(ReleaseErrorLevel || (exports.ReleaseErrorLevel = ReleaseErrorLevel = {}));
class ReleaseError extends Error {
level;
constructor(message, level = ReleaseErrorLevel.Error) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
this.name = "ReleaseListError";
this.level = level;
}
}
exports.ReleaseError = ReleaseError;
ou.spc.ptpINV.workflow.sendsignal.createReleaseList (INV-614)
Hier werden dem Skript zwei Importe hinzugefügt:
const ou_spc_ptpINV_lib_release_list_functions_1 = require("ou.spc.ptpINV.lib.release.list.functions");
const releaseErrorAttribute = "$releaseError";
und in der Funktion executeDirectly() das Error-Handling erweitert:
Vorher:
function executeDirectly() {
const docFile = otrAssert_1.default.getThrowingObject(context.file);
try {
const releaseList = (0, ou_spc_ptpINV_lib_release_list_1.createReleaseList)({
currentReleaseUser: (0, ou_spc_ptpINV_lib_release_1.getResponsibleUserFromFirstCostcenter)(docFile),
currentVerificationUser: docFile.verificationUser,
amount: docFile.totalAmount,
existingEntries: JSON.parse(docFile.verification || "[]"),
minimumNumberOfReleaseUsers: (0, ou_spc_ptpINV_settings_1.getSettings)().minimumNumberOfReleaseUsers
});
docFile.setFieldValue("verification", JSON.stringify(releaseList));
docFile.sync();
} catch (error) {
logger.error(`Skipping release list creation due to error: ${error}`);
docFile.insertStatusEntry("Freigabeliste erstellen fehlgeschlagen", `Es konnte keine Freigabeliste erstellt werden: ${error}`);
}
}
Nachher:
function executeDirectly() {
const docFile = otrAssert_1.default.getThrowingObject(context.file);
try {
/* geänderter Code */
docFile.setAttribute(releaseErrorAttribute, "");
/* Ende geänderter Code */
const releaseList = (0, ou_spc_ptpINV_lib_release_list_1.createReleaseList)({
currentReleaseUser: (0, ou_spc_ptpINV_lib_release_1.getResponsibleUserFromFirstCostcenter)(docFile),
currentVerificationUser: docFile.verificationUser,
amount: docFile.totalAmount,
existingEntries: JSON.parse(docFile.verification || "[]"),
minimumNumberOfReleaseUsers: (0, ou_spc_ptpINV_settings_1.getSettings)().minimumNumberOfReleaseUsers,
});
docFile.setFieldValue("verification", JSON.stringify(releaseList));
docFile.sync();
}
catch (error) {
logger.error(`Skipping release list creation due to error: ${error}`);
/* geänderter Code */
const statusEntry = error instanceof ou_spc_ptpINV_lib_release_list_functions_1.ReleaseError
? `Es konnte keine Freigabeliste erstellt werden: ${error.message}`
: "Es konnte keine Freigabeliste erstellt werden";
docFile.insertStatusEntry("Freigabeliste erstellen fehlgeschlagen", statusEntry);
const errorLevel = error instanceof ou_spc_ptpINV_lib_release_list_functions_1.ReleaseError ? error.level : "error";
docFile.setAttribute(releaseErrorAttribute, errorLevel);
/* Ende geänderter Code */
}
}
ou.spc.ptpINV.lib.gadgets.verification (INV-665)
Hier muss eine fehlerhafte Zeile wie folgt ersetzt werden:
...
static getOptions(entries, maxNumberOfVerificationRows) {
const verifications = this.parseEntries(entries, maxNumberOfVerificationRows);
const options = {
showOptions: false,
showHeader: false,
showFooter: false,
hover: false,
border: false,
bordered: false,
striped: false,
id: "verification",
allowEdit: false,
allowInsert: false,
allowDelete: false,
select: false,
rows: verifications,
rowStyle: opt => {
return opt.row.isCurrent ? "font-weight: bold;" : "";
},
columns: {
status: {
type: "icon",
label: "Status",
width: "30px"
},
message: {
label: "Info",
disableInternationalization: true,
attributes: {
/* Alter Code
title(value, opts) {
*/
// Neuer Code:
title: (value, opts) => {
return opts.row.tooltip || "";
}
}
}
}
};
return options;
}
...
ou.spc.ptpINV.settings (INV-676, INV-688, LIB-516, LIB-519, INV-738, INV-726)
Die Eigenschaft extractExportBatchSize muss auf 1 gesetzt werden, weil
diese bei Dexpro keine Auswirkung hat. Falls Insiders für die Datenextraktion
zum Einsatz kommen sollte, kann die Eigenschaft so belassen werden.
// Hier kann die Menge an Dokumenten pro Stapel geändert werden
// ACHTUNG: Diese Eigenschaft darf nur angepasst werden, wenn Insiders für die Datenextraktion verwendet wird
// Für Dexpro hat diese Eigenschaft keine Auswirkung
extractExportBatchSize: 1,
Die hartcodierten DB-Credentials databaseConnections werden nun durch einen Import aus dem zentralen Library-Skript ersetzt (LIB-516).
Dazu muss zum einen folgender Import hinzugefügt werden:
const { databaseConnections } = require("ou.spc.library.dbConnections");
const ou_spc_library_dbConnections_1 = require("ou.spc.library.dbConnections");
Folgender Code muss entfernt werden:
(...)
getDatabaseConnection(name) {
const cn = this.databases[name];
if (cn === undefined) return null;
return new DBConnection(cn.type, cn.string, cn.user, cn.pass);
},
};
/* Anfang - dieser Teil ist fortan überflüssig */
const dbUser = util.getEnvironment("OUSP_DATABASE_USER");
const dbPassword = util.getEnvironment("OUSP_DATABASE_PASSWORD");
const databaseConnections = {
ptpData: {
dbType: "odbc",
dbName: "ptpData",
dbUser,
dbPassword,
},
ousp: {
dbType: "odbc",
dbName: "ousp",
dbUser,
dbPassword,
},
};
/* Ende - dieser Teil ist fortan überflüssig */
Object.keys(ou_spc_library_dbConnections_1.databaseConnections).forEach((key) => {
const connection = ou_spc_library_dbConnections_1.databaseConnections[key];
ptpConnections.registerDatabaseConnection(
key,
(...)
Außerdem muss die neue Eigenschaft goodsReceiptCheckTimeoutDays hinzugefügt werden:
// Wie viele Tage soll auf den Wareneingang gewartet werden
goodsReceiptCheckTimeoutDays: 5,
Diese Eigenschaft wird für den neuen RMB-Workflow benötigt und legt fest, wie viele Tage RMBs auf den Wareneingang warten, bevor sie vom neuen Job ou.sp.ptpINV.job.goodsReceiptCheck in den Status Prüfen Einkauf geschickt werden.
Zudem wird eine neue Funktion getDatabaseConnectionAndProvider hinzugefügt und der provider (DbSystem) der DBConnection als optionaler Parameter hinzugefügt (LIB-519).
(...)
registerDatabaseConnection(name, type, connString, user, pass, /* Neu */ dbSystem) {
if (!name) {
throw new Error("registering database failed: no name for this connection defined.");
}
const connection = new DBConnection(type, connString, user, pass);
if (!connection || connection.getLastError() != null) {
throw new Error(`Registering ${name} database failed: couldn't establish a connection with the given data.\nConnection: ${type}:${connString}:${user}\n\n${connection ? connection.getLastError() : "connection undefined"}`);
}
connection.close();
this.databases[name] = {
type,
string: connString,
user,
pass,
/* Neuer Code */
dbSystem
/* Ende neuer Code */
};
},
getDatabaseConnection(name) {
const cn = this.databases[name];
if (cn === undefined) return null;
return new DBConnection(cn.type, cn.string, cn.user, cn.pass);
},
/* Anfang neuer Code */
getDatabaseConnectionAndProvider(name) {
const cn = this.databases[name];
if (cn === undefined) return null;
const dbProvider = cn.dbSystem;
return {
db: new DBConnection(cn.type, cn.string, cn.user, cn.pass),
provider: dbProvider
};
}
/* Ende neuer Code */
};
Object.keys(databaseConnections).forEach(key => {
const connection = databaseConnections[key];
ptpConnections.registerDatabaseConnection(key, connection.dbType, connection.dbName, connection.dbUser, connection.dbPassword, /* Neu */ connection.dbSystem);
});
(...)
Es muss das neue Object units zum settings-Objekt hinzugefügt werden.
const settings = {
(...)
units: {
unitGroups: {
STK: ["ST", "STK", "STÜCK", "STCK", "PCE", "PCS", "PC"]
},
disableUnitNormalization: false
}
};
(...)
Zudem muss die neue Eigenschaft settings.validateAgainstOrderQuantity
hinzugefügt werden:
(...)
const settings = {
(...)
validateAgainstOrderQuantity: false
};
(...)
ou.spc.ptpINV.settings.monitorRMB (INV-675, INV-677, INV-683, INV-705, INV-688, INV-696, INV-565, INV-693, INV-730, INV-734, INV-725, INV-724, INV-740, INV-743, INV-749)
Hier wurde ein Check auf Stati [15, 91, 95] eingeführt und entsprechend werden Monitor-Einstellungen gesetzt. Dazu wird im Falle von Status 15, 91 oder 95 eine Nachricht hinterlegt, die im MultiTable erscheint, wenn dieser leer ist. Außerdem wurde die Logik überarbeitet, welche die Zeilen kopiert - der Standardfall ist nun hier ein Einfügen direkt nach der kopierten Zeile.
Außerdem wurde im Zuge der RMB-Erweiterung (INV-683) folgende Spalten im MultiTable hinzugefügt:
Wareneingang erforderlich (itemReceiptRequired), Wareneingangsmengeneinheit (itemQuantityUnitDelivered), Preiseinheit (itemPriceUnit, allerdings ausgeblendet), Bestellmengeneinheit (itemOrderQuantityUnit), Steuerbetrag (itemTotalVatAmount), Steuersatz (itemVatRate), Steuercode (itemVatCode), Bruttobetrag (itemTotalAmount), Kostenstelle (itemCostCenter), Kostenträger (itemCostUnit) und Sachkonto (itemImpersonalAccount), Menge gesamt (itemQuantityTotal).
Desweiteren wurden die Fehlerstati für die Zeilen und Zellen erweitert:
- Wenn der Wareneingang erforderlich ist und die Wareneingangsmenge oder Wareneingangsmengeneinheit fehlt
- Wenn die Menge, Wareneingangsmenge oder Bestellmenge von einander abweichen
- Wenn die Mengeneinheit, Wareneingangsmengeneinheit oder Bestellmengeneinheit von einander abweichen
Zudem wurde die Spalte itemGoodsReceiptQuantity in itemQuantityDelivered umbenannt und die Spalte itemQuantityUnit hinter die Spalte itemQuantity verschoben.
Außerdem wurden Typos behoben, die Kommentare für die Spaltenbreite gelöscht und Tooltips für die Tabellen Header Spalten hinzugefügt, um die Tabellen Header Spalten abgekürzten Bezeichner auch ausführlich dazustellen.
Außerdem wurden eine Spalte und ein Button für den Dexpro-Datenbankabgleich hinzugefügt (INV-693). In den Stati 15, 91 und 95 wird nur noch dieser Button angezeigt und nicht mehr der Button Bestellzeilen laden. (INV-734)
Außerdem wurde eine neue Spalte für das Markieren der Positionen auf der Rechnung hinzugefügt (INV-725).
Außerdem wird die Spalte itemPriceUnit nun standardmäßig angezeigt (INV-749), direkt nach der Spalte itemOrderSingleAmount.
Für dieses Skript am besten einen Entwickler heranziehen, der dann die Diff-Ansicht besorgen kann, was das Nachvollziehen der Änderungen erleichtert.
Vorher:
(...)
const monitorOptions = {
showOptions: true,
allowInsert: false,
additionalParams: {
userConfigurationAttribute,
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)(),
currencySymbol,
},
rowStyle: options => {
const isRowInvalid = (options.row.itemGoodsReceiptQuantity ? +options.row.itemQuantity !== +options.row.itemGoodsReceiptQuantity : +options.row.itemQuantity !== +options.row.itemOrderQuantity) || +options.row.itemSingleAmount !== +options.row.itemOrderSingleAmount;
return isRowInvalid ? "background: #F5C4C4" : "";
},
columns: {
(...)
itemTag: {
label: "",
allowEdit: false,
allowInsert: false
// width: "20px"
},
itemOrderNumber: {
label: "de:Bestell-Nr;en:Order no",
// width: "80px",
selectOnFocus: true,
change: "getOrderByNumber"
},
itemReceiptNumber: {
label: "de:Wareneingang-Nr;en:Receipt no",
// width: "140px",
selectOnFocus: true,
change: "getOrderByReceipt"
},
itemOrderItemNumber: {
label: "de:Bestellpos.;en:Orderpos no",
// width: "80px",
selectOnFocus: true,
change: "getOrderByOrderItemNumber"
},
itemDeliveryNoteNumber: {
label: "de:Lieferschein-Nr;en:Delivery no",
// width: "140px",
selectOnFocus: true,
change: "getOrderByDeliveryNoteNumber"
},
itemArticleNumber: {
label: "de:Artikel-Nr;en:Article no",
// width: "80px",
selectOnFocus: true,
(...)
},
// Optional aktivierbar
itemArticleName: {
visible: false,
label: "de:Artikel-Name;en:Article name",
// width: "140px",
selectOnFocus: true
},
itemQuantity: {
label: "de:Menge;en:Quantity",
// width: "120px",
type: "number",
selectOnFocus: true,
change: (value, options) => {
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
CalculationAmountMultiTable.calculateTotalAmountForRow(options.row, 0, 0);
},
attributes: {
style: (value, options) => {
const quantity = +value;
const orderQuantity = +options.row.itemOrderQuantity;
const goodsReceiptQuantity = +options.row.itemGoodsReceiptQuantity || null;
const isCellInvalid = goodsReceiptQuantity ? quantity !== goodsReceiptQuantity : quantity !== orderQuantity;
return `color: ${isCellInvalid ? "#f44336" : "inherit"}`;
}
}
},
itemOrderQuantity: {
label: "de:Bestellmenge;en:Order Quantity",
// width: "120px",
type: "number",
(...)
attributes: {
disabled: "disabled"
}
},
itemGoodsReceiptQuantity: {
(...)
},
itemQuantityUnit: {
label: "de:ME;en:QU",
// width: "120px",
selectOnFocus: true
},
itemSingleAmount: {
label: "de:Einzelpreis;en:Single amount",
// width: "90px",
type: "currency",
(...)
attributes: {
style: (value, options) => {
const quantity = +value;
const orderQuantity = +options.row.itemOrderSingleAmount;
const isCellInvalid = quantity != orderQuantity;
return `color: ${isCellInvalid ? "#f44336" : "inherit"}`;
}
}
},
itemOrderSingleAmount: {
label: "de:Bestell-EP;en:Order-SA",
type: "currency",
currency: currencySymbol,
// width: "120px",
selectOnFocus: true,
(...)
},
itemDiscountRate: {
label: "de:Rab.(%);en:Disc.(%)",
// width: "120px",
type: "number",
(...)
},
itemDiscountAmount: {
label: "de:Rab.;en:Disc.",
// width: "120px",
type: "currency",
(...)
},
iitemTotalNetAmount: {
label: "de:Nettobetrag;en:Net amount",
type: "currency",
currency: currencySymbol,
// width: "120px",
allowEdit: false,
allowInsert: false,
attributes: {
disabled: "disabled"
},
footerTemplate: options => {
const ValidationAmountMultiTable = mustGetClientModuleMember("invplus", "validationAmountMultiTable");
const validator = new ValidationAmountMultiTable(options.rows,
// @ts-expect-error untyped
options.table.additionalParams.roundingErrorMargin);
const result = validator.validate();
const BigConstructor = mustGetClientModule("Big");
const positionsAmount = options.rows.reduce((acc, row) => acc.plus(+row.itemTotalNetAmount || 0), new BigConstructor(0)).round(2).toNumber();
const textColor = result.success ? "#1b5e20" : "#f44336";
const styles = [result.showWarning ? "display: flex;justify-content: space-between;align-items: center" : "text-align: right", "border-top: solid 2px #000", "border-bottom: double 3px #000", "margin: -4px", "padding-right: 4px", "padding-left: 4px", "font-weight: bold", `color: ${textColor}`];
const formatted = documents.sdk.utils.formatNumber(positionsAmount, null, null, 2);
return `<div style='${styles.join(";")}' title='${result.message}'>${result.showWarning ? "<span class='entypo warning' style='color: #333333'></span>" : ""}${formatted} ${options.table.additionalParams.currencySymbol}</div>`;
}
},
itemBookingText: {
label: "de:Buchungstext;en:Booking text",
// width: "120px",
selectOnFocus: true
},
itemComment: {
label: "de:Kommentar;en:comment",
// width: "120px",
selectOnFocus: true
}
},
(...)
buttons: {
(...)
copyRow: {
label: "de:Zeile(n) kopieren;en:copy row(s)",
tooltip: "de:Ausgewählte Zeile(n) kopieren;en:Copy selected row(s)",
enabled: "multi",
click: options => {
options.selectedRows.forEach(options.addRow);
options.commit();
}
},
(...)
compareAmount: {
label: "de:Nettobetrag prüfen;en:check after deduction",
tooltip: "de:Nettobetrag aller Zeilen splitten;en:Check all rows after deduction",
click: options => {
(...)
}
},
(...)
}
}
(...)
Nachher:
/* neuer Import */
const ou_sp_ptpINV_lib_rmb_positionMatching_1 = require("ou.sp.ptpINV.lib.rmb.positionMatching");
const ou_sp_ptpINV_lib_xtract_1 = require("ou.sp.ptpINV.lib.xtract");
/* Ende neuer Import */
(...)
/* geänderter Code */
const inStatus15 = context.file.globalState === "15";
const isDocumentsIntegratedValidation = (0, ou_sp_ptpINV_lib_xtract_1.hasDocumentsIntegratedValidation)();
const copyDialogTitle = context.getFromSystemTable("PTPINV_MONTIOR_COPY_TITLE");
const copyDialogBody = context.getFromSystemTable("PTPINV_MONTIOR_COPY_BODY");
(0, ou_sp_ptpINV_lib_rmb_positionMatching_1.updateDocFileWithDbMatchStatus)(context.file);
const beforeGoodsReceiptCheck = ["15", "91", "95"].includes(context.file.globalState);
const inState35 = (context.file as ptpInvoice).globalState === "35";
/* Ende geänderter Code */
const monitorOptions = {
/* geänderter Code */
showOptions: true,
allowEdit: true,
emptyMessage: beforeGoodsReceiptCheck ? context.getFromSystemTable("PTPINV_MONITOR_RMB_EMPTYMESSAGE") : undefined,
/* Ende geänderter Code */
allowInsert: false,
additionalParams: {
userConfigurationAttribute,
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)(),
currencySymbol,
/* geänderter Code */
copyDialogTitle,
copyDialogBody,
onlyShowUnmatchedPositions: false,
validateItemQuantities: (orderNumber, orderItemNumber, itemQuantityTotal, rows) => {
const itemQuantitySum = rows.reduce((acc, row) => {
if (row.itemOrderNumber === orderNumber && row.itemOrderItemNumber === orderItemNumber) {
return acc + (+row.itemQuantity || 0);
}
return acc;
}, 0);
return (+itemQuantityTotal || 0) === itemQuantitySum;
},
styleQuantity: (beforeGoodsReceiptCheckInput, itemReceiptRequiredInput, itemQuantityInput, itemOrderQuantityInput, itemQuantityDeliveredInput, isItemQuantityTotalValid = true) => {
const itemQuantity = +itemQuantityInput;
const itemOrderQuantity = +itemOrderQuantityInput;
const itemQuantityDelivered = +itemQuantityDeliveredInput;
let isCellInvalid;
if (beforeGoodsReceiptCheckInput) {
isCellInvalid = itemQuantity !== itemOrderQuantity;
} else {
isCellInvalid = !isItemQuantityTotalValid || (itemQuantityDeliveredInput ?? false) && itemQuantity !== itemQuantityDelivered || !itemReceiptRequiredInput && itemQuantity !== itemOrderQuantity;
}
return `color: ${isCellInvalid ? "#f44336" : "inherit"}`;
},
styleQuantityUnit: (beforeGoodsReceiptCheckInput, itemQuantityUnit, itemOrderQuantityUnit, itemQuantityUnitDelivered) => {
let isCellInvalid;
if (beforeGoodsReceiptCheckInput) {
isCellInvalid = itemQuantityUnit !== itemOrderQuantityUnit;
} else {
isCellInvalid = (itemQuantityUnitDelivered ?? false) && (itemQuantityUnit !== itemQuantityUnitDelivered || itemOrderQuantityUnit !== itemQuantityUnitDelivered) || itemQuantityUnit !== itemOrderQuantityUnit;
}
return `color: ${isCellInvalid ? "#f44336" : "inherit"}`;
},
styleNetAmountPerVatValidation: options => {
const ValidationAmountMultiTable = mustGetClientModuleMember("invplus", "validationAmountMultiTable");
const roundingErrorMargin = options.table.additionalParams.roundingErrorMargin || 0;
const validator = new ValidationAmountMultiTable(options.rows, roundingErrorMargin);
const result = validator.validate();
const isCellInvalid = options.row.itemVatRate && result.netWithVat.missingAmounts?.[options.row.itemVatRate];
return `color: ${isCellInvalid ? "#f44336" : "inherit"}`;
},
titleNetAmountPerVatValidation: options => {
const ValidationAmountMultiTable = mustGetClientModuleMember("invplus", "validationAmountMultiTable");
const roundingErrorMargin = options.table.additionalParams.roundingErrorMargin || 0;
const validator = new ValidationAmountMultiTable(options.rows, roundingErrorMargin);
const result = validator.validate();
const isCellInvalid = options.row.itemVatRate && result.netWithVat.missingAmounts?.[options.row.itemVatRate];
return isCellInvalid ? result.netWithVat.errorList?.[options.row.itemVatRate] : "";
},
beforeGoodsReceiptCheck
/* Ende geänderter Code */
},
/* geänderter Code */
rowStyle: options => {
const {
itemReceiptRequired,
itemQuantityDelivered,
itemQuantityUnitDelivered,
itemSingleAmount,
itemOrderSingleAmount,
itemQuantityTotal,
itemOrderNumber,
itemOrderItemNumber
} = options.row;
const ValidationAmountMultiTable = mustGetClientModuleMember("invplus", "validationAmountMultiTable");
const roundingErrorMargin = options.table.additionalParams.roundingErrorMargin || 0;
const validator = new ValidationAmountMultiTable(options.rows, roundingErrorMargin);
const result = validator.validate();
const isItemReceiptRequiredInvalid = itemReceiptRequired && (!(itemQuantityDelivered ?? false) || !(itemQuantityUnitDelivered ?? false));
const isSingleAmountInvalid = +itemSingleAmount !== +itemOrderSingleAmount;
let isItemQuantityTotalInvalid = false;
if (!options.table.additionalParams.beforeGoodsReceiptCheck && itemReceiptRequired) {
isItemQuantityTotalInvalid = !options.table.additionalParams.validateItemQuantities(itemOrderNumber, itemOrderItemNumber, itemQuantityTotal, options.rows);
}
const isRowInvalid = isItemReceiptRequiredInvalid || isSingleAmountInvalid || !result.netWithVat.success || isItemQuantityTotalInvalid;
return isRowInvalid ? "background: #F5C4C4" : "";
},
rowFilter: options => {
const additionalParams = options.table.additionalParams;
const {
onlyShowUnmatchedPositions
} = additionalParams;
const matchResult = options.row.itemMatchesDatabase;
return onlyShowUnmatchedPositions ? matchResult !== "FullMatch" : true;
},
/* Ende geänderter Code */
columns: {
(...)
itemTag: {
label: "",
allowEdit: false,
allowInsert: false
/* gelöschter Code */
},
/* Neue Spalte für die Markierung der Position im Viewer */
markPosition: {
type: "button",
label: "",
allowEdit: false,
visible: inStatus15 && isDocumentsIntegratedValidation,
attributes: {
title: context.getLocaleValue("de:Position in Rechnung anzeigen;en:Show position in invoice"),
style: "width: 24px; height: 24px; display: flex; justify-content: center; align-items: center; margin: 0 auto"
},
render: () => {
return "<span class='mdi mdi-format-color-highlight' style='margin: 0 auto; font-size: 16px; margin-top: 4px;' />";
},
click: (_, row, options) => {
const squeezeViewer = document.getElementById("Viewer");
const parentContainer = squeezeViewer?.closest("[data-module-name='SiphinitiViewer']");
const isParentContainerInvisible = parentContainer ? parentContainer.style.visibility === "hidden" || globalThis.getComputedStyle(parentContainer).display === "none" : true;
if (isParentContainerInvisible) {
documentsContext.openMessageDialog(documents.sdk.utils.getLocaleValue("de:Warnung;en:Warning"), documents.sdk.utils.getLocaleValue("de:Der Viewer für die Datenextraktion ist nicht geöffnet. Bitte öffnen Sie das Register 'Datenextraktion' und versuchen Sie es erneut.;en:The viewer for data extraction is not open. Please open the 'Data Extraction' register and try again."));
return;
}
const index = options.table.rows.indexOf(row);
if (index === -1) return;
const Squeeze = mustGetClientModule("SqueezeGlobal");
Squeeze?.markPosition(index);
}
},
/* Ende neue Spalte */
/* Neue Spalte für Datenbankabgleich */
itemMatchesDatabase: {
label: "DB?",
tooltip: "de:Datenbankabgleich;en:Database match",
allowEdit: false,
attributes: {
style: "display: flex; justify-content: center; align-items: center; margin: 0 auto",
},
render: opts => {
const status = opts.row.itemMatchesDatabase;
const createIcon = (iconClass, color, title) => {
return `<span class='${iconClass}' style='color: ${color}' title='${title}'></span>`;
};
switch (status) {
case "FullMatch":
return createIcon("ionicon ion-md-checkmark-circle", "#4caf50", "Übereinstimmung gefunden");
case "PartialMatch":
return createIcon("ionicon ion-md-warning", "#ff9800", "Teilweise Übereinstimmung");
case "NotFound":
return createIcon("ionicon ion-md-alert", "#f44336", "Keine Übereinstimmung");
default:
return "";
}
}
},
/* Ende neue Spalte für Datenbankabgleich */
itemOrderNumber: {
label: "de:Bestell-Nr;en:Order no",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Bestellnummer;en:Order number",
/* Ende hinzugefügter Code */
selectOnFocus: true,
change: "getOrderByNumber"
},
itemReceiptNumber: {
/* hinzugefügter Code */
visible: !beforeGoodsReceiptCheck,
/* Ende hinzugefügter Code */
label: "de:Wareneingang-Nr;en:Receipt no",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Wareneingangsnummer;en:Receipt number",
/* Ende hinzugefügter Code */
selectOnFocus: true,
change: "getOrderByReceipt"
},
/* hinzugefügter Code */
itemReceiptRequired: {
label: "de:WE?;en:GR?",
tooltip: "de:Wareneingang erforderlich;en:Receipt required",
type: "checkbox",
allowEdit: false,
attributes: {
style: (value, options) => {
const itemReceiptRequired = value;
const {
itemQuantityDelivered,
itemQuantityUnitDelivered
} = options.row;
const isCellInvalid = itemReceiptRequired && (!(itemQuantityDelivered ?? false) || !(itemQuantityUnitDelivered ?? false));
return isCellInvalid ? "accent-color: #f44336" : "";
}
}
},
/* Ende hinzugefügter Code */
itemOrderItemNumber: {
label: "de:Bestellpos.;en:Orderpos no",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Bestellpositionsnummer;en:Order position number",
/* Ende hinzugefügter Code */
selectOnFocus: true,
change: "getOrderByOrderItemNumber"
},
itemDeliveryNoteNumber: {
/* hinzugefügter Code */
visible: !beforeGoodsReceiptCheck,
/* Ende hinzugefügter Code */
label: "de:Lieferschein-Nr;en:Delivery no",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Lieferscheinnummer;en:Delivery number",
/* Ende hinzugefügter Code */
selectOnFocus: true,
change: "getOrderByDeliveryNoteNumber"
},
itemArticleNumber: {
label: "de:Artikel-Nr;en:Article no",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Artikelnummer;en:Article number",
/* Ende hinzugefügter Code */
selectOnFocus: true,
(...)
},
// Optional aktivierbar
itemArticleName: {
visible: false,
label: "de:Artikel-Name;en:Article name",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Artikel-Name;en:Article name",
/* Ende hinzugefügter Code */
selectOnFocus: true
},
itemQuantity: {
label: "de:Menge;en:Quantity",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: beforeGoodsReceiptCheck ? "de:Rechnungsmenge;en:Invoice quantity" : "de:Menge zur Betragsberechnung der Zeile. Bei Teillieferungen (mehrere Zeilen pro Rechnungsposition) sollte die Summe der Mengen der Positionsmenge auf der Rechnung entsprechen.;en:Quantity used for calculating the row amount. In the case of partial deliveries (multiple rows per invoice item), the sum of the quantities should correspond to the item quantity on the invoice.",
/* Ende hinzugefügter Code */
type: "number",
selectOnFocus: true,
/* gelöschter Code */
/* hinzugefügter Code */
change: (value, options) => {
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
CalculationAmountMultiTable.calculateTotalAmountForRow(options.row, 0, 0);
if (!options.row.get("itemQuantityTotal")) {
options.row.set("itemQuantityTotal", value);
}
if (options.row.get("itemReceiptRequired")) {
return;
}
const itemQuantity = +options.row.get("itemQuantity");
if (!itemQuantity) {
return;
}
options.row.set("itemQuantityDelivered", itemQuantity);
},
/* Ende hinzugefügter Code */
attributes: {
/* geänderter Code */
style: (value, options) => {
const isItemQuantityTotalValid = !options.row.itemReceiptRequired || options.table.additionalParams.validateItemQuantities(options.row.itemOrderNumber, options.row.itemOrderItemNumber, options.row.itemQuantityTotal, options.rows);
return options.table.additionalParams.styleQuantity(options.table.additionalParams.beforeGoodsReceiptCheck, options.row.itemReceiptRequired, value, options.row.itemOrderQuantity, options.row.itemQuantityDelivered, isItemQuantityTotalValid);
},
/* Ende geänderter Code */
/* hinzugefügter Code */
title: (value, options) => {
const Utils = mustGetClientModuleMember("invplus", "utils");
const msgDe = `Die Summe der Mengen für diese Bestellposition stimmt nicht mit der Menge der Rechnungsposition (${options.row.itemQuantityTotal}) überein`;
const msgEn = `The quantity sum for this order item does not match the invoice item quantity (${options.row.itemQuantityTotal})`;
if (!options.table.additionalParams.beforeGoodsReceiptCheck && options.row.itemReceiptRequired && !options.table.additionalParams.validateItemQuantities(options.row.itemOrderNumber, options.row.itemOrderItemNumber, options.row.itemQuantityTotal, options.rows)) {
return Utils.getLocaledMessage({
de: msgDe,
en: msgEn
});
}
return "";
}
/* Ende hinzugefügter Code */
}
},
/* gelöschter Code */
itemQuantityUnit: {
label: "de:ME;en:QU",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Mengeneinheit;en:Quantity Unit",
/* Ende hinzugefügter Code */
selectOnFocus: true,
/* hinzugefügter Code */
attributes: {
style: (value, options) => options.table.additionalParams.styleQuantityUnit(options.table.additionalParams.beforeGoodsReceiptCheck, value, options.row.itemOrderQuantityUnit, options.row.itemQuantityUnitDelivered)
}
/* Ende hinzugefügter Code */
},
/* hinzugefügter Code */
itemQuantityTotal: {
label: "de:Menge gesamt;en:Total Quantity",
tooltip: "de:Rechnungsmenge;en:Invoice quantity",
type: "number",
allowEdit: false,
allowInsert: false,
visible: inState35,
attributes: {
disabled: "disabled",
},
},
/* Ende hinzugefügter Code */
/* hinzugefügter Code */
itemQuantityDelivered: {
label: "de:WE-Menge;en:Del. Quantity",
tooltip: "de:Wareneingangsmenge;en:Delivered Quantity",
type: "number",
allowEdit: false,
allowInsert: false,
visible: !beforeGoodsReceiptCheck,
attributes: {
disabled: "disabled",
style: (value, options) => options.table.additionalParams.styleQuantity(options.table.additionalParams.beforeGoodsReceiptCheck, options.row.itemReceiptRequired, options.row.itemQuantity, options.row.itemOrderQuantity, value)
}
},
itemQuantityUnitDelivered: {
label: "de:WE-ME;en:Del. QU",
tooltip: "de:Wareneingangsmengeneinheit;en:Delivered Quantity Unit",
allowEdit: false,
allowInsert: false,
visible: !beforeGoodsReceiptCheck,
attributes: {
disabled: "disabled",
style: (value, options) => options.table.additionalParams.styleQuantityUnit(options.table.additionalParams.beforeGoodsReceiptCheck, options.row.itemQuantityUnit, options.row.itemOrderQuantityUnit, value)
}
},
/* Ende hinzugefügter Code */
itemOrderQuantity: {
label: "de:Bestellmenge;en:Order Quantity",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Bestellmenge;en:Order Quantity",
/* Ende hinzugefügter Code */
type: "number",
(...)
attributes: {
disabled: "disabled",
/* hinzugefügter Code */
style: (value, options) => options.table.additionalParams.styleQuantity(options.table.additionalParams.beforeGoodsReceiptCheck, options.row.itemReceiptRequired, options.row.itemQuantity, value, options.row.itemQuantityDelivered)
/* Ende hinzugefügter Code */
}
}
/* geänderter Code */
itemOrderQuantityUnit: {
/* Ende geänderter Code */
label: "de:Bestell-ME;en:Order QU",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Bestellmengeneinheit;en:Order Quantity Unit",
/* Ende hinzugefügter Code */
selectOnFocus: true,
allowEdit: false,
allowInsert: false,
attributes: {
disabled: "disabled",
/* hinzugefügter Code */
style: (value, options) => options.table.additionalParams.styleQuantityUnit(options.table.additionalParams.beforeGoodsReceiptCheck, options.row.itemQuantityUnit, value, options.row.itemQuantityUnitDelivered)
/* Ende hinzugefügter Code */
}
},
itemSingleAmount: {
label: "de:Einzelpreis;en:Single amount",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Einzelpreis (mit Preiseinheit);en:Single amount (with price unit)",
/* Ende hinzugefügter Code */
type: "currency",
currency: currencySymbol,
selectOnFocus: true,
change: (value, options) => {
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
CalculationAmountMultiTable.calculateTotalAmountForRow(options.row, 0, 0);
},
attributes: {
style: (value, options) => {
/* geänderter Code */
const itemSingleAmount = +value;
const itemOrderSingleAmount = +options.row.itemOrderSingleAmount;
const isCellInvalid = itemSingleAmount !== itemOrderSingleAmount;
/* Ende geänderter Code */
return `color: ${isCellInvalid ? "#f44336" : "inherit"}`;
}
}
},
itemOrderSingleAmount: {
label: "de:Bestell-EP;en:Order-SA",
/* hinzugefügter Code */
tooltip: "de:Bestell-Einzelpreis;en:Order Single Amount",
/* Ende hinzugefügter Code */
type: "currency",
currency: currencySymbol,
/* gelöschter Code */
selectOnFocus: true,
(...)
},
/* geänderter Code */
itemPriceUnit: {
label: "de:PE;en:PU",
tooltip: "de:Preiseinheit;en:Price Unit",
type: "number",
allowEdit: true,
allowInsert: true
change: (_, options) => {
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
CalculationAmountMultiTable.calculateTotalAmountForRow(options.row, 0, 0);
}
},
/* Ende geänderter Code */
itemDiscountRate: {
label: "de:Rab.(%);en:Disc.(%)",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Rabatt(%);en:Discount(%)",
/* Ende hinzugefügter Code */
type: "number",
(...)
},
itemDiscountAmount: {
label: "de:Rab.;en:Disc.",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Rabatt;en:Discount",
/* Ende hinzugefügter Code */
type: "currency",
(...)
},
itemTotalNetAmount: {
label: "de:Nettobetrag;en:Net amount",
/* hinzugefügter Code */
tooltip: "de:Nettobetrag;en:Net amount",
/* Ende hinzugefügter Code */
type: "currency",
currency: currencySymbol,
/* gelöschter Code */
allowEdit: false,
(...)
attributes: {
disabled: "disabled"
},
/* hinzugefügter Code */
change: (value, options) => {
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
CalculationAmountMultiTable.calculateAmountForRow(options.row, false);
},
/* Ende hinzugefügter Code */
footerTemplate: options => {
/* geänderter Code */
const getItemTotalAmountFooterFunc = mustGetClientModule("getItemTotalAmountFooter");
return getItemTotalAmountFooterFunc(options, true);
/* Ende geänderter Code */
}
},
/* hinzugefügter Code */
itemVatRate: {
label: "de:Steuersatz;en:Vat rate",
tooltip: "de:Steuersatz;en:Vat rate",
type: "number",
decimalPrecision: 2,
selectOnFocus: true,
change: (value, options) => {
const getVatCodeByVatRateFunc = mustGetClientModule("getVatCodeByVatRate");
getVatCodeByVatRateFunc(value, options);
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
CalculationAmountMultiTable.calculateAmountForRow(options.row, !options.row.itemVatCode);
},
attributes: {
style: (_, options) => options.table.additionalParams.styleNetAmountPerVatValidation(options),
title: (_, options) => options.table.additionalParams.titleNetAmountPerVatValidation(options)
}
},
itemVatCode: {
label: "de:Steuercode;en:Vat code",
tooltip: "de:Steuercode;en:Vat code",
selectOnFocus: true,
change: "getVatCode"
},
itemTotalVatAmount: {
label: "de:Steuerbetrag;en:Vat amount",
tooltip: "de:Steuerbetrag;en:Vat amount",
type: "currency",
currency: currencySymbol,
allowEdit: false,
allowInsert: false,
attributes: {
disabled: "disabled"
},
change: (value, options) => {
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
CalculationAmountMultiTable.calculateAmountForRow(options.row, value === "");
}
},
itemTotalAmount: {
label: "de:Bruttobetrag;en:Total amount",
tooltip: "de:Bruttobetrag;en:Total amount",
type: "currency",
currency: currencySymbol,
allowEdit: false,
allowInsert: false,
attributes: {
disabled: "disabled"
},
footerTemplate: "getItemTotalAmountFooter"
},
itemCostCenter: {
label: "de:KST;en:CC",
tooltip: "de:Kostenstelle;en:Cost Center",
selectOnFocus: true,
attributes: {
title: (value, options) => {
return options.row.itemCostCenterDescription || "";
}
},
change: "getCostCenter"
},
// Optional aktivierbar
itemCostCenterDescription: {
visible: false,
label: "de:KST Beschreibung;en:CC Description",
tooltip: "de:Kostenstelle Beschreibung;en:Cost Center Description",
allowEdit: false,
allowInsert: false
},
itemCostUnit: {
label: "de:KTR;en:CU",
tooltip: "de:Kostenträger;en:Cost Unit",
selectOnFocus: true,
attributes: {
title: (value, options) => {
return options.row.itemCostUnitDescription || "";
}
},
change: "getCostUnit"
},
// Optional aktivierbar
itemCostUnitDescription: {
visible: false,
label: "de:KTR Beschreibung;en: CU Description",
tooltip: "de:Kostenträger Beschreibung;en: Cost Unit Description",
allowEdit: false,
allowInsert: false
},
itemImpersonalAccount: {
label: "de:KTO;en:GLA",
tooltip: "de:Sachkonto;en:Impersonal Account",
selectOnFocus: true,
attributes: {
title: (value, options) => {
return options.row.itemImpersonalAccountDescription || "";
}
},
change: "getImpersonalAccount"
},
// Optional aktivierbar
itemImpersonalAccountDescription: {
visible: false,
label: "de:KTO Beschreibung;en:GLA Description",
tooltip: "de:Sachkonto Beschreibung;en:Impersonal Account Description",
allowEdit: false,
allowInsert: false
},
/* Ende hinzugefügter Code */
itemBookingText: {
label: "de:Buchungstext;en:Booking text",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Buchungstext;en:Booking text",
/* Ende hinzugefügter Code */
selectOnFocus: true
},
itemComment: {
label: "de:Kommentar;en:comment",
/* gelöschter Code */
/* hinzugefügter Code */
tooltip: "de:Kommentar;en:comment",
/* Ende hinzugefügter Code */
selectOnFocus: true
}
(...)
}
(...)
buttons: {
/* Diesen Button entfernen */
appendAllOrderItems: {
label: "appendAllOrderItems",
icon: "mdi mdi-download-multiple",
onlyShowIcon: true,
tooltip: "de:Bestellzeilen laden;en:Load positions",
// visible: !!docFile.orderNumber,
click: "loadAllOrders"
},
/* Ende Button entfernen */
/* neuer Button für Synchronisation der Positionen */
syncPositions: {
visible: inState35,
label: "syncPositions",
icon: "mdi mdi-sync",
onlyShowIcon: true,
tooltip: "de:Bestellzeilen synchronisieren;en:Sync order items",
click: () => {
const fileId = documentsContext.getFileContext().getFileId();
documentsContext.startBusyPanel("MainFile");
try {
const errorMessage = documentsContext.executeScript("ou.sp.ptpINV.action.callback.syncRmbPositions", {
fileId
});
if (errorMessage) {
throw new Error(errorMessage);
}
} catch (error) {
documentsContext.openMessageDialog(documents.sdk.utils.getLocaleValue("de:Fehler;en:Error"), error.message || "Unknown error");
return;
} finally {
documentsContext.stopBusyPanel("MainFile");
}
documentsContext.updateFileView();
}
},
/ * Ende neuer Button für Synchronisation der Positionen */
/* neuer Button für Datenbankabgleich */
matchPositions: {
label: "de:Positionen abgleichen;en:Match positions",
icon: "mdi mdi-database-search",
onlyShowIcon: true,
tooltip: "de:Positionen mit Datenbank abgleichen;en:Match positions with database",
click: opts => {
const rows = opts.selectedRows.length > 0 ? opts.selectedRows : opts.rows;
const indices = Object.fromEntries(rows.map(row => [opts.getRowIndex(row), true]));
const fileContext = documentsContext.getFileContext();
const fileId = fileContext.getFileId();
documentsContext.callGadget({
gadgetScript: "Gadget_ou.sp.ptpINV.action.matchPositions",
gadgetAction: "build",
gadgetDestination: "dialog",
gadgetWidth: "1800",
gadgetHeight: "900",
gadgetParams: JSON.stringify({
fileId,
indices
})
});
}
},
/* Ende neuer Button für Datenbankabgleich */
/* Neuer Button für Filterung unvollständiger Positionen */
toggleShowUnmatchedPositions: {
label: "de:Unvollständige Positionen anzeigen;en:Show unmatched positions",
icon: "mdi mdi-filter",
onlyShowIcon: true,
tooltip: "de:Nur Positionen ohne vollständigen Datenbankabgleich anzeigen;en:Show only positions without complete database match",
click: ({
externalInterface
}) => {
const additionalParams = externalInterface.options.additionalParams;
externalInterface.updateOptions({
additionalParams: {
...additionalParams,
onlyShowUnmatchedPositions: !additionalParams.onlyShowUnmatchedPositions
}
});
}
},
/* Ende neuer Button für Filterung unvollständiger Positionen */
(...)
copyRow: {
label: "de:Zeile(n) kopieren;en:copy row(s)",
tooltip: "de:Ausgewählte Zeile(n) kopieren;en:Copy selected row(s)",
enabled: "multi",
click: options => {
/* geänderter Code */
const copyRows = mustGetClientModule("copyInvoicePositions");
copyRows(options, options.table.additionalParams.copyDialogTitle, options.table.additionalParams.copyDialogBody);
/* Ende geänderter Code */
}
},
(...)
compareAmount: {
/* geänderter Code */
label: "de:Nettobetrag + Steuer prüfen;en:Check net amount + tax",
/* Ende geänderter Code */
tooltip: "de:Nettobetrag aller Zeilen splitten;en:Check all rows after deduction",
click: options => {
(...)
}
},
(...)
},
(...)
}
(...)
ou.spc.ptpINV.settings.monitorROB (INV-675, INV-677, INV-683, INV-705, INV-565)
Hier wurde ebenfalls ein Check auf Status 15 eingeführt und entsprechend werden die Monitor-Einstellungen gesetzt. Dazu wird im Falle von Status 15 eine Nachricht hinterlegt, die im MultiTable erscheint, wenn dieser leer ist. Außerdem wurde die Logik überarbeitet, welche die Zeilen kopiert - der Standardfall ist nun hier ein Einfügen direkt nach der kopierten Zeile.
Außerdem wurden die deaktiverte Spalten itemTotalVatAmount und itemTotalAmount optischen hervorgehoben, der Style und ein Tooltip bei Fehler bei der Netto und Steuer Prüfung für die Spalte Steuersatz hinzugefügt, die Reihenfolge der Spalten wurde geändert (itemVatRate, itemVatCode, itemTotalVatAmount statt wie früher itemTotalVatAmount, itemVatRate, itemVatCode) und Tooltips für die Tabellen Header Spalten hinzugefügt, um die Tabellen Header Spalten abgekürzten Bezeichner auch ausführlich dazustellen.
Vorher:
const monitorOptions = {
showOptions: true,
allowInsert: false,
additionalParams: {
userConfigurationAttribute,
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)(),
currencySymbol
},
columns: {
(...)
itemDebitCreditIndicator: {
label: "de:S/H;en:D/C",
type: "select",
(...)
},
itemImpersonalAccount: {
label: "de:KTO;en:GLA",
width: "80px",
(...)
},
// Optional aktivierbar
itemImpersonalAccountDescription: {
visible: false,
label: "de:KTO Beschreibung;en:GLA Description",
allowEdit: false,
allowInsert: false,
width: "140px"
},
itemCostCenter: {
label: "de:KST;en:CC",
width: "80px",
(...)
},
// Optional aktivierbar
itemCostCenterDescription: {
visible: false,
allowEdit: false,
(...)
},
itemCostUnit: {
label: "de:KTR;en:CU",
width: "80px",
(...)
},
// Optional aktivierbar
itemCostUnitDescription: {
visible: false,
label: "de:KTR Beschreibung;en: CU Description",
allowEdit: false,
(...)
},
itemTotalNetAmount: {
label: "de:Nettobetrag;en:Net amount",
type: "currency",
(...)
change: (value, options) => {
(...)
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
CalculationAmountMultiTable.calculateAmountForRow(options.row, vatRate !== "");
}
},
itemTotalVatAmount: {
label: "de:Steuerbetrag;en:Vat amount",
type: "currency",
currency: currencySymbol,
selectOnFocus: true,
allowEdit: true,
allowInsert: false,
width: "120px",
change: (value, options) => {
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
CalculationAmountMultiTable.calculateAmountForRow(options.row, false);
}
},
itemVatRate: {
label: "de:Steuersatz;en:Vat rate",
width: "90px",
(...)
change: (value, options) => {
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
CalculationAmountMultiTable.calculateAmountForRow(options.row, value !== "");
const getVatCodeByVatRateFunc = mustGetClientModule("getVatCodeByVatRate");
getVatCodeByVatRateFunc(value, options);
}
},
itemVatCode: {
label: "de:Steuercode;en:Vat code",
width: "90px",
(...)
},
itemTotalAmount: {
label: "de:Bruttobetrag;en:Total amount",
type: "currency",
(...)
allowInsert: false,
footerTemplate: options => {
const roundingErrorMargin = options.table.additionalParams.roundingErrorMargin || 0;
const ValidationAmountMultiTable = mustGetClientModuleMember("invplus", "validationAmountMultiTable");
const validator = new ValidationAmountMultiTable(options.rows, roundingErrorMargin);
const result = validator.validate();
const fileContext = documentsContext.getFileContext();
const docType = fileContext.getFileFieldValue("docType");
const debitCreditIndicator = docType.substring(0, 1).toUpperCase() === "R" ? "S" : "H";
const BigConstructor = mustGetClientModule("Big");
const positionsAmount = options.rows.reduce(function (acc, row) {
let amount = +row.itemTotalAmount || 0;
if (row.itemDebitCreditIndicator && debitCreditIndicator !== row.itemDebitCreditIndicator) {
// If debitCreditIndicator doesn't match to itemDebitCreditIndicator, flip operator
amount *= -1;
}
return acc.plus(amount);
}, new BigConstructor(0)).round(2).toNumber();
const textColor = result.success ? "#1b5e20" : "#f44336";
const styles = [result.showWarning ? "display: flex;justify-content: space-between;align-items: center" : "text-align: right", "border-top: solid 2px #000", "border-bottom: double 3px #000", "margin: -4px", "padding-right: 4px", "padding-left: 4px", "font-weight: bold", `color: ${textColor}`];
const formatted = documents.sdk.utils.formatNumber(positionsAmount, null, null, 2);
return `<div style='${styles.join(";")}' title='${result.message}'>${result.showWarning ? "<span class='entypo warning' style='color: #333333'></span>" : ""}${formatted} ${options.table.additionalParams.currencySymbol}</div>`;
}
},
itemBookingText: {
label: "de:Buchungstext;en:Booking text",
width: "120px",
selectOnFocus: true
},
itemComment: {
label: "de:Kommentar;en:comment",
width: "120px",
selectOnFocus: true
}
}
(...)
buttons: {
(...)
copyRow: {
label: "de:Zeile(n) kopieren;en:copy row(s)",
tooltip: "de:Ausgewählte Zeile(n) kopieren;en:Copy selected row(s)",
enabled: "multi",
click: options => {
options.selectedRows.forEach(options.addRow);
options.commit();
}
},
(...)
compareAmount: {
label: "de:Nettobetrag prüfen;en:check after deduction",
tooltip: "de:Nettobetrag aller Zeilen splitten;en:Check all rows after deduction",
click: options => {
(...)
}
},
(...)
}
}
(...)
Nachher:
/* geänderter Code */
const copyDialogTitle = context.getFromSystemTable("PTPINV_MONTIOR_COPY_TITLE");
const copyDialogBody = context.getFromSystemTable("PTPINV_MONTIOR_COPY_BODY");
const inStatus15 = context.file.globalState === "15";
/* Ende geänderter Code */
const monitorOptions = {
/* geänderter Code */
showOptions: !inStatus15,
allowEdit: !inStatus15,
emptyMessage: inStatus15 ? context.getFromSystemTable("PTPINV_MONITOR_ROB_EMPTYMESSAGE") : undefined,
/* Ende geänderter Code */
allowInsert: false,
additionalParams: {
userConfigurationAttribute,
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)(),
currencySymbol,
/* geänderter Code */
copyDialogTitle,
copyDialogBody,
styleNetAmountPerVatValidation: options => {
const ValidationAmountMultiTable = mustGetClientModuleMember("invplus", "validationAmountMultiTable");
const roundingErrorMargin = options.table.additionalParams.roundingErrorMargin || 0;
const validator = new ValidationAmountMultiTable(options.rows, roundingErrorMargin);
const result = validator.validate();
const isCellInvalid = options.row.itemVatRate && result.netWithVat.missingAmounts?.[options.row.itemVatRate];
return `color: ${isCellInvalid ? "#f44336" : "inherit"}`;
},
titleNetAmountPerVatValidation: options => {
const ValidationAmountMultiTable = mustGetClientModuleMember("invplus", "validationAmountMultiTable");
const roundingErrorMargin = options.table.additionalParams.roundingErrorMargin || 0;
const validator = new ValidationAmountMultiTable(options.rows, roundingErrorMargin);
const result = validator.validate();
const isCellInvalid = options.row.itemVatRate && result.netWithVat.missingAmounts?.[options.row.itemVatRate];
return isCellInvalid ? result.netWithVat.errorList?.[options.row.itemVatRate] : "";
}
/* Ende geänderter Code */
},
columns: {
(...)
itemDebitCreditIndicator: {
label: "de:S/H;en:D/C",
/* hinzugefügter Code */
tooltip: "de:Soll/Haben Kennzeichen;en:Debit/Credit Indicator",
/* Ende hinzugefügter Code */
type: "select",
(...)
},
itemImpersonalAccount: {
label: "de:KTO;en:GLA",
/* hinzugefügter Code */
tooltip: "de:Sachkonto;en:Impersonal Account",
/* Ende hinzugefügter Code */
width: "80px",
(...)
},
// Optional aktivierbar
itemImpersonalAccountDescription: {
visible: false,
label: "de:KTO Beschreibung;en:GLA Description",
/* hinzugefügter Code */
tooltip: "de:Sachkonto Beschreibung;en:Impersonal Account Description",
/* Ende hinzugefügter Code */
allowEdit: false,
allowInsert: false,
width: "140px"
},
itemCostCenter: {
label: "de:KST;en:CC",
/* hinzugefügter Code */
tooltip: "de:Kostenstelle;en:Cost Center",
/* Ende hinzugefügter Code */
width: "80px",
(...)
},
// Optional aktivierbar
itemCostCenterDescription: {
visible: false,
label: "de:KST Beschreibung;en:CC Description",
/* hinzugefügter Code */
tooltip: "de:Kostenstelle Beschreibung;en:Cost Center Description",
/* Ende hinzugefügter Code */
allowEdit: false,
(...)
},
itemCostUnit: {
label: "de:KTR;en:CU",
/* hinzugefügter Code */
tooltip: "de:Kostenträger;en:Cost Unit",
/* Ende hinzugefügter Code */
width: "80px",
(...)
},
// Optional aktivierbar
itemCostUnitDescription: {
visible: false,
label: "de:KTR Beschreibung;en: CU Description",
/* hinzugefügter Code */
tooltip: "de:Kostenträger Beschreibung;en: Cost Unit Description",
/* Ende hinzugefügter Code */
allowEdit: false,
(...)
},
itemTotalNetAmount: {
label: "de:Nettobetrag;en:Net amount",
/* hinzugefügter Code */
tooltip: "de:Nettobetrag;en:Net amount",
/* Ende hinzugefügter Code */
type: "currency",
(...)
change: (value, options) => {
(...)
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
/* geänderter Code */
CalculationAmountMultiTable.calculateAmountForRow(options.row, vatRate !== "");
/* Ende geänderter Code */
},
/* hinzugefügter Code */
footerTemplate: options => {
const getItemTotalAmountFooterFunc = mustGetClientModule("getItemTotalAmountFooter");
return getItemTotalAmountFooterFunc(options, true);
}
/* Ende hinzugefügter Code */
},
itemVatRate: {
label: "de:Steuersatz;en:Vat rate",
/* hinzugefügter Code */
tooltip: "de:Steuersatz;en:Vat rate",
/* Ende hinzugefügter Code */
width: "90px",
(...)
change: (value, options) => {
/* geänderter Code */
const getVatCodeByVatRateFunc = mustGetClientModule("getVatCodeByVatRate");
getVatCodeByVatRateFunc(value, options);
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
CalculationAmountMultiTable.calculateAmountForRow(options.row, !options.row.itemVatCode);
/* Ende geänderter Code */
},
/* hinzugefügter Code */
attributes: {
style: (_, options) => options.table.additionalParams.styleNetAmountPerVatValidation(options),
title: (_, options) => options.table.additionalParams.titleNetAmountPerVatValidation(options)
}
/* Ende hinzugefügter Code */
},
itemVatCode: {
label: "de:Steuercode;en:Vat code",
/* hinzugefügter Code */
tooltip: "de:Steuercode;en:Vat code",
/* Ende hinzugefügter Code */
width: "90px",
(...)
},
itemTotalVatAmount: {
label: "de:Steuerbetrag;en:Vat amount",
/* hinzugefügter Code */
tooltip: "de:Steuerbetrag;en:Vat amount",
/* Ende hinzugefügter Code */
type: "currency",
currency: currencySymbol,
/* gelöschter Code */
/* geänderter Code */
allowEdit: false,
/* Ende geänderter Code */
allowInsert: false,
/* hinzugefügter Code */
attributes: {
disabled: "disabled"
},
/* Ende hinzugefügter Code */
width: "120px",
change: (value, options) => {
const CalculationAmountMultiTable = mustGetClientModuleMember("invplus", "calculationAmountMultiTable");
/* ge änderter Code */
CalculationAmountMultiTable.calculateAmountForRow(options.row, value === "");
/* Ende geänderter Code */
}
},
itemTotalAmount: {
label: "de:Bruttobetrag;en:Total amount",
/* hinzugefügter Code */
tooltip: "de:Bruttobetrag;en:Total amount",
/* Ende hinzugefügter Code */
type: "currency",
(...)
allowInsert: false,
/* hinzugefügter Code */
attributes: {
disabled: "disabled"
},
/* Ende hinzugefügter Code */
/* geänderter Code */
footerTemplate: "getItemTotalAmountFooter"
/* Ende geänderter Code */
},
itemBookingText: {
label: "de:Buchungstext;en:Booking text",
/* hinzugefügter Code */
tooltip: "de:Buchungstext;en:Booking text",
/* Ende hinzugefügter Code */
width: "120px",
selectOnFocus: true
},
itemComment: {
label: "de:Kommentar;en:comment",
/* hinzugefügter Code */
tooltip: "de:Kommentar;en:comment",
/* Ende hinzugefügter Code */
width: "120px",
selectOnFocus: true
}
}
(...)
buttons: {
(...)
copyRow: {
label: "de:Zeile(n) kopieren;en:copy row(s)",
tooltip: "de:Ausgewählte Zeile(n) kopieren;en:Copy selected row(s)",
enabled: "multi",
click: options => {
/* geänderter Code */
const copyRows = mustGetClientModule("copyInvoicePositions");
copyRows(options, options.table.additionalParams.copyDialogTitle, options.table.additionalParams.copyDialogBody);
/* Ende geänderter Code */
}
},
(...)
compareAmount: {
/* geänderter Code */
label: "de:Nettobetrag + Steuer prüfen;en:Check net amount + tax",
/* Ende geänderter Code */
tooltip: "de:Nettobetrag aller Zeilen splitten;en:Check all rows after deduction",
click: options => {
(...)
}
},
(...)
},
(...)
}
ou.spc.ptpINV.callback.dashboard.xtractWorkspace (INV-629)
Da wie oben bereits beschrieben die Unterordner des Ordners ptpInvoiceError umbenannt wurden, müssen hier die neuen Namen hinterlegt werden:
...
function entryInvoice() {
const errorFolders = ["ptpInvoiceError10error", "ptpInvoiceError55", "ptpInvoiceError90", "ptpInvoiceError91", "ptpInvoiceError95"];
...
}
...
ou.spc.ptpINV.clientHeaderCode.css (INV-683, INV-688, INV-743)
Es wurden Weitere Spallten zur optischen Hervorhebung von deaktivierten Feldern hinzugefügt (itemQuantityTotal, itemTotalAmount, itemOrderQuantityUnit, itemQuantityDelivered, itemQuantityUnitDelivered)
Vorher:
(...)
/* Optische Hervorhebung von deaktivierten Feldern */
td.cell-itemOrderQuantity,
td.cell-itemOrderSingleAmount,
td.cell-itemDiscountAmount,
td.cell-itemTotalNetAmount {
position: relative;
}
td.cell-itemOrderQuantity > span[disabled]:before,
td.cell-itemOrderSingleAmount > span[disabled]:before,
td.cell-itemDiscountAmount > span[disabled]:before,
td.cell-itemTotalNetAmount > span[disabled]:before {
content: '';
position: absolute;
background-color: ${disabledFieldBackgroundColor} !important;
opacity: ${disabledFieldOpacity};
top: 0;
left: 0;
bottom: 0;
right: 0;
}
(...)
Nachher:
...
/* Optische Hervorhebung von deaktivierten Feldern */
td.cell-itemQuantityTotal,
td.cell-itemQuantityDelivered,
td.cell-itemQuantityUnitDelivered,
td.cell-itemOrderQuantity,
td.cell-itemOrderQuantityUnit,
td.cell-itemOrderSingleAmount,
td.cell-itemDiscountAmount,
td.cell-itemTotalNetAmount,
td.cell-itemTotalVatAmount,
td.cell-itemTotalAmount {
position: relative;
}
td.cell-itemQuantityTotal > span[disabled]:before,
td.cell-itemQuantityDelivered > span[disabled]:before,
td.cell-itemQuantityUnitDelivered > span[disabled]:before,
td.cell-itemOrderQuantity > span[disabled]:before,
td.cell-itemOrderQuantityUnit > span[disabled]:before,
td.cell-itemOrderSingleAmount > span[disabled]:before,
td.cell-itemDiscountAmount > span[disabled]:before,
td.cell-itemTotalNetAmount > span[disabled]:before,
td.cell-itemTotalVatAmount > span[disabled]:before,
td.cell-itemTotalAmount > span[disabled]:before {
content: '';
position: absolute;
background-color: ${disabledFieldBackgroundColor} !important;
opacity: ${disabledFieldOpacity};
top: 0;
left: 0;
bottom: 0;
right: 0;
}
...
ou.spc.ptpINV.lib (INV-683, INV-705)
Es wurde auch hier die fehlenden Spalten, die im Zuge der RMB-Erweiterung (INV-683) hinzu kamen, hinzugefügt und teilweise die Reihenfolge geändert.
Vorher:
(...)
function fillPositionsByOrder(docFile) {
(...)
try {
(...)
const newRows = rows.map(row => {
return {
itemOrderNumber: row.orderNumber,
itemReceiptNumber: row.receiptNumber,
itemOrderItemNumber: row.itemNumber,
itemDeliveryNoteNumber: row.deliveryNoteNumber,
itemArticleNumber: row.articleNumber,
itemArticleName: row.articleName,
itemQuantityUnit: row.quantityUnit,
itemQuantity: +row.quantity || 0,
itemSingleAmount: +row.singleAmount || 0,
itemOrderQuantity: +row.quantity || 0,
itemOrderSingleAmount: +row.singleAmount || 0,
itemTotalNetAmount: new ou_sp_invplus_big_1.Big(row.singleAmount).times(row.quantity).round(2).toNumber()
};
});
(...)
} catch (error) {
(...)
} finally {
(...)
}
}
(...)
Nachher:
(...)
function fillPositionsByOrder(docFile) {
(...)
try {
(...)
const newRows = rows.map(row => {
return {
/* geänderter Code */
itemOrderNumber: row.orderNumber,
itemReceiptNumber: row.receiptNumber,
itemReceiptRequired: row.receiptRequired ?? false,
itemOrderItemNumber: row.itemNumber,
itemDeliveryNoteNumber: row.deliveryNoteNumber,
itemArticleNumber: row.articleNumber,
itemArticleName: row.articleName,
itemQuantity: +row.quantity || 0,
itemQuantityUnit: row.quantityUnit,
itemQuantityDelivered: +row.quantityDelivered || 0,
itemQuantityUnitDelivered: row.quantityUnitDelivered,
itemPriceUnit: +row.priceUnit || 0,
itemOrderQuantity: +row.quantity || 0,
itemOrderQuantityUnit: row.quantityUnit,
itemSingleAmount: +row.singleAmount || 0,
itemOrderSingleAmount: +row.singleAmount || 0,
itemVatRate: +row.vatRate || 0,
itemVatCode: row.vatCode,
itemTotalNetAmount: new ou_sp_invplus_big_1.Big(row.singleAmount).times(row.quantity).round(2).toNumber(),
itemCostUnit: row.costUnit,
itemCostCenter: row.costCenter,
itemImpersonalAccount: row.impersonalAccount
/* Ende geänderter Code */
};
});
(...)
} catch (error) {
(...)
} finally {
(...)
}
}
(...)
ou.spc.ptpINV.clientHeaderCode (INV-565)
Mit ou.sp.ptpINV.userexit.functions.multiTable kam ein neuer User Exit hinzu der sowohl für ROB als auch RMB gedacht ist. Dieser muss nun noch registriert werden.
Vorher:
(...)
const clientHeaderCode = {
beforeTransferCode: clientHeader => {
try {
const userExits = ["ou.sp.ptpINV.userexit.callbacks.calc", "ou.sp.ptpINV.userexit.callbacks.enum", "ou.sp.ptpINV.userexit.callbacks.invoice", "ou.sp.ptpINV.userexit.callbacks.mail", "ou.sp.ptpINV.userexit.functions.invoice", "ou.sp.ptpINV.userexit.functions.rmbMultiTable", "ou.sp.ptpINV.userexit.functions.robMultiTable", "ou.sp.ptpINV.userexit.invplus.calculationAmount", "ou.sp.ptpINV.userexit.invplus.calculationAmountMultiTable", "ou.sp.ptpINV.userexit.invplus.calculationCashDiscount", "ou.sp.ptpINV.userexit.invplus.configuration", "ou.sp.ptpINV.userexit.invplus.fieldMapping", "ou.sp.ptpINV.userexit.invplus.locale", "ou.sp.ptpINV.userexit.invplus.splitCalculationMultiTable", "ou.sp.ptpINV.userexit.invplus.utils", "ou.sp.ptpINV.userexit.invplus.validationAmountMultiTable"];
(...)
} catch (e) {
(...)
}
}
};
(...)
Nachher:
(...)
const clientHeaderCode = {
beforeTransferCode: clientHeader => {
try {
const userExits = ["ou.sp.ptpINV.userexit.callbacks.calc", "ou.sp.ptpINV.userexit.callbacks.enum", "ou.sp.ptpINV.userexit.callbacks.invoice", "ou.sp.ptpINV.userexit.callbacks.mail", "ou.sp.ptpINV.userexit.functions.invoice", "ou.sp.ptpINV.userexit.functions.multiTable", "ou.sp.ptpINV.userexit.functions.rmbMultiTable", "ou.sp.ptpINV.userexit.functions.robMultiTable", "ou.sp.ptpINV.userexit.invplus.calculationAmount", "ou.sp.ptpINV.userexit.invplus.calculationAmountMultiTable", "ou.sp.ptpINV.userexit.invplus.calculationCashDiscount", "ou.sp.ptpINV.userexit.invplus.configuration", "ou.sp.ptpINV.userexit.invplus.fieldMapping", "ou.sp.ptpINV.userexit.invplus.locale", "ou.sp.ptpINV.userexit.invplus.splitCalculationMultiTable", "ou.sp.ptpINV.userexit.invplus.utils", "ou.sp.ptpINV.userexit.invplus.validationAmountMultiTable"];
(...)
} catch (e) {
(...)
}
}
};
(...)
ou.spc.ptpINV.filetype.action.approvedButtons (INV-685, INV-663, INV-664, INV-667, INV-739)
Am Ende der for-Schleife muss hier eine weitere if-Bedingung hinzugefügt werden, damit der Button für die neue benutzerdefinierte Aktion zum Prüfen des Wareneingangs (nur RMB) nur im neuen Status 32 angezeigt wird.
...
function executeDirectly() {
...
for (let i = 0; i < enumval.length; i++) {
...
if (enumval[i] == "startReleaseWF") {
if (!hasAdmin) enumval[i] = "";
continue;
}
/* Neuer Code */
// Prüfung Wareneingang nur in Status 32
if (enumval[i] === "checkGoodsReceipt") {
if (globalState !== "32") enumval[i] = "";
continue;
}
/* Ende neuer Code */
}
}
Zwei weitere if-Bedingungen m üssen für die Beiden Buttons "updateVendor" und "updateOrderData" hinzugefügt werden:
...
function executeDirectly() {
...
for (let i = 0; i < enumval.length; i++) {
...
/* Neuer Code */
// Kreditor aktualisieren in Validierung, Indexierung (ROB) / Prüfen und Buchen (RMB) und in Fehlerstati
if (enumval[i] == "updateVendor") {
const state = parseInt(globalState, 10);
if (!hasAccounting || !(state > 10 && state < 21 || state === 35 || state === 91)) {
enumval[i] = "";
}
continue;
}
// Bestelldaten aktualisieren in Status “Prüfen und Buchen” und Datenfehler (nur RMB)
if (enumval[i] == "updateOrderData") {
const isRMB = docFile.docType.substring(1) === "MB";
if (!hasAccounting || !isRMB || !(globalState === "35" || globalState === "91")) {
enumval[i] = "";
}
continue;
}
/* Ende neuer Code */
}
}
Der Button Datenbank aktualisieren für die benutzerdefinierte Aktion actualDB muss der for-Schleife hinzugefügt werden, um nicht länger in Status 98, 99 und 100 angezeigt zu werden.
...
function executeDirectly() {
...
for (let i = 0; i < enumval.length; i++) {
...
/* Neuer Code */
// Datenbank aktualisieren nur in Status <98
if (enumval[i] === "actualDB") {
if (parseInt(globalState, 10) >= 98) enumval[i] = "";
continue;
}
/* Ende neuer Code */
}
}
Eine neue if-Bedingung muss für die Aktion "changeRecipient" hinzugefügt werden:
if (enumval[i] === "changeRecipient") {
if (!hasAccounting || globalState !== "15" && globalState !== "20" && globalState !== "35" && globalState !== "91" && globalState !== "95") {
enumval[i] = "";
}
continue;
}
ou.spc.ptpINV.filetype.property.dFROnFileViewScript (INV-685)
Fall 1: cust-Skript existiert bereits -> Untenstehender alter Code muss entfernt werden und if-Bedingung zur Kompatibilität mit altem Workflow sowie neuer Code eingefügt werden. Hintergrund ist, dass im neuen RMB-Workflow für den Status 35 nicht mehr die verificationGroup sondern die purchaseGroup zuständig ist.
Fall 2: cust-Skript existiert noch nicht -> cust-Skript muss angelegt werden und die if-Bedingung ins Skript eingefügt werden bis es keine Mappen mehr mit altem Workflow gibt.
...
function executeDirectly() {
...
if (docFile.docType.substring(docFile.docType.length - 2, docFile.docType.length).toUpperCase() != "OB") {
/* Alter Code -> Direkt entfernen */
arFieldsRW.push("verificationGroup");
arFieldWritableInStatus.push(["20", "35"]);
/* Ende alter Code */
/* If-Bedingung zur Kompatibilität -> Einfügen. Entfernen sobald keine Mappen mehr im alten Workflow */
if (docFile.goodsReceiptCheckStatus === "") {
arFieldsRW.push("verificationGroup");
arFieldWritableInStatus.push(["20", "35"]);
}
/* Ende */
/* Neuer Code -> Direkt einfügen */
arFieldsRW.push("purchaseGroup");
arFieldWritableInStatus.push(["20", "35"]);
/* Ende neuer Code */
}
...
}
ou.spc.ptpINV.workflow.action.incomingEvent (INV-685)
Fall 1: cust-Skript existiert bereits -> Analog zum vorherigen Skript muss auch hier für den Status 35 die verificationGroup (alter Code) durch die purchaseGroup (neuer Code) ersetzt werden. Um die Kompatibilität mit dem alten Workflow sicherzustellen muss eine zusätzliche if-Bedingung eingefügt werden, die entfernt werden kann sobald sich keine Mappen mehr im alten Workflow befinden.
Fall 2: cust-Skript existiert noch nicht -> cust-Skript muss angelegt werden und die zusätzliche if-Bedingung zur Kompatibilität mit dem alten Workflow muss eingefügt werden.
...
function executeDirectly() {
...
switch (docFile.globalState) {
...
case "35":
/* Alter Code -> Direkt entfernen */
Gruppe = docFile.verificationGroup;
/* Ende alter Code */
/* Neuer Code -> Direkt einfügen */
Gruppe = docFile.purchaseGroup;
/* Ende neuer Code */
/* Neuer Code zur Kompatibilität mit altem Workflow -> Einfügen. Entfernen sobald keine Mappen mehr im alten Workflow */
if (docFile.goodsReceiptCheckStatus === "") {
Gruppe = docFile.verificationGroup;
}
/* Ende neuer Code */
break;
case "50":
...
}
...
}
ou.spc.ptpINV.workflow.decision.RMB (INV-685)
Hier muss ein neuer Import sowie eine if-Bedingung hinzugefügt werden, sodass im Falle einer RMB die Funktion setGoodsReceiptCheckStatus aufgerufen wird, die an der Mappe den Status der Wareneingangsprüfung setzt.
Fall 1: cust-Skript existiert bereits -> Untenstehenden alten Code direkt entfernen und neuen Code sowie Code zur Kompatibilität mit altem Workflow einfügen.
Fall 2: cust-Skript existiert noch nicht -> cust-Skript muss angelegt werden und Code zur Kompatibilität mit altem Workflow eingefügt werden.
...
/* Neuer Code -> Direkt einfügen */
const ou_sp_ptpINV_lib_goodsReceiptCheck_1 = require("ou.sp.ptpINV.lib.goodsReceiptCheck");
/* Ende neuer Code */
function executeDirectly() {
const docFile = context.file;
/* Alter Code -> Direkt entfernen */
return docFile.docType.substring(1, 3) != "OB";
/* Ende alter Code */
/* Neuer Code zur Kompatibilität mit altem Workflow -> Einfügen. Entfernen sobald keine Mappen mehr im alten Workflow */
if (docFile.docType.substring(1, 3) !== "OB" && docFile.goodsReceiptCheckStatus === "") {
return true;
}
/* Ende neuer Code */
/* Neuer Code -> Direkt einfügen */
if (docFile.docType.substring(1, 3) !== "OB") {
(0, ou_sp_ptpINV_lib_goodsReceiptCheck_1.setGoodsReceiptCheckStatus)(docFile);
return true;
}
return false;
/* Ende neuer Code */
}
ou.spc.ptpINV.workflow.decision.autoIndex (INV-685)
Fall 1: cust-Skript existiert bereits -> Sobald sich keine Mappen mehr im alten Workflow befinden, müssen untenstehender Import und if-Bedingung aus diesem Skript entfernt werden.
Fall 2: cust-Skript existiert noch nicht -> cust-Skript muss angelegt werden und untenstehender alter Code (Import und if-Bedingung) muss vorübergehend wieder eingefügt werden.
...
/* Alter Code -> Entfernen sobald keine Mappen mehr im alten Workflow */
const ou_sp_ptpINV_lib_access_1 = require("ou.sp.ptpINV.lib.access");
/* Ende alter Code */
...
function executeDirectly() {
const docFile = context.file;
try {
logger.info(`Starte AutoIndex für ${docFile.getid()}`);
/* Alter Code -> Entfernen sobald keine Mappen mehr im alten Workflow */
// AutoIndex gibt es nur bei ROB
if (docFile.docType.substring(1) === "MB") {
logger.debug(`AutoIndex übersprungen da ${docFile.docType}`);
// Per default set group ptpPurchaseGroup
docFile.setFieldValue("verificationGroup", "ptpPurchaseGroup");
(0, ou_sp_ptpINV_lib_access_1.setAccess)(docFile, "access", docFile.verificationGroup);
docFile.sync();
return 1;
}
/* Ende alter Code */
const skipPositions = false;
...
} ...
}
ou.spc.ptpINV.lib.index.validation (INV-685)
Fall 1: cust-Skript existiert bereits -> Untenstehenden alten Code direkt entfernen und den Code zur Kompatibilität mit dem alten Workflow einfügen.
Fall 2: cust-Skript existiert noch nicht -> cust-Skript muss angelegt werden und der Code zur Kompatibilität mit dem alten Workflow eingefügt werden.
...
function validateRMB(docFile) {
logger.debug("Starte Validierung RMB");
/* Alter Code -> Direkt entfernen */
if (!docFile.verificationGroup) {
throw new Error("Bitte wählen Sie unter 'Fachliche Prüfungsgruppe' eine Gruppe zum Prüfen der Bestellung aus!");
}
/* Ende alter Code */
/* Neuer Code zur Kompatibilität mit altem Workflow -> Einfügen. Entfernen sobald keine Mappen mehr im alten Workflow */
if (!docFile.verificationGroup && docFile.goodsReceiptCheckStatus === "") {
throw new Error("Bitte wählen Sie unter 'Fachliche Prüfungsgruppe' eine Gruppe zum Prüfen der Bestellung aus!");
}
/* Ende neuer Code */
let errors = [...rmbValidation.validateFields(docFile)];
...
}
...
ou.spc.ptpINV.settings.mappingConfig (INV-693, INV-705, INV-749)
Das Mapping invRMBPosMapping.dexpro hat sich verändert und schaut jetzt wie folgt aus:
dexpro: {
PosOrderNumber: {
field: "itemOrderNumber"
},
PosOrderItem: {
field: "itemOrderItemNumber"
},
PosDeliveryNumber: {
field: "itemDeliveryNoteNumber"
},
PosArticleNumber: {
field: "itemArticleNumber"
},
PosDescription: {
field: "itemArticleName"
},
PosQuantity: {
field: "itemQuantity",
format: stringToNumber
},
PosQuantityOrder: {
field: "itemOrderQuantity",
format: stringToNumber
},
PosQuantityUnit: [{
field: "itemQuantityUnit"
}, {
field: "itemOrderQuantityUnit"
}],
PosSinglePrice: {
field: "itemSingleAmount",
format: stringToNumber
},
PosPriceUnit: {
field: "itemPriceUnit",
format: stringToNumber
},
PosSinglePriceOrder: {
field: "itemOrderSingleAmount",
format: stringToNumber
},
PosTaxRate: {
field: "itemVatRate",
format: stringToNumber
},
PosDiscount1: {
field: "itemDiscountAmount",
format: stringToNumber
},
PosTotalAmount: {
field: "itemTotalNetAmount",
format: stringToNumber
}
}
ou.tmpl.ptpINV.callbacks.dataExtraction (INV-693)
Hier wurde die Callback-Funktion validatedDataExtractionAfter angepasst.
Falls ein zugehöriges Cust-Skript existiert, muss dieses ebenfalls angepasst
werden:
Das Argument der Funktion ist nicht mehr vom Typ
data: { docFile: ptpInvoice; isDataValid: boolean }
sondern
data: {
docFile: ptpInvoice;
validationResult: ValidateDocFileMandatoryXtractFieldsResult;
}
ou.cust.ptpINV.callbacks.actions (INV-685)
Es gibt neue Callbacks für die neuen Aktionen des generischen ERP-Interfaces, die nun hier eingetragen werden müssen:
const updateVendorBefore = data => {
logger.debug("trigger updateVendor before");
// Return 0 or -1 to skip the action and return that value
return undefined;
};
/**
* Callback nach dem die benutzerdefinierte Aktion updateVendor ausgeführt wurde
* @param {{docFile: ptpInvoice, vendorId: string, response: UpdateMasterDataResponse }} data Objekt mit docFile, vendorId und response
* @returns {number | undefined} 0 oder -1 um den Rückgabewert der Aktion zu überschreiben
*/
const updateVendorAfter = data => {
logger.debug("trigger updateVendor after");
// Return 0 or -1 to override the return value of the action
return undefined;
};
/**
* Callback bevor die benutzerdefinierte Aktion updateOrder ausgeführt wird
* @param {{docFile: ptpInvoice, orderNumber: string }} data Objekt mit docFile und orderNumber
* @returns {number | undefined} 0 oder -1 um die Aktion zu überspringen und diesen Wert zurückzugeben
*/
const updateOrderBefore = data => {
logger.debug("trigger updateOrder before");
// Return 0 or -1 to skip the action and return that value
return undefined;
};
/**
* Callback nach dem die benutzerdefinierte Aktion updateOrder ausgeführt wurde
* @param {{docFile: ptpInvoice, orderNumber: string, response: UpdateOrderDataResponse }} data Objekt mit docFile, orderNumber und response
* @returns {number | undefined} 0 oder -1 um den Rückgabewert der Aktion zu überschreiben
*/
const updateOrderAfter = data => {
logger.debug("trigger updateOrder after");
// Return 0 or -1 to override the return value of the action
return undefined;
};
Zudem müssen die Callbacks am Ende des Skripts registriert werden:
ou_sp_ptpINV_lib_callbacks_1.ptpINVCallbacks.registerCallback("before", "updateVendor", updateVendorBefore);
ou_sp_ptpINV_lib_callbacks_1.ptpINVCallbacks.registerCallback("after", "updateVendor", updateVendorAfter);
ou_sp_ptpINV_lib_callbacks_1.ptpINVCallbacks.registerCallback("before", "updateOrder", updateOrderBefore);
ou_sp_ptpINV_lib_callbacks_1.ptpINVCallbacks.registerCallback("after", "updateOrder", updateOrderAfter);
ou.spc.ptpINV.callback.functions.RMB.lookupItemOrderNumber (INV-689, INV-705, INV-716)
Änderung: Die Filterlogik zum Ausschließen bereits vorhandener Bestellzeilen wurde erweitert.
Beschreibung: Die bisherige Filterung basierte nur auf der Bestellposition und funktionierte daher nur bei RMBs mit genau einer Bestellnummer. Die Logik wurde so angepasst, dass Bestellzeilen nun kombiniert nach Bestellnummer und Bestellposition ausgeschlossen werden. Hierzu werden die übergebenen Bestellzeilen nach Bestellnummer gruppiert und pro Bestellung gezielt die bereits vorhandenen Positionen ausgeschlossen. Zusätzlich wird sichergestellt, dass Positionen aus noch nicht hinzugefügten Bestellungen weiterhin angezeigt werden.
Vorher:
(...)
function executeDirectly() {
const args = ou_sp_gadget_TableDialogReact_1.TableDialogReact.getParams();
const recipient = args.recipient.replace(/\*/, "%") || "%";
const vendorId = args.vendorId.replace(/\*/, "%") || "%";
const {
ptpConnections
} = (0, ou_spc_ptpINV_settings_1.getSettings)();
const db = ptpConnections.getDatabaseConnection("ptpData");
try {
const whereConditions = [`recipient = '${recipient}'`, `vendorId = '${vendorId}'`];
if (args.searchValue) {
const searchField = args.searchField.replace(/\*/, "%") || "%";
const searchValue = args.searchValue.replace(/\*/, "%") || "%";
const orders = searchValue.split(/,\s*/g).map(order => `${searchField} like '%${order.trim()}%'`);
whereConditions.push(`(${orders.join(" OR ")})`);
if (args.orderItemNumbers && args.orderItemNumbers.length > 0) {
const validItemNumbers = args.orderItemNumbers.filter(arg => arg);
if (validItemNumbers.length > 0) {
whereConditions.push(`itemNumber NOT IN (${validItemNumbers.join(", ")})`);
}
}
return (0, ou_sp_ptpINV_lib_rmb_orderItems_1.count)(db, whereConditions.join(" AND "));
}
return (0, ou_sp_ptpINV_lib_rmb_orderItems_1.count)(db, whereConditions.join(" AND "), "viewActiveOrderItemsNotAssigned");
} catch (error) {
context.errorMessage = error.message;
return -1;
} finally {
if (db) {
db.close();
}
}
}
(...)
Nachher:
(...)
/* geänderter Code */
function executeDirectly() {
const {
ptpConnections
} = (0, ou_spc_ptpINV_settings_1.getSettings)();
const db = ptpConnections.getDatabaseConnection("ptpData");
try {
const {
recipient,
searchValue,
searchField,
orderItems,
vendorId,
search
} = ou_sp_gadget_TableDialogReact_1.TableDialogReact.getParams();
const searchConditions = searchField && searchValue ? [{
searchValue,
searchField
}, ...(search || [])] : search;
const whereConditions = (0, ou_sp_ptpINV_lib_rmb_orderItems_1.getOrderItemsWhereConditions)({
recipient,
search: searchConditions,
orderItems,
vendorId
});
if (searchConditions.some(condition => !!condition.searchValue)) {
return (0, ou_sp_ptpINV_lib_rmb_orderItems_1.count)(db, whereConditions);
}
return (0, ou_sp_ptpINV_lib_rmb_orderItems_1.count)(db, whereConditions, "viewActiveOrderItemsNotAssigned");
} catch (error) {
context.errorMessage = error.message;
return -1;
} finally {
if (db) {
db.close();
}
}
}
/* Ende geänderter Code */
(...)
ou.spc.ptpINV.callback.functions.RMB.getItemOrderNumber (INV-683, INV-689, INV-705, INV-716)
Es wurde auch hier die fehlenden Spalten, die im Zuge der RMB-Erweiterung (INV-683) hinzu kamen, hinzugefügt und teilweise die Reihenfolge geändert.
Außerdem wurden hier die Spaltenbeschriftungen auch auf Englisch ergänzt und die Spalte Menge (quantity) wurde der Type number hinzugefügt.
Zusätlich wurde für INV-689 noch folgendes angepasst:
Änderung: Anpassung der Ausschlusslogik für bereits ausgewählte Bestellzeilen analog zur Lookup-Funktion.
Beschreibung: Auch in dieser Callback-Funktion wurde die Filterung von einer reinen Bestellpositions-Prüfung auf eine kombinierte Prüfung aus Bestellnummer und Bestellposition umgestellt. Damit wird gewährleistet, dass bei RMBs mit mehreren Bestellnummern ausschließlich die tatsächlich bereits hinzugefügten Bestellzeilen ausgeschlossen werden und keine gültigen Positionen fälschlicherweise fehlen.
Vorher:
(...)
function executeDirectly() {
const {
ptpConnections
} = (0, ou_spc_ptpINV_settings_1.getSettings)();
const db = ptpConnections.getDatabaseConnection("ptpData");
try {
const args = ou_sp_gadget_TableDialogReact_1.TableDialogReact.getParams();
const recipient = args.recipient.replace(/\*/, "%") || "%";
const vendorId = args.vendorId.replace(/\*/, "%") || "%";
let rows = [];
if (args.searchValue) {
const searchField = args.searchField.replace(/\*/, "%") || "%";
const searchValue = args.searchValue.replace(/\*/, "%") || "%";
const whereConditions = [`recipient = '${recipient}'`, `vendorId = '${vendorId}'`];
const orders = searchValue.split(/,\s*/g).map(order => `${searchField} like '%${order.trim()}%'`);
whereConditions.push(`(${orders.join(" OR ")})`);
if (args.orderItemNumbers && args.orderItemNumbers.length > 0) {
const validItemNumbers = args.orderItemNumbers.filter(arg => arg);
if (validItemNumbers.length > 0) {
whereConditions.push(`itemNumber NOT IN (${validItemNumbers.join(", ")})`);
}
}
rows = (0, ou_sp_ptpINV_lib_rmb_orderItems_1.select)(db, whereConditions.join(" AND "));
} else {
rows = (0, ou_sp_ptpINV_lib_rmb_orderItems_1.findByOrderNumbersNotAssigned)(db, vendorId, recipient);
}
return new ou_sp_gadget_TableDialogReact_1.TableDialogReact({
rows,
columns: {
orderNumber: {
label: "Bestell-Nr.",
field: "itemOrderNumber"
},
receiptNumber: {
label: "Wareneingang-Nr.",
field: "itemReceiptNumber"
},
itemNumber: {
label: "Bestellpos-Nr.",
field: "itemOrderItemNumber"
},
deliveryNoteNumber: {
label: "Lieferschein-Nr.",
field: "itemDeliveryNoteNumber"
},
articleNumber: {
label: "Artikel-Nr.",
field: "itemArticleNumber"
},
articleName: {
label: "Artikel",
field: "itemArticleName"
},
quantity: {
label: "Menge",
field: "itemQuantity"
},
quantityUnit: {
label: "ME",
field: "itemQuantityUnit"
},
singleAmount: {
label: "Einzelpreis",
type: "currency",
field: "itemSingleAmount"
}
}
}).transfer();
} catch (error) {
(...)
} finally {
(...)
}
}
(...)
Nachher:
(...)
function executeDirectly() {
(...)
function executeDirectly() {
const {
ptpConnections
} = (0, ou_spc_ptpINV_settings_1.getSettings)();
const db = ptpConnections.getDatabaseConnection("ptpData");
try {
const {
recipient,
searchValue,
searchField,
orderItems,
vendorId,
search
} = ou_sp_gadget_TableDialogReact_1.TableDialogReact.getParams();
const searchConditions = searchField && searchValue ? [{
searchValue,
searchField
}, ...(search || [])] : search;
const whereConditions = (0, ou_sp_ptpINV_lib_rmb_orderItems_1.getOrderItemsWhereConditions)({
recipient,
search: searchConditions,
orderItems,
vendorId
});
let rows = [];
if (search.some(condition => !!condition.searchValue)) {
rows = (0, ou_sp_ptpINV_lib_rmb_orderItems_1.select)(db, whereConditions);
} else {
rows = (0, ou_sp_ptpINV_lib_rmb_orderItems_1.select)(db, whereConditions, "viewActiveOrderItemsNotAssigned");
}
return new ou_sp_gadget_TableDialogReact_1.TableDialogReact({
rows,
columns: {
orderNumber: {
/* geänderter Code */
label: "de:Bestell-Nr;en:Order no",
/* Ende geänderter Code */
/* hinzugefügter Code */
tooltip: "de:Bestellnummer;en:Order number",
/* Ende hinzugefügter Code */
field: "itemOrderNumber"
},
receiptNumber: {
/* geänderter Code */
label: "de:Wareneingang-Nr;en:Receipt no",
/* Ende geänderter Code */
/* hinzugefügter Code */
tooltip: "de:Wareneingangsnummer;en:Receipt number",
/* Ende hinzugefügter Code */
field: "itemReceiptNumber"
},
/* hinzugefügter Code */
receiptRequired: {
label: "de:WE?;en:GR?",
tooltip: "de:Wareneingang erforderlich;en:Receipt required",
field: "itemReceiptRequired",
type: "checkbox"
},
/* Ende hinzugefügter Code */
itemNumber: {
/* geänderter Code */
label: "de:Bestellpos.;en:Orderpos no",
/* Ende geänderter Code */
/* hinzugefügter Code */
tooltip: "de:Bestellpositionsnummer;en:Order position number",
/* Ende hinzugefügter Code */
field: "itemOrderItemNumber"
},
deliveryNoteNumber: {
/* geänderter Code */
label: "de:Lieferschein-Nr;en:Delivery no",
/* Ende geänderter Code */
/* hinzugefügter Code */
tooltip: "de:Lieferscheinnummer;en:Delivery number",
/* Ende hinzugefügter Code */
field: "itemDeliveryNoteNumber"
},
articleNumber: {
/* geänderter Code */
label: "de:Artikel-Nr;en:Article no",
/* Ende geänderter Code */
/* hinzugefügter Code */
tooltip: "de:Artikelnummer;en:Article number",
/* Ende hinzugefügter Code */
field: "itemArticleNumber"
},
articleName: {
/* geänderter Code */
label: "de:Artikel-Name;en:Article name",
/* Ende geänderter Code */
/* hinzugefügter Code */
tooltip: "de:Artikel-Name;en:Article name",
/* Ende hinzugefügter Code */
field: "itemArticleName"
},
quantity: {
/* geänderter Code */
label: "de:Menge;en:Quantity",
/* Ende geänderter Code */
/* hinzugefügter Code */
tooltip: "de:Menge;en:Quantity",
/* Ende hinzugefügter Code */
/* geänderter Code */
field: "itemQuantity",
/* Ende geänderter Code */
/* hinzugefügter Code */
type: "number"
/* Ende hinzugefügter Code */
},
quantityUnit: {
/* geänderter Code */
label: "de:ME;en:QU",
/* Ende geänderter Code */
/* hinzugefügter Code */
tooltip: "de:Mengeneinheit;en:Quantity Unit",
/* Ende hinzugefügter Code */
field: "itemQuantityUnit"
},
/* hinzugefügter Code */
quantityDelivered: {
label: "de:WE-Menge;en:Del. Quantity",
tooltip: "de:Wareneingangsmenge;en:Delivered Quantity",
field: "itemQuantityDelivered",
type: "number"
},
quantityUnitDelivered: {
label: "de:WE-ME;en:Del. QU",
tooltip: "de:Wareneingangsmengeneinheit;en:Delivered Quantity Unit",
field: "itemQuantityUnitDelivered"
},
// Optional aktivierbar
priceUnit: {
visible: false,
label: "de:PE;en:PU",
tooltip: "de:Preiseinheit;en:Price Unit",
field: "itemPriceUnit",
type: "number"
},
/* Ende hinzugefügter Code */
singleAmount: {
/* geänderter Code */
label: "de:Einzelpreis;en:Single amount",
/* Ende geänderter Code */
/* hinzugefügter Code */
tooltip: "de:Einzelpreis;en:Single amount",
/* Ende hinzugefügter Code */
type: "currency",
field: "itemSingleAmount"
},
/* hinzugefügter Code */
vatRate: {
label: "de:Steuersatz;en:Vat rate",
tooltip: "de:Steuersatz;en:Vat rate",
field: "itemVatRate",
type: "number",
decimalPrecision: 2
},
vatCode: {
label: "de:Steuercode;en:Vat code",
tooltip: "de:Steuercode;en:Vat code",
field: "itemVatCode"
},
costCenter: {
label: "de:KST;en:CC",
tooltip: "de:Kostenstelle;en:Cost Center",
field: "itemCostCenter"
},
costUnit: {
label: "de:KTR;en:CU",
tooltip: "de:Kostenträger;en:Cost Unit",
field: "itemCostUnit"
},
impersonalAccount: {
label: "de:KTO;en:GLA",
tooltip: "de:Sachkonto;en:Impersonal Account",
field: "itemImpersonalAccount"
}
/* Ende hinzugefügter Code */
}
}).transfer();
} catch (error) {
(...)
} finally {
(...)
}
}
(...)
ou.spc.ptpINV.job.syncRunningSapTasks (LIB-519)
Der provider wird hier nun zusammen mit der Db aus den DB-Credentials abgefragt,
Vorher:
function executeDirectly() {
const logger = ou_sp_Logging_1.Logging.use("ou.spc.ptpINV.job.syncRunningSapTasks");
const db = (0, ou_spc_ptpINV_settings_1.getSettings)().ptpConnections.getDatabaseConnection("ousp");
const options = {
db
// For error reporting uncomment the following lines and fill in the values
// errorReportConfig: {
// customerEmail: "abc@xyz.com",
// customerKey: "CXXXX",
// systemKey: "OUK-XXXX",
// productKey: "OUK-XXXX",
// priority: "medium",
// },
};
(...)
}
Nachher:
function executeDirectly() {
const logger = ou_sp_Logging_1.Logging.use("ou.spc.ptpINV.job.syncRunningSapTasks");
const {
db,
provider
} = (0, ou_spc_ptpINV_settings_1.getSettings)().ptpConnections.getDatabaseConnectionAndProvider("ousp");
const options = {
db,
provider
// For error reporting uncomment the following lines and fill in the values
// errorReportConfig: {
// customerEmail: "abc@xyz.com",
// customerKey: "CXXXX",
// systemKey: "OUK-XXXX",
// productKey: "OUK-XXXX",
// priority: "medium",
// },
};
(...)
}
ou.spc.ptpINV.job.uploadMasterData.dexpro (LIB-519)
Der provider wird hier nun zusammen mit der Db aus den DB-Credentials abgefragt,
Vorher:
Nachher:
function runJob() {
logger.info("Starting job to upload master data to Dexpro");
const settings = (0, ou_spc_ptpINV_settings_1.getSettings)();
/* Anfang geänderter Code */
const taskAuditDb = settings.ptpConnections.getDatabaseConnectionAndProvider("ousp").db;
const masterDataDb = settings.ptpConnections.getDatabaseConnectionAndProvider("ptpData").db;
const {
provider
} = settings.ptpConnections.getDatabaseConnectionAndProvider("ptpData");
/* Ende geänderter Code */
try {
(0, ou_sp_ptpINV_lib_1.mustValidateDbConnection)(taskAuditDb);
(0, ou_sp_ptpINV_lib_1.mustValidateDbConnection)(masterDataDb);
const tasks = [{
clear: true,
sourceTableName: "viewDexproVendor"
}, {
clear: true,
sourceTableName: "viewDexproRecipient"
}, {
clear: true,
sourceTableName: "viewDexproCurrencySymbol"
}, {
clear: true,
sourceTableName: "viewDexproVatRates"
}, {
clear: true,
sourceTableName: "viewDexproOrderItem"
}, {
clear: true,
sourceTableName: "viewDexproVendor",
selectStatement: "SELECT CreditorId as result, Name1 as regex FROM viewDexproVendor",
targetTableName: "creditorsearch"
}, {
clear: true,
sourceTableName: "viewDexproRecipient",
selectStatement: "SELECT Code as result, Name as regex FROM viewDexproRecipient",
targetTableName: "companysearch"
}];
(0, ou_sp_ptpINV_lib_masterData_dexpro_1.uploadDexproMasterData)({
tasks,
taskAuditDb,
masterDataDb,
/* Anfang neuer Code */
provider
/* Ende neuer Code */
});
} catch (error) {
logger.error(`Error during job execution: ${error.message ? error.message : error}`);
} finally {
if (taskAuditDb) taskAuditDb.close();
if (masterDataDb) masterDataDb.close();
}
logger.info("Job to upload master data to Dexpro completed");
}
ou.spc.ptpINV.callback.functions.cover.history (INV-678)
Die Abfrage wurde optimiert, indem das Limit (maxCount) direkt in die SQL-Query integriert wurde, anstatt alle Ergebnisse abzurufen und anschließend zu beschneiden. Dadurch werden nur die tatsächlich benötigten Datensätze aus der Datenbank geladen.
Vorher:
(...)
const ou_sp_SelectBuilder_1 = require("ou.sp.SelectBuilder");
const ou_spc_ptpINV_settings_1 = require("ou.spc.ptpINV.settings");
const logger = ou_sp_Logging_1.Logging.use("ou.spc.ptpINV.callback.functions.cover.history");
function executeDirectly() {
const maxCount = 5;
const {
ptpConnections
} = (0, ou_spc_ptpINV_settings_1.getSettings)();
const db = ptpConnections.getDatabaseConnection("ptpData");
try {
const docFile = context.file;
const query = ou_sp_SelectBuilder_1.SelectBuilder.from("viewInvoiceHeaderLatest", db).select("invoiceDate", "date").select("invoiceNumber", "string").select("totalNetAmount", "float").select("erpInvoiceNumber", "string").select("bookingDate", "date").where(`recipient = '${docFile.recipient}'`).where(`AND vendorId = '${docFile.vendorId}'`).where(`AND fileId <> '${docFile.fileId}'`).orderBy("invoiceDate", false);
logger.debug(query.toSQL());
const rows = query.execute();
const trimmedRows = rows.splice(0, maxCount).map(row => {
return {
...row,
bookingDate: row.bookingDate ? context.convertDateToString(row.bookingDate) : "",
invoiceDate: row.invoiceDate ? context.convertDateToString(row.invoiceDate) : ""
};
});
return JSON.stringify(trimmedRows);
} catch (error) {
(...)
} finally {
(...)
}
}
(...)
Nachher:
(...)
const ou_sp_SelectBuilder_1 = require("ou.sp.SelectBuilder");
/* geänderter Code */
const ou_spc_library_dbConnections_1 = require("ou.spc.library.dbConnections");
/* Ende geänderter Code */
const logger = ou_sp_Logging_1.Logging.use("ou.spc.ptpINV.callback.functions.cover.history");
function executeDirectly() {
const maxCount = 5;
/* geänderter Code */
let db = null;
/* Ende geänderter Code */
try {
const docFile = context.file;
/* geänderter Code */
const dbWithProvider = (0, ou_spc_library_dbConnections_1.getDatabaseConnectionAndProviderOrThrow)("ptpData");
db = dbWithProvider.db;
const query = ou_sp_SelectBuilder_1.SelectBuilder.from("viewInvoiceHeaderLatest", dbWithProvider).select("invoiceDate", "date").select("invoiceNumber", "string").select("totalNetAmount", "float").select("erpInvoiceNumber", "string").select("bookingDate", "date").where(`recipient = '${docFile.recipient}'`).where(`AND vendorId = '${docFile.vendorId}'`).where(`AND fileId <> '${docFile.fileId}'`).orderBy("invoiceDate", false).limit(maxCount);
/* Ende geänderter Code */
logger.debug(query.toSQL());
const rows = query.execute();
/* geänderter Code */
const formattedRows = rows.map(row => {
/* Ende geänderter Code */
return {
...row,
bookingDate: row.bookingDate ? context.convertDateToString(row.bookingDate) : "",
invoiceDate: row.invoiceDate ? context.convertDateToString(row.invoiceDate) : ""
};
});
/* geänderter Code */
return JSON.stringify(formattedRows);
/* Ende geänderter Code */
} catch (error) {
(...)
} finally {
(...)
}
}
(...)
ou.spc.ptpINV.callback.functions.cover.historyPositions (INV-678)
Die Abfrage wurde optimiert, indem das Limit (maxCount) direkt in die SQL-Query integriert wurde, anstatt alle Ergebnisse abzurufen und anschließend zu beschneiden. Dadurch werden nur die tatsächlich benötigten Datensätze aus der Datenbank geladen.
Vorher:
(...)
const ou_sp_SelectBuilder_1 = require("ou.sp.SelectBuilder");
const ou_spc_ptpINV_settings_1 = require("ou.spc.ptpINV.settings");
const logger = ou_sp_Logging_1.Logging.use("ou.spc.ptpINV.callback.functions.cover.historyPositions");
function executeDirectly() {
const maxCount = 5;
const {
ptpConnections
} = (0, ou_spc_ptpINV_settings_1.getSettings)();
const db = ptpConnections.getDatabaseConnection("ptpData");
try {
logger.debug(`Lookup for vendor ${vendorId} with by recipient ${recipient} using ${method}`);
const query = getQuery(db);
logger.debug(query.toSQL());
const rows = query.execute();
return JSON.stringify(rows.splice(0, maxCount));
} catch (error) {
(...)
} finally {
(...)
}
}
function getQuery(db) {
if (invoiceNumber) {
return ou_sp_SelectBuilder_1.SelectBuilder.from("viewInvoiceItemLatestByInvoiceNumber", db).select("itemDebitCreditIndicator", "string").select("itemImpersonalAccount", "string").select("itemImpersonalAccountDescription", "string").select("itemCostCenter", "string").select("itemCostCenterDescription", "string").select("itemCostUnit", "string").select("itemCostUnitDescription", "string").select("itemTotalNetAmount", "float").select("itemVatRate", "string").select("itemVatCode", "string").select("invoiceDate", "date").where(`recipient = '${recipient}'`).andWhere(`vendorId = '${vendorId}'`).andWhere(`invoiceNumber = '${invoiceNumber}'`).orderBy("invoiceDate", false);
}
if (method === "history") {
return ou_sp_SelectBuilder_1.SelectBuilder.from("viewInvoiceItemLatest", db).select("itemDebitCreditIndicator", "string").select("itemImpersonalAccount", "string").select("itemImpersonalAccountDescription", "string").select("itemCostCenter", "string").select("itemCostCenterDescription", "string").select("itemCostUnit", "string").select("itemCostUnitDescription", "string").select("invoiceDate", "date").select("Anzahl", "number").where(`recipient = '${recipient}'`).andWhere(`vendorId = '${vendorId}'`).orderBy("invoiceDate", false).orderBy("Anzahl", false);
}
return ou_sp_SelectBuilder_1.SelectBuilder.from("viewInvoiceItemFrequency", db).select("itemDebitCreditIndicator", "string").select("itemImpersonalAccount", "string").select("itemImpersonalAccountDescription", "string").select("itemCostCenter", "string").select("itemCostCenterDescription", "string").select("itemCostUnit", "string").select("itemCostUnitDescription", "string").select("Prozent", "float").select("Anzahl", "number").where(`recipient = '${recipient}'`).andWhere(`vendorId = '${vendorId}'`).orderBy("Prozent", false).orderBy("Anzahl", false);
}
(...)
Nachher:
(...)
const ou_sp_SelectBuilder_1 = require("ou.sp.SelectBuilder");
const ou_spc_library_dbConnections_1 = require("ou.spc.library.dbConnections");
const logger = ou_sp_Logging_1.Logging.use("ou.spc.ptpINV.callback.functions.cover.historyPositions");
function executeDirectly() {
const maxCount = 5;
let db = null;
try {
logger.debug(`Lookup for vendor ${vendorId} with by recipient ${recipient} using ${method}`);
/* geänderter Code */
const dbWithProvider = (0, ou_spc_library_dbConnections_1.getDatabaseConnectionAndProviderOrThrow)("ptpData");
db = dbWithProvider.db;
const query = getQuery(dbWithProvider, maxCount);
/* Ende geänderter Code */
logger.debug(query.toSQL());
const rows = query.execute();
/* geänderter Code */
return JSON.stringify(rows);
/* Ende geänderter Code */
} catch (error) {
(...)
} finally {
(...)
}
}
/* geänderter Code */
function getQuery(dbWithProvider, maxCount) {
/* Ende geänderter Code */
if (invoiceNumber) {
/* geänderter Code */
return ou_sp_SelectBuilder_1.SelectBuilder.from("viewInvoiceItemLatestByInvoiceNumber", dbWithProvider).select("itemDebitCreditIndicator", "string").select("itemImpersonalAccount", "string").select("itemImpersonalAccountDescription", "string").select("itemCostCenter", "string").select("itemCostCenterDescription", "string").select("itemCostUnit", "string").select("itemCostUnitDescription", "string").select("itemTotalNetAmount", "float").select("itemVatRate", "string").select("itemVatCode", "string").select("invoiceDate", "date").where(`recipient = '${recipient}'`).andWhere(`vendorId = '${vendorId}'`).andWhere(`invoiceNumber = '${invoiceNumber}'`).orderBy("invoiceDate", false).limit(maxCount);
/* Ende geänderter Code */
}
if (method === "history") {
/* geänderter Code */
return ou_sp_SelectBuilder_1.SelectBuilder.from("viewInvoiceItemLatest", dbWithProvider).select("itemDebitCreditIndicator", "string").select("itemImpersonalAccount", "string").select("itemImpersonalAccountDescription", "string").select("itemCostCenter", "string").select("itemCostCenterDescription", "string").select("itemCostUnit", "string").select("itemCostUnitDescription", "string").select("invoiceDate", "date").select("Anzahl", "number").where(`recipient = '${recipient}'`).andWhere(`vendorId = '${vendorId}'`).orderBy("invoiceDate", false).orderBy("Anzahl", false).limit(maxCount);
/* Ende geänderter Code */
}
/* geänderter Code */
return ou_sp_SelectBuilder_1.SelectBuilder.from("viewInvoiceItemFrequency", dbWithProvider).select("itemDebitCreditIndicator", "string").select("itemImpersonalAccount", "string").select("itemImpersonalAccountDescription", "string").select("itemCostCenter", "string").select("itemCostCenterDescription", "string").select("itemCostUnit", "string").select("itemCostUnitDescription", "string").select("Prozent", "float").select("Anzahl", "number").where(`recipient = '${recipient}'`).andWhere(`vendorId = '${vendorId}'`).orderBy("Prozent", false).orderBy("Anzahl", false).limit(maxCount);
/* Ende geänderter Code */
}
(...)
ou.spc.ptpINV.callback.functions.cover.historyVerification (INV-678)
Die Abfrage wurde optimiert, indem das Limit (maxCount) direkt in die SQL-Query integriert wurde, anstatt alle Ergebnisse abzurufen und anschließend zu beschneiden. Dadurch werden nur die tatsächlich benötigten Datensätze aus der Datenbank geladen.
Vorher:
(...)
const ou_sp_SelectBuilder_1 = require("ou.sp.SelectBuilder");
const ou_spc_ptpINV_settings_1 = require("ou.spc.ptpINV.settings");
const ou_spc_ptpINV_lib_proposal_1 = require("ou.spc.ptpINV.lib.proposal");
const logger = ou_sp_Logging_1.Logging.use("ou.spc.ptpINV.callback.functions.cover.historyVerification");
function executeDirectly() {
const maxCount = 5;
const {
ptpConnections
} = (0, ou_spc_ptpINV_settings_1.getSettings)();
const db = ptpConnections.getDatabaseConnection("ptpData");
try {
const query = getQuery(db);
logger.debug(query.toSQL());
const rows = query.execute().splice(0, maxCount).map(row => {
(...)
});
return JSON.stringify(rows);
} catch (error) {
(...)
} finally {
(...)
}
}
function getQuery(db) {
const verificationUserPriority = (0, ou_spc_ptpINV_lib_proposal_1.getVerificationUserPriorityFor)(recipient, vendorId);
if (method === "history") {
return ou_sp_SelectBuilder_1.SelectBuilder.from(verificationUserPriority === "assigned" ? "viewVerificationUserLatestAssigned" : "viewVerificationUserLatestExecuted", db).select("editor", "string").select("invoiceDate", "date").select("Anzahl", "number").where(`recipient = '${recipient}'`).andWhere(`vendorId = '${vendorId}'`).orderBy("invoiceDate", false).orderBy("Anzahl", false);
}
return ou_sp_SelectBuilder_1.SelectBuilder.from(verificationUserPriority === "assigned" ? "viewVerificationUserFrequencyAssigned" : "viewVerificationUserFrequencyExecuted", db).select("editor", "string").select("Prozent", "float").select("Anzahl", "number").where(`recipient = '${recipient}'`).andWhere(`vendorId = '${vendorId}'`).orderBy("Prozent", false).orderBy("Anzahl", false);
}
(...)
Nachher:
(...)
const ou_sp_SelectBuilder_1 = require("ou.sp.SelectBuilder");
const ou_spc_library_dbConnections_1 = require("ou.spc.library.dbConnections");
const ou_spc_ptpINV_lib_proposal_1 = require("ou.spc.ptpINV.lib.proposal");
const logger = ou_sp_Logging_1.Logging.use("ou.spc.ptpINV.callback.functions.cover.historyVerification");
function executeDirectly() {
const maxCount = 5;
let db = null;
try {
/* geänderter Code */
const dbWithProvider = (0, ou_spc_library_dbConnections_1.getDatabaseConnectionAndProviderOrThrow)("ptpData");
db = dbWithProvider.db;
const query = getQuery(dbWithProvider, maxCount);
/* Ende geänderter Code */
logger.debug(query.toSQL());
/* geänderter Code */
const rows = query.execute();
const enrichedRows = rows.map(row => {
/* Ende geänderter Code */
(...)
});
/* geänderter Code */
return JSON.stringify(enrichedRows);
/* Ende geänderter Code */
} catch (error) {
(...)
} finally {
(...)
}
}
/* geänderter Code */
function getQuery(dbWithProvider, maxCount) {
/* Ende geänderter Code */
const verificationUserPriority = (0, ou_spc_ptpINV_lib_proposal_1.getVerificationUserPriorityFor)(recipient, vendorId);
if (method === "history") {
/* geänderter Code */
return ou_sp_SelectBuilder_1.SelectBuilder.from(verificationUserPriority === "assigned" ? "viewVerificationUserLatestAssigned" : "viewVerificationUserLatestExecuted", dbWithProvider).select("editor", "string").select("invoiceDate", "date").select("Anzahl", "number").where(`recipient = '${recipient}'`).andWhere(`vendorId = '${vendorId}'`).orderBy("invoiceDate", false).orderBy("Anzahl", false).limit(maxCount);
/* Ende geänderter Code */
}
/* geänderter Code */
return ou_sp_SelectBuilder_1.SelectBuilder.from(verificationUserPriority === "assigned" ? "viewVerificationUserFrequencyAssigned" : "viewVerificationUserFrequencyExecuted", dbWithProvider).select("editor", "string").select("Prozent", "float").select("Anzahl", "number").where(`recipient = '${recipient}'`).andWhere(`vendorId = '${vendorId}'`).orderBy("Prozent", false).orderBy("Anzahl", false).limit(maxCount);
/* Ende geänderter Code */
}
(...)
ou.spc.ptpINV.filetype.property.dFROnFileViewScript (INV-708)
Status 15 muss dem Array hinzugefügt werden, sodass die Feldberechtigungen korrekt gesetzt werden.
...
function executeDirectly() {
// Berechtigungen nur bei folgenden Status prüfen und setzten
const arStatus = [
"0",
"10",
/* Neuer Code */
"15",
/* Ende neuer Code */
"20",
"90",
"91",
"95",
"30",
"35",
"40",
"50",
"80",
"90",
"91",
"95",
"91",
"95",
"98",
"100",
];
}
Status 15 muss zudem zu den verschiedenen Feldern als Berechtigung hinzugefügt werden. Außerdem wurden Feldberechtigungen für das Feld currency hinzugefügt.
Vorher:
(...)
// ROB: Felder schreibend
if (docFile.docType.substring(docFile.docType.length - 2, docFile.docType.length).toUpperCase() != "OB") {
arFieldsRW.push("purchaseGroup");
arFieldWritableInStatus.push(["20", "35"]);
}
arFieldsRW.push("reasonReject");
arFieldWritableInStatus.push(["0", "30", "40"]);
arFieldsRW.push("netAmount1");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("netAmount2");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("netAmount3");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatRate1");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatRate2");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatRate3");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatAmount1");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatAmount2");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatAmount3");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("additionalExpenses1");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("additionalExpenses2");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("additionalExpenses3");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("totalVatAmount");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("totalAdditionalExpenses");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("discount");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("totalNetAmount");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("totalAmount");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("valutaDate");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("paymentTerm");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("paymentTermName");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("paymentTermSource");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountDays1");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountDays2");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountRate1");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountRate2");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountAmount1");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountAmount2");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountDate1");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountDate2");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("netDays");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("netDate");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorSelection");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorId");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorName");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorCountryCode");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorZipCode");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorCity");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorStreet");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorBICNumber");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorIBANNumber");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorBankNumber");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorAccountNumber");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorVatNumber");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorTaxNumber");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("invoiceDate");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("invoiceNumber");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("orderNumber");
arFieldWritableInStatus.push(["0", "20", "35", "50", "90", "91", "95"]);
arFieldsRW.push("deliveryNoteNumber");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
arFieldsRW.push("supplyDate");
arFieldWritableInStatus.push(["0", "20", "50", "90", "91", "95"]);
(...)
Nachher:
(...)
// ROB: Felder schreibend
if (
docFile.docType
.substring(docFile.docType.length - 2, docFile.docType.length)
.toUpperCase() != "OB"
) {
arFieldsRW.push("purchaseGroup");
arFieldWritableInStatus.push(["20", "35"]);
}
arFieldsRW.push("reasonReject");
arFieldWritableInStatus.push(["0", "30", "40"]);
/** Beginn Änderung */
arFieldsRW.push("netAmount1");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("netAmount2");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("netAmount3");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatRate1");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatRate2");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatRate3");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatAmount1");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatAmount2");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vatAmount3");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("additionalExpenses1");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("additionalExpenses2");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("additionalExpenses3");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("totalVatAmount");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("totalAdditionalExpenses");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("discount");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("totalNetAmount");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("totalAmount");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
/** Beginn neuer Code */
arFieldsRW.push("currency");
arFieldWritableInStatus.push(["15", "20", "35", "91"]);
/** Ende neuer Code */
arFieldsRW.push("valutaDate");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("paymentTerm");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("paymentTermName");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("paymentTermSource");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountDays1");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountDays2");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountRate1");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountRate2");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountAmount1");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountAmount2");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountDate1");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("cashDiscountDate2");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("netDays");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("netDate");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorSelection");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorId");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorName");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorCountryCode");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorZipCode");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorCity");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorStreet");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorBICNumber");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorIBANNumber");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorBankNumber");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorAccountNumber");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorVatNumber");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("vendorTaxNumber");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("invoiceDate");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("invoiceNumber");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("orderNumber");
arFieldWritableInStatus.push(["0", "15", "20", "35", "50", "90", "91", "95"]);
arFieldsRW.push("deliveryNoteNumber");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
arFieldsRW.push("supplyDate");
arFieldWritableInStatus.push(["0", "15", "20", "50", "90", "91", "95"]);
/** Ende Änderung */
(...)
Workflow
Ab sofort dürfen Workflow-Anpassungen nicht mehr an bestehenden Workflow-Versionen vorgenommen werden. Stattdessen muss zwingend eine neue Workflow-Version erstellt werden (siehe Beschreibung in Ablauf von Upgrades)
Im Workflow gibt es nun zwei neue Entscheidungen zwischen Status 20 und Status 30 nach dem Skript ou.spc.ptpINV.workflow.sendsignal.createReleaseList. Zum Einbinden sind folgende Schritte nötig:
-
Nach dem Skript
ou.spc.ptpINV.workflow.sendsignal.createReleaseListmuss eine neue Entscheidung gesetzt werden. -
Von der Entscheidung geht ein Kontrollfluss zur Aktion fachliche Prüfung im Status 30. Am Kontrollfluss muss das Skript
ou.spc.ptpINV.workflow.decision.checkAfterCreateReleaseListhinterlegt werden. -
Der Else-Kontrollfluss geht zur zweiten neuen Entscheidung.
-
Von der zweiten Entscheidung aus geht ein Kontrollfluss zurück zur Indexierung in Status 20. Am Kontrollfluss muss das Skript
ou.spc.ptpINV.workflow.decision.checkAfterCreateReleaseListErrorhinterlegt werden. -
Der Else-Kontrollfluss der zweiten Entscheidung geht zur Aktion Freigabeliste Prüfen, die in den Fehlerstatus 95 geht. Diese Aktion muss angelegt werden wie folgt:

-
Von Status 95 geht ein Guard Kontrollfluss zurück zum Skript Freigabeliste erstellen
ou.spc.ptpINV.workflow.sendsignal.createReleaseList. Am Guard Kontrollfluss muss das Skriptou.spc.ptpINV.workflow.guard.backToCreateReleaseListhinterlegt sein.
Der Workflow sollte nun wie folgt aussehen:

Signaleingang "Speichern des fachlichen Prüfers" (INV-640)
Der Signaleingang "Speichern des fachlichen Prüfers" muss durch einen Signalausgang ersetzt werden:
- Bezeichnung:
Speichern des fachlichen Prüfers - Typ:
Javascript - Skriptname:
ou.sp.ptpINV.workflow.sendsignal.logVerificationUser
Erweiterung RMB-Workflow (INV-685)
Überblick neuer Workflow



Schritt 1 (RMB-Workflow)
Zunächst muss am existierenden Kontrollfluss Keine Error Codes folgender Eintrag im Tab Feldbelegung hinzugefügt werden:
goodsReceiptCheckStatus->none

Als Nächstes muss eine neue Entscheidung zwischen dem Kontrollfluss Keine Error Codes und der Auto-Indexierungs-Entscheidung eingebaut werden.
Von dieser Entscheidung geht ein Kontrollfluss RMB zum neuen Signaleingang ou.sp.ptpINV.workflow.receivesignal.checkGoodsReceiptRMB
Bezeichnung->RMBBedingung->runscript:ou.spc.ptpINV.workflow.decision.RMBpreIndexKommentar pf->workflow.ptpInvoice.comment.RMB

Der zugehörige ELSE-Kontrollfluss ROB führt zur bereits bestehenden Auto-Indexierungs-Entscheidung
Bezeichnung->ROBBedingung->ELSEKommentar pf->workflow.ptpInvoice.comment.ROB


Schritt 2 (RMB-Workflow)
Der neue Signaleingang ou.sp.ptpINV.workflow.receivesignal.checkGoodsReceiptRMB sieht wie folgt aus:
Bezeichner pf->workflow.ptpInvoice.receivesignal.checkGoodsReceiptRMBBedingung->runscript:ou.sp.ptpINV.workflow.receivesignal.checkGoodsReceiptRMBKommentar pf->workflow.ptpInvoice.comment.checkGoodsReceiptRMB


Vom Signaleingang geht ein Kontrollfluss Warten beendet zu einer weiteren neuen Entscheidung

Schritt 3 (RMB-Workflow)
Von der neuen Entscheidung gehen zwei Kontrollflüsse ab. Der erste Kontrollfluss WE gefunden sieht wie folgt aus:
Bezeichnung->WE gefundenBedingung->runscript:ou.sp.ptpINV.workflow.decision.goodsReceiptKommentar pf->workflow.ptpInvoice.comment.goodsReceived

Der zugehörige ELSE-Kontrollfluss Kein WE bzw. Abweichung WE führt zu einer neuen Aktion Prüfen Einkauf
Bezeichnung->Kein WE bzw. Abweichung WEBedingung->ELSEKommentar pf->workflow.ptpInvoice.comment.goodsNotReceived

Schritt 4 (RMB-Workflow)
Die neue Aktion Prüfen Einkauf sieht wie folgt aus:


Schritt 5 (RMB-Workflow)
Von der Aktion Prüfen Einkauf geht ein Kontrollfluss ou.spc.ptpINV.workflow.guard.backToGoodsReceiptCheck zurück zum oben erstellten Signaleingang ou.sp.ptpINV.workflow.receivesignal.checkGoodsReceiptRMB. Der Kontrollfluss ou.spc.ptpINV.workflow.guard.backToGoodsReceiptCheck führt hierbei die Wareneingangs-Prüfungslogik erneut aus.
Bezeichnung pf->workflow.ptpInvoice.action.backToGoodsReceiptCheckBedingung->runscript:ou.spc.ptpINV.workflow.guard.backToGoodsReceiptCheckKommentar pf->workflow.ptpInvoice.comment.backToGoodsReceiptCheck

Schritt 6 (RMB-Workflow)
Von der Aktion Prüfen Einkauf geht außerdem ein Kontrollfluss ou.spc.ptpINV.workflow.guard.changeToROB zurück zur Auto-Indexierungs-Entscheidung.
Bezeichnung pf->workflow.ptpInvoice.action.changeToROBBedingung->runscript:ou.spc.ptpINV.workflow.guard.changeToROBKommentar pf->workflow.ptpInvoice.comment.changeToROB


Schritt 7 (RMB-Workflow)
Der Kontrollfluss ou.spc.ptpINV.workflow.guard.rejected (ebenfalls ausgehend von der Aktion Prüfen Einkauf) und sein zugehöriger Endpunkt sehen wie folgt aus:
Bezeichnung pf->workflow.ptpInvoice.action.rejectedBedingung->runscript:ou.spc.ptpINV.workflow.guard.rejectedKommentar pf->workflow.ptpInvoice.comment.purchaseVerificationRejected



Schritt 8 (RMB-Workflow)
Als Nächstes muss noch der Kontrolfluss ou.spc.ptpINV.workflow.guard.verifiedRMB angelegt werden, der ebenfalls von der Aktion Prüfen Einkauf ausgeht und zusammen mit dem oben bereits erstellten Kontrollfluss WE gefunden in einer Zusammenführung endet.
Bezeichnung pf->workflow.ptpInvoice.action.purchaseVerificationVerifiedBedingung->runscript:ou.spc.ptpINV.workflow.guard.verifiedRMBKommentar pf->workflow.ptpInvoice.comment.purchaseVerificationVerified



Schritt 9 (RMB-Workflow)
Von der Zusammenführung aus muss ein Kontrollfluss angelegt werden, der in die bereits bestehende Entscheidung Zur Buchung vs. Dunkelbuchung mündet.


Schritt 10 (RMB-Workflow)
Am bereits bestehenden Kontrollfluss ou.spc.ptpINV.workflow.guard.backToVerification, der von der Aktion Prüfen und Buchen (Status 50) ausgeht, muss unter Zugriff -> Sichtbarkeit die Bedingung runscript:ou.spc.ptpINV.workflow.actionVisibility.backToVerification hinterlegt werden.

Außerdem muss von derselben Aktion ausgehend ein neuer Kontrollfluss ou.spc.ptpINV.workflow.guard.backToPurchaseVerification angelegt werden, der wieder in die oben erstellte Aktion Prüfen Einkauf mündet:
Bezeichnung pf->workflow.ptpInvoice.action.backToPurchaseVerificationBedingung->runscript:ou.spc.ptpINV.workflow.guard.backToPurchaseVerificationKommentar pf->workflow.ptpInvoice.comment.backToPurchaseVerificationZugriff->Bedingung->runscript:ou.spc.ptpINV.workflow.actionVisibility.backToPurchaseVerification




Schritt 11 (RMB-Workflow)
Als Letztes muss noch die Aktion Prüfen und Buchen (Status 35) sowie alle davon ausgehenden Kontrollflüsse (ou.spc.ptpINV.workflow.guard.backToIndex, ou.spc.ptpINV.workflow.guard.rejected, ou.spc.ptpINV.workflow.guard.export) entfernt werden. Der eingehende Kontrollfluss RMB wird stattdessen auf den neu erstellten Signaleingang ou.sp.ptpINV.workflow.receivesignal.checkGoodsReceiptRMB geleitet.