Zum Hauptinhalt springen

Upgrade auf 25.0.0

gefahr

Unbedingt die aktuellen Installationsvoraussetzungen vorab prüfen!

tipp

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

  • Workflow-ext wurde aufgelöst: clientHeaderCode in Portalskripte implementiert (INV-563)
  • Das Invoice-Cover wurde in React implementiert und alle MultiTable-Gadgets durch ihr React-Äquivalent ersetzt (INV-536)
  • Das neue Xtractor-Interface wurde für Insiders implementiert (INV-624)
  • Dexpro wurde in den Invoice-Workflow integriert (INV-633)
  • Es wurde ein Job für den Dexpro-Stammdatenupload implementiert und der Insiders-Stammdatenupload geändert/überarbeitet (INV-638)
  • An den Rechnungskreis-Mappen müssen nun die zulässigen Währungen sowie die lokale Währung des Rechnungskreises konfiguriert werden. Darüber hinaus wird in den Positionszeilen der ptpInvoice Mappen nun automatisch das korrekte Währungssymbol angezeigt (INV-595)

Was wurde gemacht?

Zusammenfassung

  • Das Gadget-Skript Gadget_ou.spc.filetype.folder.html.Counter, welches an den Ordnern ptpInvoice und ptpInvoiceError verwendet wurde, wurde durch das neue Skript Gadget_ou.spc.ptpINV.folder.html.Counter ersetzt (POM-442)
  • Am MultiTable für die Positionen gibt es nun einen Button zum Hinzufügen einer Zeile für die Rundungsdifferenz (INV-607)
  • Durch Codeverbesserungen wurden alte Verhaltensweisen, wie Fehler geworfen wurden, auf den aktuellen Stand geändert (INV-602, INV-603)
  • Wenn der fachliche Prüfer gleichzeitig der Freigeber ist und den Betrag freigeben darf, wird der Freigabepfad nun korrekt geupdated (INV-609)
  • Am Button "zurück zur fachl. Prüfung" wurde das Skript-Parameter verificationGroup entfernt (INV-647)
  • Wird an einer Kostenstellen-Mappe eine Änderung via dem Button 'Kostenstelle hinzufügen' durchgeführt, so wird nun die Trefferliste des Ordners 'Kostenstellenzuordnungen' aktualisiert. Außerdem wurden die Fehlermeldungen des Dialogs optimiert (INV-652)
  • An Status 50 wurde ein zusätzlicher Workflow-Button eingefügt, der die Buchung auf den Vormonat im Standard ermöglicht (INV-617)

Manuell auszuführende Schritte

Auflösung von Workflow-Ext (INV-563)

Cover-Templates migrieren

Existiert ein Skript ou.cust.ptpINV.filetype.field.html.cover, so muss diesem Skript folgende Funktion hinzugefügt werden:

function getHtmlTemplate(name) {
var htmlProperties = context.getCustomProperties(name);
if (htmlProperties.size() <= 0) {
throw new Error(context.getFromSystemTable("PTPINV_HTML_ERROR_TEMPLATE_NOT_FOUND"));
}
var htmlTemplateProperty = htmlProperties.first();
if (!htmlTemplateProperty.value) {
throw new Error(context.getFromSystemTable("PTPINV_HTML_ERROR_TEMPLATE_EMPTY"));
}
return htmlTemplateProperty.value;
}

Anschließend muss überprüft werden, ob im selben Skript Custom-Templates definiert sind, diese sind wenn dann ab dieser Stelle zu finden:

module.exports = function (docFile) {
return {
// templateUrl: "ext/html/ou/cust/ptpINV/cover.html",
// templates: {
// global: {
// start: "ext/html/ou/cust/ptpINV/cover/global-start.html",
// end: "ext/html/ou/cust/ptpINV/cover/global-end.html",
// },
// vendor: {
// start: "ext/html/ou/cust/ptpINV/cover/vendor-start.html",
// end: "ext/html/ou/cust/ptpINV/cover/vendor-end.html",
// },
// cockpit: {
// start: "ext/html/ou/cust/ptpINV/cover/cockpit-start.html",
// end: "ext/html/ou/cust/ptpINV/cover/cockpit-end.html",

Wird hier ein Custom-Template definiert, so muss dieses Template als Globale Eigenschaft angelegt und anschließend mit der gerade hinzugefügten Funktion getHtmlTemplate geladen werden, z.B. so:

module.exports = function (docFile) {
return {
templates: {
global: {
start: getHtmlTemplate("ou.cust.ptpINV.filetype.field.html.cover.globalStart"),
end: getHtmlTemplate("ou.cust.ptpINV.filetype.field.html.cover.globalEnd"),
},
},
/* ... */
};
};
Skripte löschen

SP-Skripte

Folgende Skripte können gelöscht werden, da diese in Portalskripte verschoben wurden:

  • Workflow-ext/js/ou/sp/ptp/enum-callbacks-file.js
  • Workflow-ext/js/ou/sp/ptp/mail-callbacks-file.js
  • Workflow-ext/js/ou/sp/invplus/big.min.js
  • Workflow-ext/js/ou/sp/invplus/invplus.calculationAmount.js
  • Workflow-ext/js/ou/sp/invplus/invplus.calculationAmountMultiTable.js
  • Workflow-ext/js/ou/sp/invplus/invplus.calculationCashDiscount.js
  • Workflow-ext/js/ou/sp/invplus/invplus.configuration.js
  • Workflow-ext/js/ou/sp/invplus/invplus.fieldMapping.js
  • Workflow-ext/js/ou/sp/invplus/invplus.locale.js
  • Workflow-ext/js/ou/sp/invplus/invplus.splitCalculationMultiTable.js
  • Workflow-ext/js/ou/sp/invplus/invplus.utils.js
  • Workflow-ext/js/ou/sp/invplus/invplus.validationAmountMultiTable.js
  • Workflow-ext/css/ou/sp/invoice.css

Anschließend muss die Datei Workflow-ext/jsp/script-ousp-invoice.jsp entfernt werden.

cust-Skripte

Folgende Skripte wurden aus Workflow-ext/js/ou/cust/ptp/invoice/ in ein Portalskript verschoben und sind nun jeweils unter neuem Namen verfügbar:

  • Workflow-ext/js/ou/cust/ptp/invoice/calc-callbacks-file.js --> ou.sp.ptpINV.userexit.callbacks.calc
  • Workflow-ext/js/ou/cust/ptp/invoice/invoice-callbacks-file-functions.js --> ou.sp.ptpINV.userexit.functions.invoice
  • Workflow-ext/js/ou/cust/ptp/invoice/invoice-callbacks-file.js --> ou.sp.ptpINV.userexit.callbacks.invoice
  • Workflow-ext/js/ou/cust/ptp/invoice/rmb-callbacks-multiTable.js --> ou.sp.ptpINV.userexit.functions.rmbMultiTable
  • Workflow-ext/js/ou/cust/ptp/invoice/rob-callbacks-multiTable.js --> ou.sp.ptpINV.userexit.functions.robMultiTable

Existieren in einem der hier aufgeführten Skripte kundenspezifische Anpassungen, so müssen diese auch in ein Portalskript umgezogen werden – dies ist folgendermaßen zu tun:

Schritt 1: ClientHeaderCode-Skript kopieren

Falls es zum Skript ou.spc.ptpINV.clientHeaderCode noch kein gleichnamiges cust-Skript gibt, muss das spc-Skript kopiert und in ou.cust.ptpINV.clientHeaderCode umbenannt werden.

Schritt 2: Customizing migrieren

Nun gibt es zwei Möglichkeiten, wie sich das Customizing in einem der Skripte darstellt und entsprechend migriert werden muss:

Möglichkeit 1: Das Customizing bezieht sich direkt auf Code (z.B. auf eine Funktion oder die Registrierung eines User-Exit-Callbacks), der in dem mit dem Standard mitgelieferten Skript ebenfalls existiert. In diesem Fall muss also das betreffende sp-Skript kopiert und in ein cust-Skript umbenannt werden, wo dann das Customizing direkt übernommen wird.

Beispiel: Im Skript Workflow-ext/js/ou/cust/ptp/invoice/rob-callbacks-multiTable wurde die Funktion getImpersonalAccount angepasst. Das neue Skript ou.sp.ptpINV.userexit.functions.robMultiTable muss also kopiert und in ou.cust.ptpINV.userexit.functions.robMultiTable umbenannt werden. Dort wird nun das Customizing in die Funktion getImpersonalAccount übernommen.

Anschließend muss im Skript ou.cust.ptpINV.clientHeaderCode, um bei diesem Beispiel zu bleiben, im Array userExits der Eintrag "ou.sp.ptpINV.userexit.functions.robMultiTable" mit "ou.cust.ptpINV.userexit.functions.robMultiTable" ausgetauscht werden.

Möglichkeit 2: Das Customizing besteht aus einer Funktion oder einer Registrierung eines User-Exit-Callbacks, die im jeweiligen Standard-Skript nicht existiert. In diesem Fall kann einfach ein neues Portalskript angelegt werden, z.B. ou.cust.ptpINV.userexit.custFunctions.invoice, in welchem das Customizing dann platziert wird. Anschließend muss dieses Skript im Array userExits des Skripts ou.cust.ptpINV.clientHeaderCode eingetragen werden.

gefahr

Funktionen, die in einem Skript definiert werden, die dann im clientHeaderCode Skript im Browser geladen werden, sind dort nicht mehr automatisch im globalen Scope verfügbar. Dies muss nun explizit gesetzt werden:

Vorher (z.B. Workflow-ext/js/ou/cust/ptp/invoice/rob-callbacks-multiTable):

function someFunction() {
// ...
}

Nachher (z.B. ou.cust.ptpINV.userexit.functions.robMultiTable):

window.someFunction = function () {
// ...
};

Nach erfolgreicher Migration der Skripte kann die Datei Workflow-ext/jsp/script-ousp-invoice.jsp entfernt werden.

tipp

Alle Skripte im Workflow-ext-Verzeichnis, die nicht mit dem Standard mitgeliefert werden, können ebenfalls nach der beschriebenen Methode in ein Portalskript migriert werden.

HTML löschen

Folgende HTML-Dateien können gelöscht werden, falls diese in keinem cust-Skript referenziert werden:

  • Workflow-ext/html/ou/sp/ptpREC/gadget_addresses.html
  • Workflow-ext/html/ou/cust/folderCounter.html

Folgende HTML-Dateien wurden in die Globalen Eigenschaften verschoben:

  • Workflow-ext/html/ou/sp/ptpINV/activities.html --> ou.sp.ptpINV.gadget.activities
  • Workflow-ext/html/ou/sp/ptpINV/cover.html --> ou.sp.ptpINV.cover
  • Workflow-ext/html/ou/sp/ptpINV/dashboard-monitoring.html --> ou.sp.ptpINV.gadget.monitoring

Falls diese HTML-Dateien in einem cust-Skript referenziert werden, muss dort jeweils stattdessen die Globale Eigenschaft geladen werden – anschließend können die HTML-Dateien gelöscht werden.

Datenbank (ptpData)

orderItem

Die Spalten in der Tabelle orderItem wurden die Spalten quantity und singleAmount auf den korrekten Datentyp float angepasst. Außerdem wurden die Anpassungen für die Stammdaten-Erweiterungen hinzugefügt.

Vor der Datenbankmigration ist unbedingt zu prüfen: Sind die aktuellen Daten in orderItem für quantity und singleAmount in einem Format, welches automatisch migriert werden kann? (siehe Tabelle)

Konvertierung von String -> Float
BeispielwertmssqlmariaDB
1.63 (engl. Dezimalkennzeichen)
1.63 (whitespace davor / danach)✅ + Warnung
1,63 (dt. Dezimalkennzeichen)
1_63
1.000,63
1. 63

Falls das aktuelle Format passt, kann die Anpassung automatisch über das Datenbankscript ausgeführt werden.

gefahr

Es ist unbedingt zu prüfen, ob bei Customizing der Views viewActiveOrderItems bzw. viewActiveOrderItemsNotAssigned in den Spalten quantity und singleAmount nun der korrekte Datentyp zurückkommt!

recipientCurrencies

Diese Tabelle wurde hinzugefügt.

recipient

Erhält eine neue Spalte localCurrency sowie die Anpassungen für die Stammdaten-Erweiterungen.

Stammdaten-Erweiterungen (neue Spalten)

vendor, recipient, costcenter, orderItem

Den Tabellen wurden die Spalten externalId und lastModified hinzugefügt.

invoiceState

Der Tabelle invoiceState wurden die Spalten erpInvoiceNumber, bookingDate und bookingYear hinzugefügt.

Geänderte Views

gefahr

Durch das Ausführen des Datenbank-Scripts werden die Views neu erstellt. Bei Customizing unbedingt vorher ein Backup der Views anfertigen und dieses entsprechend adaptieren!

viewActiveOrderItems

Auch hier wurden die Spalten hinzugefügt.

viewInvoiceItemLatest

Die Query für die View viewInvoiceItemLatest wurde für eine bessere Laufzeit optimiert.

Neue Views

Die folgenden Views wurden für die Dexpro-Datenextraktion hinzugefügt:

  • viewDexproOrderItem
  • viewDexproVatRates
  • viewDexproCurrencySymbol
  • viewDexproCountry
  • viewDexproCostCenter
  • viewDexproRecipient
  • viewDexproVendor

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

Portalscripts

Fehlermethoden

Strings sollten nicht als Fehler geworfen werden, sondern immer als Error-Instanz.

gefahr

Es werden im SP-Code nur noch Error-Instanzen geworfen und geworfene Error-Instanzen korrekt gehandelt. Das Customizing ist daher unbedingt anzupassen!

Beispiel:

// ALT - nicht mehr machen
throw "das ist ein Fehler";

// NEU - immer so machen
throw new Error("das ist ein Fehler");

In Catch oder Logging-Einträgen kann auch alter Code unterstützt werden. Beispiel:

try {
execute();
} catch (error) {
util.out("Fehler: " + error.message ? error.message : error);
}

Durch die Abfrage, ob error.message existiert, wird sichergestellt, dass es sich um eine Error-Instanz handelt. Wenn dies nicht der Fall ist, wird davon ausgegangen, dass "error" ein String ist (alte Variante) und dieser geloggt.

Dies muss entsprechend im Customizing berücksichtigt werden. Dazu müssen alle cust-Scripte durchsucht werden.

Hilfreich sind Suchen im Code nach:

  • throw " - beginn eines Fehlerwurfs ohne Error-Instanz
  • +error und + error, um alte Loggings ohne error.message-Abfrage zu finden

Migration React Cover

Siehe Migration React File-Cover.


Migration React MultiTable-Gadgets

Siehe Migration React MultiTable-Gadgets.

In folgenden Skripten wird nun nicht mehr TableDialog aus ou.sp.gadget.TableDialog sondern TableDialogReact aus ou.sp.gadget.TableDialogReact verwendet. Die Importe und teils auch die Logik müssen in den cust-Scripten, sofern vorhanden, angepasst werden.

Liste der betroffenen SPC-Skripte
  • ou.spc.cfgVC.callback.functions.getVatCode
  • ou.spc.cfgVC.callback.functions.lookupVatCode
  • ou.spc.ptpINV.callback.functions.RMB.getItemOrderNumber
  • ou.spc.ptpINV.callback.functions.RMB.lookupItemOrderNumber
  • ou.spc.ptpINV.callback.functions.ROB.getCostCenter
  • ou.spc.ptpINV.callback.functions.ROB.getCostUnit
  • ou.spc.ptpINV.callback.functions.ROB.getImpersonalAccount
  • ou.spc.ptpINV.callback.functions.ROB.getVatCode
  • ou.spc.ptpINV.callback.functions.ROB.getVatCodeByVatRate
  • ou.spc.ptpINV.callback.functions.ROB.lookupCostCenter
  • ou.spc.ptpINV.callback.functions.ROB.lookupCostUnit
  • ou.spc.ptpINV.callback.functions.ROB.lookupImpersonalAccount
  • ou.spc.ptpINV.callback.functions.ROB.lookupVatCode
  • ou.spc.ptpINV.callback.functions.ROB.lookupVatCodeByVatRate
  • ou.spc.ptpINV.callback.functions.getPaymentTerm
  • ou.spc.ptpINV.callback.functions.getVendor
  • ou.spc.ptpINV.callback.functions.getVendorAccount
  • ou.spc.ptpINV.callback.functions.lookupPaymentTerm
  • ou.spc.ptpINV.callback.functions.lookupVendor
  • ou.spc.ptpINV.callback.functions.lookupVendorAccount
  • ou.spc.ptpVendorSettings.callback.functions.vendorSearch

SP-Skripte

ou.sp.ptpINV.lib.roundingErrorMargin (INV-607)

Das Skript ou.sp.ptpINV.lib.roundingErrorMargin wurde gelöscht, die Funktionalität ist nun im Skript ou.sp.ptpINV.lib.roundingDifference zu finden.

Dabei müssen für alle cust-Skripte, die eine Funktion aus dem gelöschten Skript importieren, die Imports auf das neue Skript angepasst werden.

SPC-Skripte

tipp

Für die Skripte dieses Abschnitts ist nur eine manuelle Anpassung notwendig, sofern ein zugehörges cust-Script auf dem System existiert.


Gadget_ou.spc.filetype.folder.html.Counter / Gadget_ou.spc.ptpINV.folder.html.Counter (POM-442)

Da das Skript Gadget_ou.spc.filetype.folder.html.Counter durch ein neues Skript Gadget_ou.spc.ptpINV.folder.html.Counter ersetzt wurde, muss überprüft werden, ob ein zugehöriges cust-Skript Gadget_ou.cust.filetype.folder.html.Counter mit Customizing existiert (Vergleich mit Inhalt von Gadget_ou.spc.ptpINV.folder.html.Counter).

Sofern das genannte cust-Skript vorhanden ist UND Customizing enthält, muss ein neues cust-Skript Gadget_ou.cust.ptpINV.folder.html.Counter mit dessen Customizing angelegt werden (hierbei die Library SDK Dokumentation zu spc-Skripten mit direkt ausführbarem Code beachten).

gefahr

Das alte Skript Gadget_ou.spc.filetype.folder.html.Counter sowie dessen zugehöriges cust-Skript wurde auch in den Produkten OUSP Request (bis einschl. Version 25.0.0) und OUSP Postman (bis einschl. Version 25.0.1) verwendet. Sofern diese Produkte installiert sind, dürfen die beiden Skripte nur dann gelöscht werden, wenn die installierten Versionen neuer als die eben Genannten sind.


ou.spc.ptpINV.settings.monitorRMB (INV-607)

Zuerst muss der Import angepasst werden:

Vorher:

var ou_sp_ptpINV_lib_roundingErrorMargin_1 = require("ou.sp.ptpINV.lib.roundingErrorMargin");

Nachher:

var ou_sp_ptpINV_lib_roundingDifference_1 = require("ou.sp.ptpINV.lib.roundingDifference");

Anschließend müssen noch die davon abhängigen Funktionsaufrufe angepasst werden:

Zum einen:

var monitorOptions = {
userConfigurationAttribute: userConfigurationAttribute,
/* Neuer Code */
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)(),
/* Ende neuer Code */
showOptions: true,
allowInsert: false,
rowStyle: function rowStyle(options) {
var isRowInvalid = +options.row.itemQuantity != +options.row.itemOrderQuantity || +options.row.itemSingleAmount != +options.row.itemOrderSingleAmount;
return isRowInvalid ? "background: #F5C4C4" : "";
},

Zum anderen:

itemTotalNetAmount: {
label: "de:Nettobetrag;en:Net amount",
type: "currency",
// width: "120px",
allowEdit: false,
allowInsert: false,
attributes: {
disabled: "disabled"
},
additionalOptions: {
/* Neuer Code */
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)()
/* Ende neuer Code */
},
footerTemplate: function footerTemplate(options) {

ou.spc.ptpINV.settings.monitorROB (INV-607)

Zuerst muss der Import angepasst werden:

Vorher:

var ou_sp_ptpINV_lib_roundingErrorMargin_1 = require("ou.sp.ptpINV.lib.roundingErrorMargin");

Nachher:

var ou_sp_ptpINV_lib_roundingDifference_1 = require("ou.sp.ptpINV.lib.roundingDifference");

Anschließend müssen noch die davon abhängigen Funktionsaufrufe angepasst werden:

Zum einen:

var monitorOptions = {
userConfigurationAttribute: userConfigurationAttribute,
/* Neuer Code */
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)(),
/* Ende neuer Code */
showOptions: true,
allowInsert: false,
columns: {

Zum anderen:

    itemTotalAmount: {
label: "de:Bruttobetrag;en:Total amount",
type: "currency",
width: "120px",
allowEdit: false,
allowInsert: false,
additionalOptions: {
/* Neuer Code */
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)()
/* Ende neuer Code */
},
footerTemplate: function footerTemplate(options) {

Abschließend muss dem Objekt buttons noch ein neuer Button hinzugefügt werden:

    roundingDifference: {
label: "de:Rundungsdifferenz;en:Rounding difference",
tooltip: "de:Position für Rundungsdifferenz hinzufügen;en:Add position for rounding difference",
click: function click() {
var _a;
var fileId = (_a = documentsContext.getFileContext()) === null || _a === void 0 ? void 0 : _a.getFileFieldValue("fileId");
if (!fileId) {
return;
}
documentsContext.executeScript("ou.sp.ptpINV.filetype.action.addRoundingDifferencePosition", {
fileId: fileId
});
documentsContext.updateFileView();
}
},

ou.spc.ptpINV.workflow.decision.checkAfterVerify (INV-609)

Dieser Import kann entfernt werden:

var ou_spc_ptpINV_lib_release_list_functions_1 = require("ou.spc.ptpINV.lib.release.list.functions");

Diese Variable muss hinzugefügt werden:

/* ... */
function isNextReleaseRequired() {
var docFile = otrAssert_1.default.getThrowingObject(context.file);
/* Neuer Code */
var settings = (0, ou_spc_ptpINV_settings_1.getSettings)();
/* Ende neuer Code */
try {
/* ... */

Zudem muss der Code in dieser If-Bedingung angepasst werden:

Vorher:

/* Code ab hier anpassen */
if (
context.currentUser === docFile.releaseUser &&
(0, ou_spc_ptpINV_lib_release_1.isUserAllowedToReleaseAmount)(context.currentUser, docFile.totalAmount)
) {
var allowedMessage = "Fachlicher Prüfer "
.concat(context.currentUser, " darf Betrag ")
.concat(docFile.totalAmount, " freigeben");
logger.info(allowedMessage);
docFile.insertStatusEntry("Betragsfreigabe", allowedMessage);
// In diesem Fall müssen alle Freigabeeinträge als inaktiv markiert werden.
var updatedList = (0, ou_spc_ptpINV_lib_release_list_functions_1.markAllAsInactive)(
JSON.parse(docFile.verification || "[]")
);
docFile.setFieldValue("verification", JSON.stringify(updatedList));
return false;
}
/* Ende Code anpassen */

Nachher:

/* Neuer Code */
if (
context.currentUser === docFile.releaseUser &&
(0, ou_spc_ptpINV_lib_release_1.isUserAllowedToReleaseAmount)(context.currentUser, docFile.totalAmount) &&
settings.minimumNumberOfReleaseUsers === 1
) {
var allowedMessage = "Fachlicher Prüfer "
.concat(context.currentUser, " darf Betrag ")
.concat(docFile.totalAmount, " freigeben");
logger.info(allowedMessage);
docFile.insertStatusEntry("Betragsfreigabe", allowedMessage);
var updatedList = (0, ou_spc_ptpINV_lib_release_list_1.markCurrentAsReleased)({
amount: docFile.totalAmount,
file: docFile,
releaseList: JSON.parse(docFile.verification || "[]"),
executingReleaseUser: docFile.releaseUser,
});
docFile.setFieldValue("verification", JSON.stringify(updatedList));
docFile.sync();
return false;
}
/* Ende neuer Code */

Zuletzt noch diese Zeile anpassen:

Vorher:

// Das Feld `releaseUser` aktualisieren
/* Code ab hier anpassen */
return (0, ou_spc_ptpINV_lib_release_list_1.setNextReleaseUser)(
context.file,
docFile.totalAmount,
(0, ou_spc_ptpINV_settings_1.getSettings)().useAssigneeUserReleaseRole
);
/* Ende Code anpassen */

Nachher:

// Das Feld `releaseUser` aktualisieren
/* Neuer Code */
return (0, ou_spc_ptpINV_lib_release_list_1.setNextReleaseUser)(
context.file,
docFile.totalAmount,
settings.useAssigneeUserReleaseRole
);
/* Ende neuer Code */

ou.spc.ptpINV.clientHeaderCode.css.ts

Vorher:

td.cell-itemOrderQuantity > span[disabled='disabled']:before,
td.cell-itemOrderSingleAmount > span[disabled='disabled']:before,
td.cell-itemDiscountAmount > span[disabled='disabled']:before,
td.cell-itemTotalNetAmount > span[disabled='disabled']:before {

Nachher:

td.cell-itemOrderQuantity > span[disabled]:before,
td.cell-itemOrderSingleAmount > span[disabled]:before,
td.cell-itemDiscountAmount > span[disabled]:before,
td.cell-itemTotalNetAmount > span[disabled]:before {

ou.spc.ptpINV.settings.monitorROB

Zuerst muss ein neuer Import hinzugefügt werden:

const ou_spc_ptpINV_settings_currencies_1 = require("ou.spc.ptpINV.settings.currencies");

Anschließend muss unterhalb der Importe folgende Zeile eingefügt werden:

const currencySymbol = ou_spc_ptpINV_settings_currencies_1.ptpINVCurrencies[context.file.currency]?.symbol || context.file.currency;

Die Parameter userConfigurationAttribute und roundingErrorMargin werden nun explizit über das Object additionalParams an das Gadget übergeben. Außerdem erhält das Object additionalParams einen zusätzlichen Eintrag currencySymbol.

Vorher:

const monitorOptions = {
/* Code ab hier anpassen */
userConfigurationAttribute,
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)(),
/* Ende Code anpassen */
showOptions: true,
allowInsert: false,
columns: {

Nachher:

const monitorOptions = {
showOptions: true,
allowInsert: false,
/* Neuer Code */
additionalParams: {
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)(),
userConfigurationAttribute,
currencySymbol
},
/* Ende neuer Code */
columns: {

Als Nächstes muss an ALLEN Spalten des Typs currency die Property currency wie folgt gesetzt werden:

Beispiel:

itemTotalNetAmount: {
label: "de:Nettobetrag;en:Net amount",
type: "currency",
/* Neuer Code */
currency: currencySymbol,
/* Ende neuer Code */
width: "120px",
required: true,
selectOnFocus: true,
...
}

Die change-Funktion der Spalte itemTotalNetAmount muss angepasst werden:

Vorher:

change: (value, options) => {
if (value == 0) {
// @ts-expect-error untyped in client
invplus.utils.setRowNumberValue(options.row, invplus.mapping.tableAmountFields.cellTaxAmount, 0);
// @ts-expect-error untyped in client
invplus.utils.setRowNumberValue(options.row, invplus.mapping.tableAmountFields.cellGrossAmount, 0);
// @ts-expect-error return for control flow
return;
}
// @ts-expect-error untyped in client
const vatRate = options.row.get(invplus.mapping.tableAmountFields.cellTaxRate);
if (value < 0) {
// @ts-expect-error untyped in client
const columnDebitCreditIndicatorVisible = options.columns.itemDebitCreditIndicator.visible;
documentsContext.openConfirmationDialog(
"Achtung",
`Sie haben einen negativen Betrag eingetragen, dieser ist nicht zulässig!<br/><br/>Soll der Betrag korrigiert${!columnDebitCreditIndicatorVisible ? " und die S/H Kennzeichen Spalte angezeigt" : ""} werden?`,
() => {
const newDebitCreditIndicator = options.row.get("itemDebitCreditIndicator") === "H" ? "S" : "H";
options.row.set("itemDebitCreditIndicator", newDebitCreditIndicator);
options.row.set("itemTotalNetAmount", value * -1);
// @ts-expect-error untyped in client
invplus.calculationAmountMultiTable.calculateAmountForRow(options.row, vatRate !== "");

/* Code ab hier anpassen */

window.multiTableInstances.invoiceItems.reRender();
if (!columnDebitCreditIndicatorVisible) {
const configurationDefinitionScript =
// @ts-expect-error untyped in client
window.multiTableInstances.invoiceItems.options.buttons.options.script;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
documentsContext.executeScript(
// @ts-expect-error untyped in client
window.multiTableInstances.invoiceItems.options.buttons.options.script,
{
configurationDefinitionScript,
userConfigurationAttribute:
window.multiTableInstances.invoiceItems.options.userConfigurationAttribute,
showItemDebitCreditIndicatorColumn: true,
},
{
async: false,
}
);
documentsContext.updateFileView();
}

/* Ende Code anpassen */
},
null,
null
);
// eslint-disable-next-line consistent-return
return "";
}
// @ts-expect-error untyped in client
invplus.calculationAmountMultiTable.calculateAmountForRow(options.row, vatRate !== "");
};

Nachher:

change: (value, options) => {
if (value == 0) {
// @ts-expect-error untyped in client
invplus.utils.setRowNumberValue(options.row, invplus.mapping.tableAmountFields.cellTaxAmount, 0);
// @ts-expect-error untyped in client
invplus.utils.setRowNumberValue(options.row, invplus.mapping.tableAmountFields.cellGrossAmount, 0);
// @ts-expect-error return for control flow
return;
}
// @ts-expect-error untyped in client
const vatRate = options.row.get(invplus.mapping.tableAmountFields.cellTaxRate);
if (value < 0) {
const columnDebitCreditIndicatorVisible = options.columns.itemDebitCreditIndicator.visible;
documentsContext.openConfirmationDialog(
"Achtung",
`Sie haben einen negativen Betrag eingetragen, dieser ist nicht zulässig!<br/><br/>Soll der Betrag korrigiert${!columnDebitCreditIndicatorVisible ? " und die S/H Kennzeichen Spalte angezeigt" : ""} werden?`,
() => {
const newDebitCreditIndicator = options.row.get("itemDebitCreditIndicator") === "H" ? "S" : "H";
options.row.set("itemDebitCreditIndicator", newDebitCreditIndicator);
options.row.set("itemTotalNetAmount", value * -1);
// @ts-expect-error untyped in client
invplus.calculationAmountMultiTable.calculateAmountForRow(options.row, vatRate !== "");

/* Neuer Code */

const ouGadgetInstances = getClientModule("ouGadgetInstances");
const button = ouGadgetInstances["multitable-invoiceItems"].buttons.options;
const { script } = button;
const { configurationDefinitionScript } = button.params;
if (!columnDebitCreditIndicatorVisible) {
documentsContext.executeScript(
script,
{
configurationDefinitionScript,
userConfigurationAttribute: options.table.additionalParams.userConfigurationAttribute,
showItemDebitCreditIndicatorColumn: true,
},
{
async: false,
}
);
documentsContext.updateFileView();
}

/* Ende neuer Code */
},
null,
null
);
// eslint-disable-next-line consistent-return
return "";
}
// @ts-expect-error untyped in client
invplus.calculationAmountMultiTable.calculateAmountForRow(options.row, vatRate !== "");
};

Die Spalte itemTotalAmount muss angepasst werden:

Vorher:

allowInsert: false,
/* Code ab hier anpassen */
additionalOptions: {
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)()
},
footerTemplate: options => {
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows,
// @ts-expect-error untyped in client
options.column.additionalOptions.roundingErrorMargin);
/* Code anpassen Ende */
const result = validator.validate();

Nachher:

allowInsert: false,
/* Neuer Code */
footerTemplate: options => {
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows, options.table.additionalParams.roundingErrorMargin);
/* Ende neuer Code */
const result = validator.validate();

Als Letztes muss an der Spalte itemTotalAmount in der footerTemplate Funktion im letzten return-Statement auch noch das Symbol durch ${options.table.additionalParams.currencySymbol} ersetzt werden.

Vorher:

itemTotalAmount: {
...
footerTemplate: options => {
...
const formatted = documents.sdk.utils.formatNumber(positionsAmount, null, null, 2);
/* Alter Code */
return `<div style='${styles.join(";")}' title='${result.message}'>${result.showWarning ? "<span class='entypo warning' style='color: #333333'></span>" : ""}${formatted} €</div>`;
/* Ende alter Code */
}
}

Nachher:

itemTotalAmount: {
...
footerTemplate: options => {
...
const formatted = documents.sdk.utils.formatNumber(positionsAmount, null, null, 2);
/* Neuer Code */
return `<div style='${styles.join(";")}' title='${result.message}'>${result.showWarning ? "<span class='entypo warning' style='color: #333333'></span>" : ""}${formatted} ${options.table.additionalParams.currencySymbol}</div>`;
/* Ende neuer Code */
}
}

Die click-Funtion des Buttons appendNewRow muss angepasst werden:

Vorher:

click: (options) => {
// @ts-expect-error untyped in client
const roundingErrorMargin = multiTableInstances.invoiceItems?.options.roundingErrorMargin || 0;
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows, roundingErrorMargin);
const newRow = validator.createRowWithNetAmountSuggestion();
if (!newRow) {
const emptyRow = options.addRow();
options.focusInputInRow(options.rows.indexOf(emptyRow), "itemImpersonalAccount");
return;
}
// @ts-expect-error untyped in client
const netAmount = newRow[invplus.mapping.tableAmountFields.cellNetAmount];
// Wenn Betrag negativ und S/H Spalte sichtbar,
// muss das Kennzeichen angepasst und der Betrag positiv werden.
if (netAmount < 0) {
// Sonderbehandlung
// Bei RoB: Wenn die Differenz negativ ist, dann soll H vorgeschlagen werden
// Bei RmB: Wenn die Differenz negativ ist, dann soll S vorgeschlagen werden
const fileContext = documentsContext.getFileContext();
const newIndicator = fileContext.getFileFieldValue("docType").substring(0, 1).toUpperCase() === "R" ? "H" : "S";
newRow.itemDebitCreditIndicator = newIndicator;
}
// Keine negativen Beträge in Rechnungen!
// @ts-expect-error untyped in client
newRow[invplus.mapping.tableAmountFields.cellNetAmount] = Math.abs(netAmount);
const row = options.addRow(newRow);
// @ts-expect-error untyped in client
invplus.calculationAmountMultiTable.calculateAmountForAllRows(options.rows, true);
options.focusInputInRow(options.rows.indexOf(row), "itemImpersonalAccount");
if (newRow.itemVatRate && !newRow.itemVatCode) {
// @ts-expect-error untyped in client
getDefaultVatCode(newRow.itemVatRate, function (entry) {
row.itemVatCode = entry.vatcode;
row.itemVatCodeDescription = entry.vatcodeName;
});
}
};

Nachher:

click: (options) => {
const roundingErrorMargin = options.table.additionalParams.roundingErrorMargin || 0;
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows, roundingErrorMargin);
const newRow = validator.createRowWithNetAmountSuggestion();
if (!newRow) {
const newRowIndex = options.rows.length;
options.addRow();
options.focusInputInRow(newRowIndex, "itemImpersonalAccount");
options.commit();
return;
}
// @ts-expect-error untyped in client
const netAmount = newRow[invplus.mapping.tableAmountFields.cellNetAmount];
// Wenn Betrag negativ und S/H Spalte sichtbar,
// muss das Kennzeichen angepasst und der Betrag positiv werden.
if (netAmount < 0) {
// Sonderbehandlung
// Bei RoB: Wenn die Differenz negativ ist, dann soll H vorgeschlagen werden
// Bei RmB: Wenn die Differenz negativ ist, dann soll S vorgeschlagen werden
const fileContext = documentsContext.getFileContext();
const newIndicator = fileContext.getFileFieldValue("docType").substring(0, 1).toUpperCase() === "R" ? "H" : "S";
newRow.itemDebitCreditIndicator = newIndicator;
}
// Keine negativen Beträge in Rechnungen!
// @ts-expect-error untyped in client
newRow[invplus.mapping.tableAmountFields.cellNetAmount] = Math.abs(netAmount);
const newIndex = options.rows.length;
const row = options.addRow(newRow);
// @ts-expect-error untyped in client
invplus.calculationAmountMultiTable.calculateAmountForAllRows(options.rows, true);
options.focusInputInRow(newIndex, "itemImpersonalAccount");
if (newRow.itemVatRate && !newRow.itemVatCode) {
// @ts-expect-error untyped in client
getDefaultVatCodeAsync(newRow.itemVatRate)
.then((data) => {
if (!data) {
return;
}
row.itemVatCode = data.vatcode;
row.itemVatCodeDescription = data.vatcodeName;
})
.catch(console.error)
.finally(() => {
options.commit();
});
} else {
options.commit();
}
};

Der click-Funktion der Buttons copyRow, deleteRow und splitAccounting muss jeweils dieser Aufruf als letzte Zeile hinzugefügt werden:

options.commit();

In der click-Funktion des Buttons compareAmount muss folgendes angepasst werden:

Vorher:

click: (options) => {
/* Code ab hier anpassen */
// @ts-expect-error untyped in client
const roundingErrorMargin = multiTableInstances.invoiceItems?.options.roundingErrorMargin || 0;
/* Ende Code anpassen */
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows, roundingErrorMargin);
const result = validator.validate();
result.showDialog();
};

Nachher:

click: (options) => {
/* Neuer Code */
const roundingErrorMargin = options.table.additionalParams.roundingErrorMargin || 0;
/* Ende neuer Code */
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows, roundingErrorMargin);
const result = validator.validate();
result.showDialog();
};

Die beiden Buttons moveRowUp und moveRowDown haben sich verändert:

Vorher:

moveRowUp: {
/* Code ab hier anpassen */
label: "<span class='ionicon ion-md-arrow-round-up'></span>",
/* Ende Code anpassen */
tooltip: "de:Zeile nach oben verschieben;en:Move row up",
enabled: "single",
toggleGroup: "navgroup",
click: options => {
/* Code ab hier anpassen */
const currentIndex = options.rows.indexOf(options.selectedRows[0]);
options.moveSelectedRow(currentIndex - 1);
/* Ende Code anpassen */
}
},
moveRowDown: {
/* Code ab hier anpassen */
label: "<span class='ionicon ion-md-arrow-round-down'></span>",
/* Ende Code anpassen */
tooltip: "de:Zeile nach unten verschieben;en:Move row down",
enabled: "single",
toggleGroup: "navgroup",
click: options => {
/* Code ab hier anpassen */
const currentIndex = options.rows.indexOf(options.selectedRows[0]);
options.moveSelectedRow(currentIndex + 1);
/* Ende Code anpassen */
}
},

Nachher:

moveRowUp: {
/* Neuer Code */
label: "move up",
icon: "ionicon ion-md-arrow-round-up",
onlyShowIcon: true,
/* Ende neuer Code */
tooltip: "de:Zeile nach oben verschieben;en:Move row up",
enabled: "single",
toggleGroup: "navgroup",
click: options => {
/* Neuer Code */
const currentIndex = options.getRowIndex(options.selectedRows[0]);
options.moveSelectedRow(currentIndex - 1);
options.commit();
/* Ende neuer Code */
}
},
moveRowDown: {
/* Neuer Code */
label: "move down",
icon: "ionicon ion-md-arrow-round-down",
onlyShowIcon: true,
/* Ende neuer Code */
tooltip: "de:Zeile nach unten verschieben;en:Move row down",
enabled: "single",
toggleGroup: "navgroup",
click: options => {
/* Neuer Code */
const currentIndex = options.getRowIndex(options.selectedRows[0]);
options.moveSelectedRow(currentIndex + 1);
options.commit();
/* Ende neuer Code */
}
},

ou.spc.ptpINV.settings.monitorRMB

Es wurde die irreführend benannte Spalte itemOrderQuantity von "WE-Menge" in die korrekte Bezeichung "Bestellmenge" umbenannt und eine neue Spalte "WE-Menge" (itemGoodsReceiptQuantity) eingeführt. Dies kann im SPC-Script nachvollzogen und ins Customizing übertragen werden, sofern benötigt.

Außerdem wurde der Datentyp von itemOrderQuantity und itemQuantity auf den korrekten Typ "number" geändert.

Zuerst muss ein neuer Import hinzugefügt werden:

const ou_spc_ptpINV_settings_currencies_1 = require("ou.spc.ptpINV.settings.currencies");

Anschließend muss unterhalb der Importe folgende Zeile eingefügt werden:

const currencySymbol = ou_spc_ptpINV_settings_currencies_1.ptpINVCurrencies[context.file.currency]?.symbol || context.file.currency;

Als Nächstes muss in den globalen additionalParams das currencySymbol hinzugefügt werden:

Vorher:

const monitorOptions = {
showOptions: true,
allowInsert: false,
additionalParams: {
userConfigurationAttribute,
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)()
},
...
}

Nachher:

const monitorOptions = {
showOptions: true,
allowInsert: false,
additionalParams: {
userConfigurationAttribute,
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)(),
/* Neuer Code */
currencySymbol
/* Ende neuer Code */
},
...
}

Als Nächstes muss an ALLEN Spalten des Typs currency die Property currency wie folgt gesetzt werden:

Beispiel:

itemSingleAmount: {
label: "de:Einzelpreis;en:Single amount",
// width: "90px",
type: "currency",
/* Neuer Code */
currency: currencySymbol,
/* Ende neuer Code */
selectOnFocus: true,
change: (value, options) => {
// @ts-expect-error untyped in client
invplus.calculationAmountMultiTable.calculateTotalAmountForRow(options.row, 0, 0);
},
...
}

Als Letztes muss an der Spalte itemTotalNetAmount in der footerTemplate Funktion im letzten return-Statement das Symbol durch ${options.table.additionalParams.currencySymbol} ersetzt werden.

Vorher:

itemTotalNetAmount: {
...
footerTemplate: options => {
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows, options.table.additionalParams.roundingErrorMargin);
const result = validator.validate();
const positionsAmount = options.rows
// @ts-expect-error untyped in client
.reduce((acc, row) => acc.plus(+row.itemTotalNetAmount || 0), new Big(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);
/* Neuer Code */
return `<div style='${styles.join(";")}' title='${result.message}'>${result.showWarning ? "<span class='entypo warning' style='color: #333333'></span>" : ""}${formatted} €</div>`;
/* Ende neuer Code */
}
}

Nachher:

itemTotalNetAmount: {
...
footerTemplate: options => {
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows, options.table.additionalParams.roundingErrorMargin);
const result = validator.validate();
const positionsAmount = options.rows
// @ts-expect-error untyped in client
.reduce((acc, row) => acc.plus(+row.itemTotalNetAmount || 0), new Big(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>`;
}
}

Die Parameter userConfigurationAttribute und roundingErrorMargin werden nun explizit über das Object additionalParams an das Gadget übergeben.

Vorher:

const monitorOptions = {
/* Code ab hier anpassen */
userConfigurationAttribute,
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)(),
/* Ende Code anpassen */
showOptions: true,
allowInsert: false,

Nachher:

const monitorOptions = {
showOptions: true,
allowInsert: false,
/* Neuer Code */
additionalParams: {
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)(),
userConfigurationAttribute
},
/* Ende neuer Code */

Die Spalte itemTotalNetAmount muss angepasst werden:

Vorher:

attributes: {
disabled: "disabled"
},
/* Code ab hier anpassen */
additionalOptions: {
roundingErrorMargin: (0, ou_sp_ptpINV_lib_roundingDifference_1.getRoundingErrorMarginAsNumberInEuros)()
},
footerTemplate: options => {
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows,
// @ts-expect-error untyped in client
options.column.additionalOptions.roundingErrorMargin);
/* Code anpassen Ende */
const result = validator.validate();

Nachher:

attributes: {
disabled: "disabled"
},
/* Neuer Code */
footerTemplate: options => {
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows, options.table.additionalParams.roundingErrorMargin);
/* Ende neuer Code */
const result = validator.validate();

Die Buttons appendAllOrderItems und appendNewRow müssen angepasst werden:

Vorher:

appendAllOrderItems: {
/* Code ab hier anpassen */
label: "<span class='mdi mdi-download-multiple'></span>",
/* Ende Code anpassen */
tooltip: "de:Bestellzeilen laden;en:Load positions",
// visible: !!docFile.orderNumber,
click: "loadAllOrders"
},
appendNewRow: {
label: "de:Neue Zeile;en:New row",
tooltip: "de:Neue Zeile hinzufügen;en:Add new Row",
click: options => {
const newRow = options.addRow({
itemSingleAmount: 0,
itemOrderSingleAmount: 0,
itemDiscountAmount: 0,
itemTotalNetAmount: 0
});
/* Code ab hier anpassen */
const index = options.rows.indexOf(newRow);
/* Ende Code anpassen */
options.focusInputInRow(index, "itemOrderNumber");
}
},

Nachher:

appendAllOrderItems: {
/* Neuer Code */
label: "appendAllOrderItems",
icon: "mdi mdi-download-multiple",
onlyShowIcon: true,
/* Ende neuer Code */
tooltip: "de:Bestellzeilen laden;en:Load positions",
// visible: !!docFile.orderNumber,
click: "loadAllOrders"
},
appendNewRow: {
label: "de:Neue Zeile;en:New row",
tooltip: "de:Neue Zeile hinzufügen;en:Add new Row",
click: options => {
const newRow = options.addRow({
itemSingleAmount: 0,
itemOrderSingleAmount: 0,
itemDiscountAmount: 0,
itemTotalNetAmount: 0
});
/* Neuer Code */
const index = options.getRowIndex(newRow);
options.focusInputInRow(index, "itemOrderNumber");
options.commit();
/* Ende neuer Code */
}
},

Den click-Funktionen der Buttons copyRow und deleteRow muss jeweils dieser Aufruf als letzte Zeile hinzugefügt werden:

options.commit();

Die click-Funktion des Buttons compareAmount muss folgendes angepasst werden:

Vorher:

compareAmount: {
label: "de:Nettobetrag prüfen;en:check after deduction",
tooltip: "de:Nettobetrag aller Zeilen splitten;en:Check all rows after deduction",
click: options => {
/* Code ab hier anpassen */
// @ts-expect-error untyped in client
const roundingErrorMargin = multiTableInstances.invoiceItems?.options.roundingErrorMargin || 0;
/* Ende Code anpassen */
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows, roundingErrorMargin);
const result = validator.validate();
result.showDialog();
}
},

Nachher:

compareAmount: {
label: "de:Nettobetrag prüfen;en:check after deduction",
tooltip: "de:Nettobetrag aller Zeilen splitten;en:Check all rows after deduction",
click: options => {
/* Neuer Code */
const roundingErrorMargin = options.table.additionalParams.roundingErrorMargin || 0;
/* Ende neuer Code */
// @ts-expect-error untyped in client
const validator = new invplus.validationAmountMultiTable(options.rows, roundingErrorMargin);
const result = validator.validate();
result.showDialog();
}
},

Die beiden Buttons moveRowUp und moveRowDown haben sich verändert:

Vorher:

moveRowUp: {
/* Code ab hier anpassen */
label: "<span class='ionicon ion-md-arrow-round-up'></span>",
/* Ende Code anpassen */
tooltip: "de:Zeile nach oben verschieben;en:Move row up",
enabled: "single",
toggleGroup: "navgroup",
click: options => {
/* Code ab hier anpassen */
const currentIndex = options.rows.indexOf(options.selectedRows[0]);
options.moveSelectedRow(currentIndex - 1);
/* Ende Code anpassen */
}
},
moveRowDown: {
/* Code ab hier anpassen */
label: "<span class='ionicon ion-md-arrow-round-down'></span>",
/* Ende Code anpassen */
tooltip: "de:Zeile nach unten verschieben;en:Move row down",
enabled: "single",
toggleGroup: "navgroup",
click: options => {
/* Code ab hier anpassen */
const currentIndex = options.rows.indexOf(options.selectedRows[0]);
options.moveSelectedRow(currentIndex + 1);
/* Ende Code anpassen */
}
},

Nachher:

moveRowUp: {
/* Neuer Code */
label: "up",
icon: "ionicon ion-md-arrow-round-up",
onlyShowIcon: true,
/* Ende neuer Code */
tooltip: "de:Zeile nach oben verschieben;en:Move row up",
enabled: "single",
toggleGroup: "navgroup",
click: options => {
/* Neuer Code */
const currentIndex = options.getRowIndex(options.selectedRows[0]);
options.moveSelectedRow(currentIndex - 1);
options.commit();
/* Ende neuer Code */
}
},
moveRowDown: {
/* Neuer Code */
label: "down",
icon: "ionicon ion-md-arrow-round-down",
onlyShowIcon: true,
/* Ende neuer Code */
tooltip: "de:Zeile nach unten verschieben;en:Move row down",
enabled: "single",
toggleGroup: "navgroup",
click: options => {
/* Neuer Code */
const currentIndex = options.rows.indexOf(options.selectedRows[0]);
options.moveSelectedRow(currentIndex + 1);
options.commit();
/* Ende neuer Code */
}
},

Gadget_ou.spc.cfgBT.filetype.field.multiTable.ts

Hier wird nun das neue TableGadgetReact verwendet. Dazu müssen Import und Aufruf angepasst werden.

Vorher:

/* Code ab hier anpassen */
const ou_sp_gadget_TableGadget_1 = require("ou.sp.gadget.TableGadget");
/* Ende Code anpassen */
const ou_sp_invplus_utils_1 = require("ou.sp.invplus.utils");
const ou_sp_invplus_fieldMapping_1 = require("ou.sp.invplus.fieldMapping");
function executeDirectly() {
/* Code ab hier anpassen */
return new ou_sp_gadget_TableGadget_1.TableGadget({
/* Ende Code anpassen */

Nachher:

/* Neuer Code */
const ou_sp_gadget_TableGadgetReact_1 = require("ou.sp.gadget.TableGadgetReact");
/* Ende neuer Code */
const ou_sp_invplus_utils_1 = require("ou.sp.invplus.utils");
const ou_sp_invplus_fieldMapping_1 = require("ou.sp.invplus.fieldMapping");
function executeDirectly() {
/* Neuer Code */
return new ou_sp_gadget_TableGadgetReact_1.TableGadgetReact({
/* Ende neuer Code */

In der change-Funktion der Spalte itemTotalNetAmount kann folgende markierte Zeile entfernt werden:

change: (value, options) => {
if (value == 0) {
(0, ou_sp_invplus_utils_1.setRowNumberValue)(options.row, ou_sp_invplus_fieldMapping_1.tableAmountFields.cellTaxAmount, 0);
(0, ou_sp_invplus_utils_1.setRowNumberValue)(options.row, ou_sp_invplus_fieldMapping_1.tableAmountFields.cellGrossAmount, 0);
return;
}
const vatRate = options.row.get(ou_sp_invplus_fieldMapping_1.tableAmountFields.cellTaxRate);
// @ts-expect-error untyped options
if (value < 0 && options.columns.itemDebitCreditIndicator.activated === true) {
const newDebitCreditIndicator = options.row.get("itemDebitCreditIndicator") === "H" ? "S" : "H";
options.row.set("itemDebitCreditIndicator", newDebitCreditIndicator);
options.row.set("itemTotalNetAmount", value * -1);
// @ts-expect-error untyped client side
invplus.calculationAmountMultiTable.calculateAmountForRow(options.row, vatRate !== "");

/* Code ab hier entfernen */
window.multiTableInstances.invoiceItems.reRender();
/* Ende Code entfernen */

ou.spc.ptpINV.settings.mappingConfig

Da die Währungsprüfung in ou.sp.ptpINV.workflow.sendsignal.default ab sofort über die am Rechnungskreis konfigurierten zulässigen Währungen erfolgt, wird das Array ptpINVAllowedCurrency - sofern nicht von anderen cust-Skripten importiert - nicht mehr benötigt und kann zusammen mit seinem Export aus dem Skript entfernt werden.


ou.spc.global.job.removeCache

Hier muss der neue PropCache currencies und der bisher fehlende allreleaseRole hinzugefügt werden:

function executeDirectly() {
/* Neuer Code */
propCache.removeProperty("currencies");
propCache.removeProperty("allreleaseRole");
/* Ende neuer Code */
propCache.removeProperty("ptpRecipient");
propCache.removeProperty("statusCodes");
propCache.removeProperty("cfgStatusCodes");
propCache.removeProperty("enumAPptpInvoiceUser");
(0, ou_sp_cfgCC_lib_1.clearCostCenterCache)();
}

ou.sp.ptpINV.lib.xtract.insiders + ou.sp.ptpINV.lib.xtract (INV-624)

Die Klasse InsidersXtractor im Skript ou.sp.ptpINV.lib.xtract.insiders wurde umgestellt auf das neue Xtractor-Interface. D.h. die alten Methoden wurden entfernt und es stehen nur noch die im Interface definierten Methoden zur Verfügung.

Ebenso wurden die Wrapper-Funktionen im Skript ou.sp.ptpINV.lib.xtract, die das Interface verwenden, dementsprechend umgestellt.

Falls es Customizing gibt, welches den alten InsidersExtractor bzw. die Funktionen aus dem Skript ou.sp.ptpINV.lib.xtract verwendet, gibt es zwei Möglichkeiten:

  1. Das Customizing muss auf das neue Interface bzw. die neuen Funktionen umgestellt werden.

  2. Über die Methode execute können immer noch generische Aufrufe an den InsidersConnector getätigt, d.h. mit minimalem Aufwand die alten Aufrufe weiterhin nachgestellt werden.

tipp

Falls die alte Funktionalität der beiden Skripte im Customizing verwendet wird, den Code am besten mit einem Entwickler evaluieren und das weitere Vorgehen besprechen.


ou.spc.ptpINV.settings.insiders

Über dieses Setting-Skript kann nun auch das Mapping zwischen Insiders- und Invoice-Feldern mitgegeben werden. Wird kein Mapping mitgegeben, zieht der InsidersXtractor standardmäßig auch das Mapping aus dem Skript ou.spc.ptpINV.settings.mappingConfig.

const ou_spc_ptpINV_settings_mappingConfig_1 = require("ou.spc.ptpINV.settings.mappingConfig");
const insidersConnectorSettings = {
os: util.getEnvironment("PATH").substring(0, 1) === "/" ? "linux" : "win",
debug: false,
mapping: {
fieldMapping: ou_spc_ptpINV_settings_mappingConfig_1.invFieldsMapping,
invoiceItemsCellMapping: ou_spc_ptpINV_settings_mappingConfig_1.invRMBPosMapping.xtract,
},
workingDir: {
win: "d:\\EASY\\OUC-Tools\\insidersConnector",
linux: "/opt/ouc-tools/insiders-connector",
},
executable: {
win: "\\insiders-connector.exe",
linux: "/insiders-connector",
},
taskNames: {
create: "create",
createWithVerify: "createWithVerify",
createWithVerifyJSON: "createWithVerifyJSON",
getData: "getData",
getFile: "getFile",
getInfo: "getInfo",
delete: "delete",
importAndLock: "startProcessing",
unlock: "finishProcessing",
},
};

ou.spc.ptpINV.callbacks

Folgende If-Bedingung im Callback registerBeforeSendsignalDefault wurde angepasst:

Vorher:

/* Code ab hier anpassen */
if (docFile.globalState !== "10") {
/* Ende Code anpassen */
const step = docFile.getFirstLockingWorkflowStep();
logger.debug(`No update for file in workflow step ${step.name}`);
return;
}
docFile.insertStatusEntry(context.scriptName, "Datenübernahme beginnt");

Nachher:

/* Code ab hier anpassen */
if (docFile.globalState !== "10" && docFile.globalState !== "15") {
/* Ende Code anpassen */
const step = docFile.getFirstLockingWorkflowStep();
logger.debug(`No update for file in workflow step ${step.name}`);
return;
}
docFile.insertStatusEntry(context.scriptName, "Datenübernahme beginnt");

In der Callback-Funktion registerAfterSendsignalDefault wurde Code entfernt:

gefahr

Falls weiterhin Insiders zur Datenextraktion verwendet wird und in diesem Code Customizing existiert, darf der Code nicht gelöscht werden!

    /* ... */
const docFile = data.docFile;
/* Ab hier löschen */
const errorCodes = [];
const documentReaderJson = JSON.parse(docFile.xtractJson || "{}");
if (docFile.xtractState === "ready" && documentReaderJson.errors && documentReaderJson.errors.some(error => error.error.substring(1, 11) === "Error10284")) {
// Korrupte PDFs
errorCodes.push("29");
} else if (docFile.xtractState === "ready" && docFile.xtractProcessingId === "") {
// no PDF found
errorCodes.push("16");
} else {
const xtractErrorDetails = [];
if (documentReaderJson.userComment) {
docFile.insertStatusEntry("Verifier Kommentar", documentReaderJson.userComment);
}
// Allgemeine Fehler aus der Belegleserrückgabe ermitteln
if (documentReaderJson.errors) {
errorCodes.push("03");
xtractErrorDetails["03"] = JSON.stringify(documentReaderJson.errors);
if (documentReaderJson.errors.status.toString() === "404" || documentReaderJson.errors.some(error => error.status.toString() === "404")) {
xtractErrorDetails["03"] += `\r\nUrsprüngliche Transaktions-ID: ${docFile.xtractProcessingId}`;
}
docFile.insertStatusEntry("Datenextraktionsfehler", `verifyXtractData - Document Reading Export Error (${context.scriptName})`);
docFile.sync();
}
// Prüfe ob Dokument ausgesteuert wurde.
if (documentReaderJson.docType === "Reject") {
errorCodes.push("27");
} else if (documentReaderJson.fields) {
// Direkte Fehlerprüfung auf Belegleserfeldern
// Prüfungen werden in invErrorMapping gepflegt
const {
fields
} = documentReaderJson;
fields.forEach(field => {
const validationFunc = ou_spc_ptpINV_settings_mappingConfig_1.invErrorMapping[field.name];
if (validationFunc) {
const errorCode = validationFunc(field.value);
if (errorCode) {
errorCodes.push(errorCode);
}
}
});
// Zusatzinformationen für Nicht-USt-konforme Belege ermitteln
if (errorCodes.indexOf("12") >= 0) {
const complComments = fields.filter(field => field.name === "INV_COMPLIANCE_COMMENTS");
xtractErrorDetails["12"] = complComments.length > 0 ? complComments[0].value : "";
}
}
}
errorCodes.forEach(code => data.updateStatus(code, false));
docFile.xtractCode = errorCodes.join("\n");
docFile.sync();
/* Bis hier löschen */
if (docFile.orderNumber && docFile.docType.substring(1) === "MB") {
/* ... */

Stattdessen wird nun folgende Funktion aufgerufen:

    /* ... */
const docFile = data.docFile;
/* Neuer Code */
try {
(0, ou_sp_ptpINV_lib_xtract_1.finishXtractionProcess)({
docFile
});
} catch (error) {
logger.error(`Failed to finish xtraction process for file ${docFile.fileId}: ${error}`);
}
/* Ende neuer Code */
if (docFile.orderNumber && docFile.docType.substring(1) === "MB") {
/* ... */

Falls der Code im letzten Schritt entfernt wurde, kann dieser Import nun entfernt werden (sofern er nicht im Customizing verwendet wird):

/* Entfernen, falls nicht im Customizing verwendet */
const ou_spc_ptpINV_settings_mappingConfig_1 = require("ou.spc.ptpINV.settings.mappingConfig");
/* Entfernen Ende */

ou.spc.ptpINV.filetype.action.manStartWF
gefahr

Wenn Insiders weiter zur Datenextraktion verwendet wird und im Code Customizing existiert, dürfen folgende Änderungen nicht durchgeführt werden!

Folgende If-Bedingung wurde angepasst:

Vorher:

/* Code ab hier anpassen */
if (startXtract) {
// verification will be cleared by clearXtractState
clearXtractState(docFile);
// check if file has pdf and xml documents and if the name is same, then delete the pdf
const attachmentsRegister = docFile.getRegisterByName("documents");
const attachments = attachmentsRegister.getDocuments();
if (attachments.size() > 1) {
(0, ou_sp_ptpINV_lib_1.deletePdfDocumentIfXInvoice)(attachmentsRegister, attachments);
}
} else {
const updatedList = (0, ou_spc_ptpINV_lib_release_list_functions_1.markAllAsInactive)(
JSON.parse(docFile.verification || "[]")
);
docFile.setFieldValue("verification", JSON.stringify(updatedList));
}
docFile.sync();
/* Ende Code anpassen */

Nachher:

/* Neuer Code */
if (startXtract) {
(0, ou_sp_ptpINV_lib_xtract_1.clearXtractState)({
docFile,
});
} else {
const updatedList = (0, ou_spc_ptpINV_lib_release_list_functions_1.markAllAsInactive)(
JSON.parse(docFile.verification || "[]")
);
docFile.setFieldValue("verification", JSON.stringify(updatedList));
docFile.sync();
}
/* Ende neuer Code */

Folgender Import ist dazu noch notwendig:

/* Neuer Code */
const ou_sp_ptpINV_lib_xtract_1 = require("ou.sp.ptpINV.lib.xtract");
/* Ende neuer Code */

Folgender Import kann entfernt werden, sofern er nicht im Customizing verwendet wird:

/* Entfernen, falls nicht im Customizing verwendet */
const ou_sp_ptpINV_lib_1 = require("ou.sp.ptpINV.lib");
/* Entfernen Ende */

ou.spc.ptpINV.settings.xtract
gefahr

Dies ist nur relevant, wenn Insiders weiterhin zur Datenextraktion verwendet werden soll!

Im Skript ou.spc.ptpINV.settings.xtract wird definiert, welche Datenextraktion im Workflow verwendet wird. Dort ist nun als Standard Dexpro hinterlegt. Um weiterhin Insiders zu verwenden, muss ein Skript ou.cust.ptpINV.settings.xtract angelegt werden, welches dann folgendermaßen aussieht:

context.enableModules();
exports.xtractSettings = void 0;
const ou_sp_ptpINV_lib_xtract_insiders_1 = require("ou.sp.ptpINV.lib.xtract.insiders");
const ou_spc_ptpINV_settings_insiders_1 = require("ou.spc.ptpINV.settings.insiders");
const xtractSettings = {
xtractor: ou_sp_ptpINV_lib_xtract_insiders_1.InsidersXtractor,
options: ou_spc_ptpINV_settings_insiders_1.insidersConnectorSettings,
};
exports.xtractSettings = xtractSettings;

ou.spc.ptpINV.settings.mappingConfig

Am Object invRMBPosMapping gibt es nun eine neue Eigenschaft dexpro, wo das Mapping für die Dexpro-RMB-Positionen definiert werden kann.

  dexpro: {
PosArticleCode: {
field: "itemArticleNumber"
},
PosDescription: {
field: "itemArticleName"
},
PosQuantity: {
field: "itemQuantity",
format: stringToNumber
},
PosSinglePrice: {
field: "itemSingleAmount",
format: stringToNumber
},
PosTotalAmount: {
field: "itemTotalNetAmount",
format: stringToNumber
}
}
};

function stringToNumber(value) {
if (typeof value === "number") {
return value;
}
return context.convertStringToNumeric(value, ",", "");
}

ou.spc.ptpINV.filetype.property.hROnFileViewScript (INV-633)

Das neue Register "Datenextraktion" ist nur für Dexpro relevant und muss daher konditional ausgeblendet werden:

Neuer Import:

const ou_sp_ptpINV_lib_xtract_1 = require("ou.sp.ptpINV.lib.xtract");

Anschließend muss dieser Code-Block hinzugefügt werden:

  const squeezeViewerIndex = enumval.indexOf("squeezeViewer");
if (squeezeViewerIndex >= 0 && (!(0, ou_sp_ptpINV_lib_xtract_1.hasDocumentsIntegratedValidation)() || docFile.globalState !== "15")) {
enumval[squeezeViewerIndex] = "";
}

ou.spc.ptpINV.workflow.guard.backToVerification (INV-647)

Am Button "zurück zur fachl. Prüfung" wurde der Skript-Parameter verificationGroup entfernt.

Falls ein cust-Skript besteht:

  1. muss aus diesem der Skript-Parameter verificationGroup manuell entfernt werden und

  2. muss im cust-Skript ou.cust.ptpINV.workflow.guard.backToVerification folgender Check entfernt werden:

if (verificationGroup) {
docFile.setFieldValue("verificationGroup", verificationGroup);
} else {
errorMessage +=
"Bitte wählen Sie unter 'Fachliche Prüfungsgruppe' eine Gruppe zur fachlichen Prüfung aus!\r\n";
}

ou.spc.cfgCC.filetype.action.addCostCenter (INV-652)

Hier wird nun bei fehlender Sicherheitsbestätigung im Dialog ein entsprechender Fehler im UI angezeigt (Abfrage if (!areYouSure) {...} wandert in den try-Block und wirft nun einen Fehler). Falls das onSave-Skript der Mappe einen Fehler wirft, wird dieser nun in einer Variable zwischengespeichert, damit er nicht durch das docFile.abort() überschrieben wird. Außerdem wird nach dem erfolgreichen Hinzufügen der ausgewählten Kostenstellen die Ansicht des Ordners cfgCostCenter aktualisiert (return 0; am Ende des try-Blocks wird durch eine neue Logik ersetzt):

Vorher:

function executeDirectly() {
if (!areYouSure) {
return 0;
}
try {
if (!costCenter) {
throw new Error("Es wurde keine Kostenstelle ausgewählt");
}
...
if (!docFile.commit()) {
docFile.abort();
throw new Error(`Mappe konnte nicht aktualisiert werden: ${docFile.getLastError()}`);
}

return 0;
} catch (error) {
...
}
}

Neu:

function executeDirectly() {
try {
/* Neuer Code */
if (!areYouSure) {
throw new Error("Wenn Sie fortfahren möchten, bestätigen Sie bitte die Änderungen indem Sie 'Ja' auswählen.");
}
/* Ende neuer Code */
if (!costCenter) {
throw new Error("Es wurde keine Kostenstelle ausgewählt");
}
...
/* Neuer Code */
if (!docFile.commit()) {
const errorMsg = docFile.getLastError();
docFile.abort();
throw new Error(`Mappe konnte nicht aktualisiert werden: ${errorMsg}`);
}

const it = context.getFoldersByName("cfgCostCenter");
if (it.size() !== 1) {
return 0;
}
context.returnType = "updateTree";
return it.first().id;
/* Ende neuer Code */
} catch (error) {
...
}
}

ou.spc.cfgCC.filetype.action.field.costCenter (INV-652)

Hier wird nun der Fall abgefangen, dass die Werte der Auswahlliste leer sind:

...
try {
const docFile = context.file;
const files = new FileResultset("cfgCostCenter", `uuid != '${docFile.uuid}' and recipient = '${docFile.recipient}'`);
const costCenters = (0, ou_sp_cfgCC_lib_1.createCostCenterNameMapping)();
ou_sp_Iterators_1.Iterators.foreach(files, file => {
file.costCenter.split("\r\n").forEach(costCenter => enumval.push(`${costCenter}|${file.getid()};${costCenter} - ${costCenters[costCenter] || ""}`));
});
/* Neuer Code */
if (enumval.length === 0) {
enumval.push("");
}
/* Ende neuer Code */
enumval.sort();
return 0;
} ...

ou.spc.ptpINV.job.uploadMasterData (INV-638)

Das Skript ou.spc.ptpINV.job.uploadMasterData wurde mit diesem Update entfernt und durch das Skript ou.spc.ptpINV.job.uploadMasterData.insiders ersetzt. Falls Insiders nicht mehr als Datenextraktion verwendet wird, kann das kann das Cust-Skript ebenfalls entfernt und die weiteren Schritte übersprungen werden.

Andernfalls muss das Cust-Skript in ou.cust.ptpINV.job.uploadMasterData.insiders umbenannt sowie das Skript ou.spc.ptpINV.job.uploadMasterData.insiders wieder als Job aktiviert werden.

Zudem wird für das Erstellen eines Jira-Tickets nun die neue Funktion aus der Library sowie die Konfiguration aus OUD verwendet.

Folgender Import kann entfernt werden:

const ou_sp_HttpClient_1 = require("ou.sp.HttpClient");

Folgende Importe müssen hinzugefügt werden, wenn diese noch nicht existieren:

const tslib_1 = require("ou.sp.externals.tslib");
const ou_sp_Jira_1 = require("ou.sp.Jira");
const ou_sp_oud_settings_1 = tslib_1.__importDefault(require("ou.sp.oud.settings"));

Nun kann die Funktion handleTaskResult angepasst werden:

Vorher:

function handleTaskResult(taskResult) {
if (taskResult.errors && taskResult.errors.length > 0 || taskResult.successful === false) {
try {
/* Code ab hier anpassen */
createJiraTicket(taskResult);
/* Ende Code anpassen */
} catch (error) {

Nachher:

function handleTaskResult(taskResult) {
if (taskResult.errors && taskResult.errors.length > 0 || taskResult.successful === false) {
try {
/* Neuer Code */
const errorMsg = `Fehler beim Stammdatenupload: ${JSON.stringify(taskResult.errors)}. Bitte prüfen und ggf. den Job erneut starten.`;
const config = ou_sp_oud_settings_1.default.jiraReportConfig?.masterDataUpload;
if (!config) {
throw new Error("Jira report config for masterDataUpload is not set");
}
(0, ou_sp_Jira_1.createJiraErrorReport)(config, errorMsg);
/* Ende neuer Code */
} catch (error) {

Abschließend kann die Funktion createJiraTicket entfernt werden, da diese nicht mehr benötigt wird.


ou.spc.ptpINV.settings.uploadMasterData.mapping (INV-638)

Das Skript ou.spc.ptpINV.settings.uploadMasterData.mapping wurde mit diesem Update entfernt und durch das Skript ou.spc.ptpINV.settings.uploadMasterData.insiders.mapping ersetzt. Falls Insiders nicht mehr zur Datenextraktion verwendet wird, kann das Cust-Skript ebenfalls entfernt werden.

Andernfalls muss das Cust-Skript in ou.cust.ptpINV.settings.uploadMasterData.insiders.mapping umbenannt werden


ou.spc.ptpINV.workflow.action.incomingEvent (INV-633)

Für die neue Documents-integrierte Validierung durch die Dexpro-Datenextraktion soll im Status 15 automatisch das Register "Datenextraktion" geöffnet werden. Dazu ist folgende Anpassung notwendig:

Neuer Import:

const ou_sp_ptpINV_lib_xtract_1 = require("ou.sp.ptpINV.lib.xtract");

Anschließend muss dieser Code-Block hinzugefügt werden:

  switch (docFile.globalState) {
/* Neuer Code */
case "15":
if ((0, ou_sp_ptpINV_lib_xtract_1.hasDocumentsIntegratedValidation)()) {
docFile.setAttribute("AutoOpenFirstDocument", "true");
}
break;
/* Ende neuer Code */
case "20":
Gruppe = docFile.indexGroup;
break;
case "30":

ou.spc.ptpINV.workflow.action.outgoingEvent (INV-633)

Nach dem Status 15 soll die Eigenschaft AutoOpenFirstDocument wieder entfernt werden, damit im nächsten Status nicht mehr automatisch das erste Dokumentenregister geöffnet wird.

Neuer Import:

const ou_sp_ptpINV_lib_xtract_1 = require("ou.sp.ptpINV.lib.xtract");

Anschließend muss dieser Code-Block hinzugefügt werden:

  switch (docFile.globalState) {
if (db.getLastError()) {
throw new Error(db.getLastError());
}
/* Neuer Code */
if ((0, ou_sp_ptpINV_lib_xtract_1.hasDocumentsIntegratedValidation)() && docFile.globalState === "15") {
docFile.setAttribute("AutoOpenFirstDocument", "false");
}
/* Ende neuer Code */
(0, ou_sp_ptpINV_lib_database_sync_1.syncInvoice)(docFile, db);
db.close();
(0, ou_sp_ptpINV_lib_monitor_1.logInvoiceMonitor)(docFile, false);

ou.spc.ptpINV.workflow.guard.export (INV-617)

Die Validierung des Skriptes wurde ausgelagert in ou.spc.ptpINV.lib.validateExport. Jegliche Customizing-Logik muss fortan auch dort umgesetzt werden.


ou.spc.ptpINV.workflow.guard.exportPreviousMonth (INV-617)

Dieser Guard wurde zusätzlich angelegt um die Buchung auf den Vormonat in den Standard aufzunehmen. Die Validierung des Skriptes wurde ausgelagert in ou.spc.ptpINV.lib.validateExport. Jegliche Customizing-Logik muss fortan auch dort umgesetzt werden.


ou.spc.ptpINV.lib.validateExpor (INV-617)

Dieses Skript enthält die validate-Funktion, die in den beiden Guards aufgerufen wird. Jegliches Customizing wird ab jetzt hier umgesetzt.


ou.spc.ptpINV.workflow.sendsignal.export (INV-617)

Das Skript wurde um einen bookingDate Check in der Funktion executeDirectly erweitert. Dieser prüft, ob bereits ein bookingDate gesetzt wurde. Wenn True, dann wird dieses beibehalten. Wenn False, wird das heutige Datum als bookingDate gesetzt. Außerdem wird der Sync mit der Tabelle invoiceState nun nach dem Buchungsdatum ausgeführt.

Vorher:

function executeDirectly() {
const docFile = context.file;
const {
ptpConnections
} = (0, ou_spc_ptpINV_settings_1.getSettings)();
const db = ptpConnections.getDatabaseConnection("ptpData");
try {
if (db.getLastError()) {
throw new Error(db.getLastError());
}
logger.info("Start export");
ou_sp_ptpINV_lib_database_mapping_1.invoiceState.sync(docFile, db);
if (!docFile.setFieldValue("bookingDate", new Date())) {
throw new Error(`${context.getFromSystemTable("VALIDATION_ERROR_SETFIELDVALUE")} Buchungsdatum. ${docFile.getLastError()}`);
}
if (!docFile.sync()) {
throw new Error(`${context.getFromSystemTable("VALIDATION_ERROR_SYNC")} ${docFile.getLastError()}`);
}
(0, ou_sp_ptpINV_lib_database_sync_1.syncInvoice)(docFile, db);
logger.info("Finished export");
} catch (error) {
logger.error(error);
docFile.xtractCode = `A01\r\n${docFile.xtractCode}`;
docFile.sync();
docFile.insertStatusEntry("Fehler Datenbank", error);
} finally {
db.close();
}
}

Nachher:

function executeDirectly() {
const docFile = context.file;
const {
ptpConnections
} = (0, ou_spc_ptpINV_settings_1.getSettings)();
const db = ptpConnections.getDatabaseConnection("ptpData");
try {
if (db.getLastError()) {
throw new Error(db.getLastError());
}
logger.info("Start export");
const existingBookingDate = docFile.bookingDate;
if (!existingBookingDate) {
const today = new Date();
if (!docFile.setFieldValue("bookingDate", today)) {
throw new Error(`${context.getFromSystemTable("VALIDATION_ERROR_SETFIELDVALUE")} Buchungsdatum. ${docFile.getLastError()}`);
}
}
if (!docFile.sync()) {
throw new Error(`${context.getFromSystemTable("VALIDATION_ERROR_SYNC")} ${docFile.getLastError()}`);
}
ou_sp_ptpINV_lib_database_mapping_1.invoiceState.sync(docFile, db);
(0, ou_sp_ptpINV_lib_database_sync_1.syncInvoice)(docFile, db);
logger.info("Finished export");
} catch (error) {
logger.error(error);
docFile.xtractCode = `A01\r\n${docFile.xtractCode}`;
docFile.sync();
docFile.insertStatusEntry("Fehler Datenbank", error);
} finally {
db.close();
}
}

Documents

Documents-Eigenschaft UseAutoOpenFirstDocumentFromFile

War diese Eigenschaft bisher noch nicht gesetzt, wurde im Update automatisch der Wert "1" gesetzt.

Falls die Eigenschaft vor dem Update allerdings auf "0" gesetzt war, muss diese für die Dexpro-Integration auf "1" gesetzt werden, damit das Register "Datenextraktion" im Status 15 automatisch geöffnet wird.

Falls weiterhin Insiders zur Datenextraktion verwendet wird, kann die Eigenschaft wieder entfernt oder auf "0" gesetzt werden.

Erlaubte Währungen an den Rechnungskreis-Mappen prüfen (INV-595)

Beim Update wird automatisch an allen bereits vorhandenen Rechnungskreis-Mappen EUR als erlaubte & lokale Währung gesetzt (neues Feld Erlaubte Währungen). Sollte diese Einstellung auf einzelne Mappen nicht zutreffen, da für den Rechnungskreis beispielsweise weitere Währungen erlaubt sind, müssen diese entsprechend manuell nachkonfiguriert werden.

Die lokale Währung des Rechnungskreises wird ab sofort in einer neuen Spalte localCurrency in der DB-Tabelle recipient gespeichert (insbesondere relevant für SAP-Kunden, da dies bis jetzt über Customizing gelöst wurde). Die vorherige Konfigurationsmöglichkeit zulässiger Währungen über das Array ptpINVAllowedCurrency in ou.spc.ptpINV.settings.mappingConfig fällt weg, da die Währungsprüfung in ou.sp.ptpINV.workflow.sendsignal.default nun stattdessen über den Rechnungskreis erfolgt.

Mandant

Die Mandanteneigenschaft ptpINVRoundingErrorMargin kann gelöscht werden, die Konfiguration für die Rundungsdifferenz ist nun in der Eigenschaft ptpINVRoundingDifferenceConfig hinterlegt.


Workflow

Im Workflow gibt es nun einen neuen Status 15 (Validierung). Um diesen einzubinden, sind folgende Schritte notwendig:

  1. Nach dem Eingangssignal ou.sp.ptpINV.workflow.receivesignal.checkXtractState muss eine neue Entscheidung gesetzt werden.

  2. Von dieser Entscheidung geht ein Kontrollfluss zum Ausgangssignal WorkflowDefaultScript – für den Kontrollfluss selbst muss das Skript ou.sp.ptpINV.workflow.decision.checkAfterDataExtraction hinterlegt werden.

  3. Der Else-Kontrollfluss geht in den neuen Status 15 (Validierung), der ebenfalls angelegt werden muss und folgendermaßen aussieht:

    Allgemein Fields

  4. Vom Status 15 geht dann wiederum ein Guard-Kontrollfluss mit dem Skript ou.sp.ptpINV.workflow.guard.validated zum Ausgangssignal WorkflowDefaultScript, der so konfiguriert werden muss:

    Guard

Der Workflow sollte nun wie folgt aussehen:

Workflow


Im Workflow gibt es am Status 50 nun einen zusätzlichen Guard-Kontrollfluss mit dem Skript ou.spc.ptpINV.workflow.guard.exportPreviousMonth zum Signalausgang mit dem Skript ou.spc.ptpINV.workflow.sendsignal.export. Dieser erzeugt neben dem bestehenden WF-Button "Buchen" einen zusätzlichen WF-Button "Buchen (Vormonat)", bei dem das bookingDate auf den letzten Tag des vorherigen Monats gesetzt wird.

Zum Erstellen des neuen Guard-Kontrollflusses den bereits bestehenden Kontrollfluss ou.spc.ptpINV.workflow.guard.export kopieren und folgende Anpassungen machen:

  • Bezeichnung: workflow.ptpInvoice.action.exportPreviousMonth
  • Bedingung: runscript:ou.spc.ptpINV.workflow.guard.exportPreviousMonth

Zusätzlich gibt es eine Anpassung am Signalausgang mit dem Skript ou.spc.ptpINV.workflow.sendsignal.export. Dieser prüft nun, ob bereits ein bookingDate gesetzt wurde. Wenn vorhanden, wird dieses übernommen, ansonsten wird das heutige Datum als bookingDate gesetzt.

Workflow