From 0f7627419ac0de192af8eeb1dcb5cae2968437c3 Mon Sep 17 00:00:00 2001 From: Ralph Soika Date: Wed, 7 May 2025 11:37:31 +0200 Subject: [PATCH] neue Code Formatierung --- BPMN-KI-Test/prompt1.txt | 210 +++ BPMN-KI-Test/prompt2.txt | 11 + BPMN-KI-Test/rechnung-ai-test.bpmn | 389 ++++++ BPMN-KI-Test/rechnung-ai-test_plain.bpmn | 207 +++ BPMN-KI-Test/schema-1.txt | 17 + BPMN-KI-Test/schema-2.txt | 46 + doc/zoho/README.md | 25 +- doc/zoho/SECRETS.md | 26 + doc/zoho/disable-auto-generated-number-01.png | Bin 0 -> 32906 bytes doc/zoho/disable-auto-generated-number-02.png | Bin 0 -> 21048 bytes .../AGLAnalyticControllerDebitor.java | 1213 ++++++++--------- .../AGLAnalyticControllerOPListe.java | 278 ++-- .../AGLAnalyticControllerSteuer.java | 968 +++++++------ .../AGLAnalyticExcelAdapter.java | 428 +++--- .../AGLAnalyticExcelAdapterDebitor.java | 70 +- .../AGLConfigController.java | 102 +- .../BusinessPartnerController.java | 860 ++++++------ .../BusinessPartnerSearchEntry.java | 56 +- .../BusinessPartnerService.java | 88 +- .../CargosoftExportAdapter.java | 16 +- .../CargosoftSplitAdapter.java | 22 +- .../CustomSearchController.java | 568 ++++---- .../DebitorStatistikController.java | 1067 +++++++-------- .../DebitorStatistikData.java | 4 +- .../com/alexanderlogistics/FTPConnector.java | 32 +- .../InvoiceAnalyseController.java | 218 +-- .../InvoiceDispatchAdapter.java | 4 +- .../InvoiceDispatchController.java | 4 +- .../InvoiceOutgoingPlugin.java | 46 +- .../com/alexanderlogistics/InvoicePlugin.java | 685 +++++----- .../alexanderlogistics/InvoiceService.java | 56 +- .../com/alexanderlogistics/InvoiceUtil.java | 45 +- .../alexanderlogistics/OPListController.java | 40 +- .../OPListExportAdapter.java | 52 +- .../OPListExportAdapterByWeek.java | 97 +- .../OPListExportController.java | 77 +- .../OPListExportScheduler.java | 38 +- .../RenameFilenamesAdapter.java | 195 ++- .../SEPAExportControllerAGL.java | 8 +- .../SteuerListController.java | 8 +- .../SteuerbescheidPlugin.java | 262 ++-- .../ZahlungsavisAppendAdapter.java | 12 +- .../ZahlungsavisController.java | 4 +- .../ZahlungsavisExportAdapter.java | 16 +- .../ZahlungseingangSaldoAdapter.java | 20 +- .../ZahlungseingangService.java | 44 +- .../ai/PromptExampleAdapterXML.java | 44 +- .../api/CargosoftMigrationRestService.java | 77 +- .../api/DataRestService.java | 46 +- .../datev/CargosoftExportController.java | 74 +- .../datev/CargosoftExportScheduler.java | 36 +- .../mahnlauf/ExcelExportController.java | 481 ++++--- .../mahnlauf/InkassoAppendAdapter.java | 24 +- .../mahnlauf/InkassoController.java | 8 +- .../mahnlauf/InkassoExportAdapter.java | 16 +- .../mahnlauf/MahnlaufExecuteAdapter.java | 75 +- .../mahnlauf/MahnlaufRefAddAdapter.java | 237 ++-- .../mahnlauf/MahnlaufService.java | 44 +- .../xml/BusinessPartnerImportService.java | 57 +- .../xml/CargosoftXMLEAkteImportService.java | 196 ++- .../xml/CargosoftXMLInvoiceImportService.java | 240 ++-- .../zoho/TokenResponse.java | 4 +- .../zoho/ZohoAPIService.java | 576 ++++---- .../zoho/ZohoController.java | 102 +- .../zoho/ZohoExportAdapter.java | 4 +- .../zoho/ZohoOAuthCallbackService.java | 68 +- .../zoho/ZohoOAuthManager.java | 91 +- .../zoho/dto/ZohoInvoiceItem.java | 58 +- .../office/forms/AnalyticController.java | 267 ++-- .../admin/excel_export_rechnungsausgang.xhtml | 40 +- .../src/main/webapp/pages/admin/zoho.xhtml | 38 +- .../java/com/alexanderlogistics/TestFTP.java | 56 +- .../com/alexanderlogistics/TestFormat.java | 6 +- .../TestIMAPAuhtenticators.java | 12 +- .../TestIMAPOutlookOAuth.java | 30 +- .../TestIMAPOutlookOAuthDebug.java | 4 +- .../alexanderlogistics/TestModelMatcher.java | 4 +- .../com/alexanderlogistics/TestOPListen.java | 4 +- .../TestPostionsnummernRegex.java | 24 +- .../com/alexanderlogistics/TestRunden.java | 4 +- .../migration/DueDateBugfix.java | 50 +- .../migration/MigrationMahnerAusziffern.java | 14 +- .../migration/MigrationMahnerDatev.java | 6 +- .../migration/MigrationSpaceZuordnung.java | 8 +- .../rest/TestDataClient.java | 15 +- .../sepa/TestSEPATransform.java | 114 +- .../alexanderlogistics/sepa/XSLTester.java | 106 +- .../xml/EAkteImportTester.java | 130 +- .../xml/InvoiceImportTester.java | 208 +-- .../zoho/TestUpdateTokens.java | 344 +++-- .../com/alexanderlogistics/zoho/test.json | 180 +-- pom.xml | 39 +- workflow/businesspartner-en-1.0.0.bpmn | 47 + 93 files changed, 6864 insertions(+), 6078 deletions(-) create mode 100644 BPMN-KI-Test/prompt1.txt create mode 100644 BPMN-KI-Test/prompt2.txt create mode 100644 BPMN-KI-Test/rechnung-ai-test.bpmn create mode 100644 BPMN-KI-Test/rechnung-ai-test_plain.bpmn create mode 100644 BPMN-KI-Test/schema-1.txt create mode 100644 BPMN-KI-Test/schema-2.txt create mode 100644 doc/zoho/SECRETS.md create mode 100644 doc/zoho/disable-auto-generated-number-01.png create mode 100644 doc/zoho/disable-auto-generated-number-02.png diff --git a/BPMN-KI-Test/prompt1.txt b/BPMN-KI-Test/prompt1.txt new file mode 100644 index 0000000..2474e4b --- /dev/null +++ b/BPMN-KI-Test/prompt1.txt @@ -0,0 +1,210 @@ +Du bist Prozessberater und Spezialisiert auf die Analyse von BPMN Modellen. +Bitte beschreibe den im folgenden BPMN 2.0 Modell definierten Prozess und erstelle eine kurze Zusammenfassung: + + + + + + + + + + + true + + + + + + + + + task_7Y3UUA + event_bH7Wag + event_oNXxgg + event_GEqHhQ + event_VJXKGg + task_SP75zg + gateway_ASbu1A + gateway_Vyo0Zw + task_xAzhzw + event_YH3bnw + event_1qR49w + task_fHgZBg + event_YnFi7A + + + + event_A1J0OA + task_5lS3mw + gateway_Hn5ZhQ + event_dJ89xQ + event_30uUNA + + + + + sequenceFlow_8yitUQ + + + + sequenceFlow_BGVbSA + sequenceFlow_FyAf3g + sequenceFlow_hvQWmg + sequenceFlow_j4GDgg + + + + sequenceFlow_iVpj0g + + + + sequenceFlow_8yitUQ + sequenceFlow_BGVbSA + + + + + + + + + + sequenceFlow_rZXh6g + sequenceFlow_sNlWkQ + + + + sequenceFlow_sNlWkQ + sequenceFlow_nxZ9bA + + + + sequenceFlow_DXGQrw + sequenceFlow_UebylQ + + + + sequenceFlow_FyAf3g + sequenceFlow_rZXh6g + sequenceFlow_i7cH2A + + + + + + + + + + + + + sequenceFlow_i7cH2A + sequenceFlow_DXGQrw + + + + + + + + + + sequenceFlow_nxZ9bA + sequenceFlow_9tp42Q + sequenceFlow_h4SfiQ + + + + sequenceFlow_PLk0RA + sequenceFlow_A0FQoA + + + + sequenceFlow_UebylQ + sequenceFlow_PLk0RA + sequenceFlow_xVEjcA + + + + + + + sequenceFlow_9OrwEg + sequenceFlow_A0FQoA + sequenceFlow_OfKNWA + + + + + + + + + + sequenceFlow_9tp42Q + sequenceFlow_9OrwEg + + + + + + + + + + sequenceFlow_h4SfiQ + sequenceFlow_hvQWmg + + + + sequenceFlow_xVEjcA + sequenceFlow_j4GDgg + + + + + + + + + + + + + + + + + + + sequenceFlow_A0FQoA + sequenceFlow_sUEtcA + sequenceFlow_iVpj0g + + + + sequenceFlow_OfKNWA + sequenceFlow_sUEtcA + + + + + + + + + + + + + \ No newline at end of file diff --git a/BPMN-KI-Test/prompt2.txt b/BPMN-KI-Test/prompt2.txt new file mode 100644 index 0000000..edc985c --- /dev/null +++ b/BPMN-KI-Test/prompt2.txt @@ -0,0 +1,11 @@ +Wir haben einen Geschäftsprozess für die Eingangsrechnungsprüfung in einem Unternehmen. + +Rechnungen werden importiert und dann zur "Rechnungsprüfung" geleitet. Dieser Zusstand ist in einem BPMN 2.0 Diagram als Task Element abgebildet. +Der Mitarbeiter hat nun über die beiden Aktionen "Sachrechnung" und "Cargorechnung" die Möglichkeit die Rechnung zu klassifizieren. Diese beiden Aktionen sind in einem +BPMN 2.0 Diagram als Event Elemente dargestellt. Das Event 'Sachrechnung' führt zum Status (Task) "Budgetprüfung" (die vom Sekretariat durchgeführt wird) +und das Event 'Cargorechnung' führt zum Status 'Prüfung Logistikauftrag' (die vom Fachbereich durchgeführt werden kann). In beiden Zuständen kann nun der Mitarbeiter die Rechnung +über das Event "Freigeben" zur Bezahlung freigeben (Task) oder mit "Ablehnen" zurück an die initiale "Rechnungsprüfung" zurücksenden. Nach der Bezahlung kann der Mitarbeiter +die Rechnung über die Aktion (Event) "Bezahlt" in den finalen Status (Task) 'Archiviert' senden. +In einem BPMN Diagram sind diese Verzweigungen bei den Entscheidungen mit Gateways dargestellt. + +Erstelle für diesen Prozessablauf eine sehr kompakte Darstellungsform, die für ein KI Prompt Template geeignet ist. diff --git a/BPMN-KI-Test/rechnung-ai-test.bpmn b/BPMN-KI-Test/rechnung-ai-test.bpmn new file mode 100644 index 0000000..654d785 --- /dev/null +++ b/BPMN-KI-Test/rechnung-ai-test.bpmn @@ -0,0 +1,389 @@ + + + + + + + + + + true + + + + + + + + + task_7Y3UUA + event_bH7Wag + event_oNXxgg + event_GEqHhQ + event_VJXKGg + task_SP75zg + gateway_ASbu1A + gateway_Vyo0Zw + task_xAzhzw + event_YH3bnw + event_1qR49w + task_fHgZBg + event_YnFi7A + + + + event_A1J0OA + task_5lS3mw + gateway_Hn5ZhQ + event_dJ89xQ + event_30uUNA + + + + + sequenceFlow_8yitUQ + + + + sequenceFlow_BGVbSA + sequenceFlow_FyAf3g + sequenceFlow_hvQWmg + sequenceFlow_j4GDgg + + + + sequenceFlow_iVpj0g + + + + sequenceFlow_8yitUQ + sequenceFlow_BGVbSA + + + + + + + + + + sequenceFlow_rZXh6g + sequenceFlow_sNlWkQ + + + + sequenceFlow_sNlWkQ + sequenceFlow_nxZ9bA + + + + sequenceFlow_DXGQrw + sequenceFlow_UebylQ + + + + sequenceFlow_FyAf3g + sequenceFlow_rZXh6g + sequenceFlow_i7cH2A + + + + + + + + + + + + + sequenceFlow_i7cH2A + sequenceFlow_DXGQrw + + + + + + + + + + sequenceFlow_nxZ9bA + sequenceFlow_9tp42Q + sequenceFlow_h4SfiQ + + + + sequenceFlow_PLk0RA + sequenceFlow_A0FQoA + + + + sequenceFlow_UebylQ + sequenceFlow_PLk0RA + sequenceFlow_xVEjcA + + + + + + + sequenceFlow_9OrwEg + sequenceFlow_A0FQoA + sequenceFlow_OfKNWA + + + + + + + + + + sequenceFlow_9tp42Q + sequenceFlow_9OrwEg + + + + + + + + + + sequenceFlow_h4SfiQ + sequenceFlow_hvQWmg + + + + sequenceFlow_xVEjcA + sequenceFlow_j4GDgg + + + + + + + + + + + + + + + + + + + sequenceFlow_A0FQoA + sequenceFlow_sUEtcA + sequenceFlow_iVpj0g + + + + sequenceFlow_OfKNWA + sequenceFlow_sUEtcA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BPMN-KI-Test/rechnung-ai-test_plain.bpmn b/BPMN-KI-Test/rechnung-ai-test_plain.bpmn new file mode 100644 index 0000000..c143e34 --- /dev/null +++ b/BPMN-KI-Test/rechnung-ai-test_plain.bpmn @@ -0,0 +1,207 @@ + + + + + + + + + + true + + + + + + + + + task_7Y3UUA + event_bH7Wag + event_oNXxgg + event_GEqHhQ + event_VJXKGg + task_SP75zg + gateway_ASbu1A + gateway_Vyo0Zw + task_xAzhzw + event_YH3bnw + event_1qR49w + task_fHgZBg + event_YnFi7A + + + + event_A1J0OA + task_5lS3mw + gateway_Hn5ZhQ + event_dJ89xQ + event_30uUNA + + + + + sequenceFlow_8yitUQ + + + + sequenceFlow_BGVbSA + sequenceFlow_FyAf3g + sequenceFlow_hvQWmg + sequenceFlow_j4GDgg + + + + sequenceFlow_iVpj0g + + + + sequenceFlow_8yitUQ + sequenceFlow_BGVbSA + + + + + + + + + + sequenceFlow_rZXh6g + sequenceFlow_sNlWkQ + + + + sequenceFlow_sNlWkQ + sequenceFlow_nxZ9bA + + + + sequenceFlow_DXGQrw + sequenceFlow_UebylQ + + + + sequenceFlow_FyAf3g + sequenceFlow_rZXh6g + sequenceFlow_i7cH2A + + + + + + + + + + + + + sequenceFlow_i7cH2A + sequenceFlow_DXGQrw + + + + + + + + + + sequenceFlow_nxZ9bA + sequenceFlow_9tp42Q + sequenceFlow_h4SfiQ + + + + sequenceFlow_PLk0RA + sequenceFlow_A0FQoA + + + + sequenceFlow_UebylQ + sequenceFlow_PLk0RA + sequenceFlow_xVEjcA + + + + + + + sequenceFlow_9OrwEg + sequenceFlow_A0FQoA + sequenceFlow_OfKNWA + + + + + + + + + + sequenceFlow_9tp42Q + sequenceFlow_9OrwEg + + + + + + + + + + sequenceFlow_h4SfiQ + sequenceFlow_hvQWmg + + + + sequenceFlow_xVEjcA + sequenceFlow_j4GDgg + + + + + + + + + + + + + + + + + + + sequenceFlow_A0FQoA + sequenceFlow_sUEtcA + sequenceFlow_iVpj0g + + + + sequenceFlow_OfKNWA + sequenceFlow_sUEtcA + + + + + + + + + + + + + \ No newline at end of file diff --git a/BPMN-KI-Test/schema-1.txt b/BPMN-KI-Test/schema-1.txt new file mode 100644 index 0000000..68eab89 --- /dev/null +++ b/BPMN-KI-Test/schema-1.txt @@ -0,0 +1,17 @@ +Prozess: Eingangsrechnungsprüfung + +START -> [Task] Rechnungsprüfung + | + |-- [Event] Sachrechnung --> [Task] Budgetprüfung (Sekretariat) + | | + | |-- [Event] Freigeben --> [Task] Bezahlung + | |-- [Event] Ablehnen --> [Task] Rechnungsprüfung + | + |-- [Event] Cargorechnung --> [Task] Prüfung Logistikauftrag (Fachbereich) + | + |-- [Event] Freigeben --> [Task] Bezahlung + |-- [Event] Ablehnen --> [Task] Rechnungsprüfung + +[Task] Bezahlung + | + |-- [Event] Bezahlt --> [Task] Archiviert (ENDE) \ No newline at end of file diff --git a/BPMN-KI-Test/schema-2.txt b/BPMN-KI-Test/schema-2.txt new file mode 100644 index 0000000..259d973 --- /dev/null +++ b/BPMN-KI-Test/schema-2.txt @@ -0,0 +1,46 @@ +prozess: Eingangsrechnungsprüfung +start_event: Rechnung importiert +tasks: + - name: Rechnungsprüfung + type: user_task + gateway: + name: Klassifikation + type: exclusive + paths: + - event: Sachrechnung + next_task: Budgetprüfung + role: Sekretariat + - event: Cargorechnung + next_task: Prüfung Logistikauftrag + role: Fachbereich + + - name: Budgetprüfung + type: user_task + gateway: + name: Entscheidung + type: exclusive + paths: + - event: Freigeben + next_task: Bezahlung + - event: Ablehnen + next_task: Rechnungsprüfung + + - name: Prüfung Logistikauftrag + type: user_task + gateway: + name: Entscheidung + type: exclusive + paths: + - event: Freigeben + next_task: Bezahlung + - event: Ablehnen + next_task: Rechnungsprüfung + + - name: Bezahlung + type: service_task + event: + name: Bezahlt + next_task: Archiviert + + - name: Archiviert + type: end_event diff --git a/doc/zoho/README.md b/doc/zoho/README.md index bc728ce..2660495 100644 --- a/doc/zoho/README.md +++ b/doc/zoho/README.md @@ -8,9 +8,15 @@ https://api-console.zoho.eu/ https://api-console.zoho.eu/client/1000.NLTM2KUEHI696MSLGEIANATVGGX7IN ``` -OrganizationID=20105697367 -client ID= 1000.NLTM2KUEHI696MSLGEIANATVGGX7IN -client secret= ee9ba49f16e77c64abb89cea13542d2326d8d03ce8 +zoho.accesstoken 1000.18f5fbeaf572425b8062d015d541aad5.a704efa994774ef33d7af75106468a13 +zoho.apidomain https://www.zohoapis.eu +zoho.clientid 1000.GR5FTPZYB06HEWSFSSX4DJU3OBD4EJ +zoho.clientsecret 3f3e74c5212bbc924a7ebf77f887c6296db1106c47 +zoho.code 1000.c184c7592a218fee58f7b7855b56eb77.7b533ff9f570fd6ab3296784caeec072 +zoho.organization 20105697367 +zoho.refreshtoken 1000.98fe4d0c4f676237b400bf8a3dc219f3.03d6bacf5b988d339eb788c4ecff440e +zoho.scope ZohoBooks.contacts.ALL ZohoBooks.invoices.CREATE ZohoBooks.invoices.READ ZohoBooks.invoices.UPDATE + ``` # API Dokumentation @@ -86,3 +92,16 @@ Danach den Code token kopieren: 1000.7fe6315e8848b92a2f281b885677d37a.428c02dc2309606c86271842f1f67de4 **Note:** Der Code muss für Test immer wieder neu erstellt werden, da er nur für einige Minuten gültig ist! + +# Disable auto-generated invoice number + +Um die Automatische Vergabe von Rechnungsnummern in Zoho zu deaktivieren kann wie folgt vorgegangen werden: + +1. Unter 'Invoices' eine neue Rechnung anlegen# +2. Im Feld 'Invoice#' das Zahnrad anklicken: + + + +3. Und dort die Option "Enter invoice numbers manually" auswählen + + diff --git a/doc/zoho/SECRETS.md b/doc/zoho/SECRETS.md new file mode 100644 index 0000000..63761e4 --- /dev/null +++ b/doc/zoho/SECRETS.md @@ -0,0 +1,26 @@ +# Zoho Web API + +AGL Dubai nutzt Zoho als Platform zusammen mit einer lokalen Steuerkanzlei + +## Test/Prod Account Imixs + +https://api-console.zoho.eu/ +https://api-console.zoho.eu/client/1000.NLTM2KUEHI696MSLGEIANATVGGX7IN + +``` +OrganizationID=20105697367 +client ID= 1000.NLTM2KUEHI696MSLGEIANATVGGX7IN +client secret= ee9ba49f16e77c64abb89cea13542d2326d8d03ce8 +``` + +# API Dokumentation + +- API Docu: https://www.zoho.com/books/api/v3/introduction/ +- Authentication: https://www.zoho.com/books/api/v3/oauth/ + +# Testumgebung + +https://books.zoho.eu/app/20105697367 +https://accounts.zoho.eu/home# + +Base API URI= https://accounts.zoho.eu/ diff --git a/doc/zoho/disable-auto-generated-number-01.png b/doc/zoho/disable-auto-generated-number-01.png new file mode 100644 index 0000000000000000000000000000000000000000..676fe5034d2501f11d1c642eb249d368643714b0 GIT binary patch literal 32906 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 literal 0 HcmV?d00001 diff --git a/doc/zoho/disable-auto-generated-number-02.png b/doc/zoho/disable-auto-generated-number-02.png new file mode 100644 index 0000000000000000000000000000000000000000..a2e043d02e1c58207d61de7e354fc2c842bb4d39 GIT binary patch literal 21048 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 literal 0 HcmV?d00001 diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerDebitor.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerDebitor.java index 7cdf274..fd6b866 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerDebitor.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerDebitor.java @@ -34,7 +34,7 @@ import jakarta.inject.Named; /** * Der AGLAnalyticControllerDebitor berechnet verschiedene Analyse Daten von * Ausgangsrechnungen zu einem Business partner - * + * * @author rsoika * */ @@ -42,614 +42,607 @@ import jakarta.inject.Named; @ConversationScoped public class AGLAnalyticControllerDebitor implements Serializable { - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(AGLAnalyticControllerDebitor.class.getName()); - - @Inject - protected DocumentService documentService; - - @Inject - protected AGLConfigController aglConfigController; - - @Inject - protected WorkflowController workflowController; - - @Inject - TeamService teamService; - - TreeMap stats = null; - - List invoices = null; - int countAll = 0; - int countOpen = 0; - int countDue = 0; - int countDunning = 0; - - double totalAllCurrency1 = 0; - double totalAllCurrency2 = 0; - - double totalOpenCurrency1 = 0; - double totalOpenCurrency2 = 0; - - double totalDueCurrency1 = 0; - double totalDueCurrency2 = 0; - - double totalDunningCurrency1 = 0; - double totalDunningCurrency2 = 0; - - double averagePaymentDue = 0; - double averagePaymentDays = 0; - - String chartData = ""; - - public void onEvent(@Observes AnalyticEvent event) { - - if (!event.getWorkitem().getModelVersion().startsWith("businesspartner") - && !event.getWorkitem().getModelVersion().startsWith("analyse-debitor")) { - // no op - return; - } - - String dbtrNumber = event.getWorkitem().getItemValueString("dbtr.number"); - logger.fine("Analytic Event : " + dbtrNumber); - String dbtrNumberLast = event.getWorkitem().getItemValueString("dbtr.number.last"); - - // Recompute only if last dbtr.number has changed or no values yet computed - - // load all invoices? - if (invoices == null || (!dbtrNumber.isEmpty() && !dbtrNumber.equals(dbtrNumberLast))) { - resetStats(event); - event.getWorkitem().setItemValue("dbtr.number.last", dbtrNumber); - logger.info("Analyse new debitor data for : " + dbtrNumber); - loadRechnungen(event); - chartData = buildChartData(); - } - - // use cache? - if (event.getWorkitem().hasItem(event.getKey())) { - // logger.info(" use cache for " + event.getKey()); - // no op - return; - } - - String link = "/pages/workitems/worklist.xhtml" + "?phrase=" + getDbtNr(); - logger.fine("process ref=" + workflowController.getWorkitem().getItemValueString("process.ref")); - ItemCollection process = documentService - .load(workflowController.getWorkitem().getItemValueString("process.ref")); - if (process != null) { - link = link + "&processref=" + process.getUniqueID() - + "&phrase=" + getDbtNr(); - } - if ("analytic.invoices.count.all".equals(event.getKey())) { - event.setValue("" + countAll); - event.setLabel(formatTotals(totalAllCurrency1, totalAllCurrency2)); - event.setDescription("Total invoices"); - event.getWorkitem().setItemValue("invoices.total", event.getValue()); - event.setLink(link); - } - - if ("analytic.invoices.count.open".equals(event.getKey())) { - event.setValue("" + countOpen); - event.setLabel(formatTotals(totalOpenCurrency1, totalOpenCurrency2)); - event.setDescription("Total invoices not yet in due"); - event.getWorkitem().setItemValue("invoices.total.open", event.getValue()); - event.setLink(link); - } - - if ("analytic.invoices.count.due".equals(event.getKey())) { - event.setValue("" + countDue); - event.setLabel(formatTotals(totalDueCurrency1, totalDueCurrency2)); - event.setDescription("Total invoices in due"); - event.getWorkitem().setItemValue("invoices.total.due", event.getValue()); - event.setLink(link); - } - if ("analytic.invoices.count.dunning".equals(event.getKey())) { - event.setValue("" + countDunning); - event.setLabel(formatTotals(totalDunningCurrency1, totalDunningCurrency2)); - event.setDescription("Total invoices in dunning"); - event.getWorkitem().setItemValue("invoices.total.dunning", event.getValue()); - event.setLink(link); - } - - if ("analytic.payment.avg.due".equals(event.getKey())) { - event.setValue("" + averagePaymentDue); - event.setLabel("days"); - event.setDescription("Average terms of credit in the last 12 months."); - event.getWorkitem().setItemValue("payment.avg.due", event.getValue()); - event.setLink(link); - } - if ("analytic.payment.avg.days".equals(event.getKey())) { - event.setValue("" + averagePaymentDays); - event.setLabel("days"); - event.setDescription("Average duration for payment during the last 12 months."); - event.getWorkitem().setItemValue("payment.avg.days", event.getValue()); - event.setLink(link); - } - - if ("analytic.invoices.trend".equals(event.getKey())) { - event.setValue(chartData); - event.setLabel("Payment duration in days"); - event.setDescription("Payment practice by week"); - } - } - - /** - * Formatiert zwei währungen untereinander - * - * @param totalCurrency1 - * @param totalCurrency2 - * @return - */ - private String formatTotals(double totalCurrency1, double totalCurrency2) { - - List currencies = aglConfigController.getWorkitem().getItemValueList("currency.out", String.class); - if (currencies.size() < 2) { - currencies.add(currencies.get(0)); - } - String label = "" + formatCurrency(totalCurrency1) + " " + currencies.get(0) - + ""; - label = label + "
" + - formatCurrency(totalCurrency2) + " " + currencies.get(1) + ""; - - return label; - } - - private void loadRechnungen(AnalyticEvent event) { - if (getDbtNr().isEmpty()) { - invoices = new ArrayList(); - stats = null; - } else { - logger.info(" ├──load invoices for " + getDbtNr() + "...."); - String query = "(type:workitem) AND dbtr.number:" + getDbtNr() - + " AND $modelversion:rechnungsausgang-*"; - try { - logger.info(" ├──refresh invoice stats for " + getDbtNr() + "...."); - invoices = documentService.find(query, 999, 0, "$created", false); - calculateStats(event); - } catch (QueryException e) { - logger.warning("Failed to query invoices: " + query + " - Error: " + e.getMessage()); - invoices = new ArrayList(); - } - } - - } - - /** - * Läd die statistik daten zu einem debitor aus den aktuellen Rechnungen - */ - private void calculateStats(AnalyticEvent event) { - countAll = 0; - countOpen = 0; - countDue = 0; - countDunning = 0; - logger.info(" ├──calculate stats for " + getDbtNr() + "...."); - // do we have data? - if (invoices != null && invoices.size() > 0) { - countAll = invoices.size(); - - for (ItemCollection invoice : invoices) { - - // währung 1 oder 2? - if (invoice.getItemValueDouble("invoice.rate") == 0) { - totalAllCurrency1 = totalAllCurrency1 + invoice.getItemValueDouble("invoice.saldo"); - } else { - totalAllCurrency2 = totalAllCurrency2 + invoice.getItemValueDouble("invoice.saldo"); - } - - int task = invoice.getTaskID(); - - // Open - if (task >= 5000 && task <= 5099) { - countOpen++; - // währung 1 oder 2? - if (invoice.getItemValueDouble("invoice.rate") == 0) { - totalOpenCurrency1 = totalOpenCurrency1 + invoice.getItemValueDouble("invoice.saldo"); - } else { - totalOpenCurrency2 = totalOpenCurrency2 + invoice.getItemValueDouble("invoice.saldo"); - } - } - - // Due - if (task >= 5100 && task <= 5199) { - countDue++; - // währung 1 oder 2? - if (invoice.getItemValueDouble("invoice.rate") == 0) { - totalDueCurrency1 = totalDueCurrency1 + invoice.getItemValueDouble("invoice.saldo"); - } else { - totalDueCurrency2 = totalDueCurrency2 + invoice.getItemValueDouble("invoice.saldo"); - } - } - - // Dunning - if (task >= 5200) { - countDunning++; - // währung 1 oder 2? - if (invoice.getItemValueDouble("invoice.rate") == 0) { - totalDunningCurrency1 = totalDunningCurrency1 + invoice.getItemValueDouble("invoice.saldo"); - } else { - totalDunningCurrency2 = totalDunningCurrency2 + invoice.getItemValueDouble("invoice.saldo"); - } - } - } - - totalAllCurrency1 = InvoiceUtil.round(totalAllCurrency1); - totalAllCurrency2 = InvoiceUtil.round(totalAllCurrency2); - totalOpenCurrency1 = InvoiceUtil.round(totalOpenCurrency1); - totalOpenCurrency2 = InvoiceUtil.round(totalOpenCurrency2); - totalDueCurrency1 = InvoiceUtil.round(totalDueCurrency1); - totalDueCurrency2 = InvoiceUtil.round(totalDueCurrency2); - totalDunningCurrency1 = InvoiceUtil.round(totalDunningCurrency1); - totalDunningCurrency2 = InvoiceUtil.round(totalDunningCurrency2); - - } else { - // reset data because no invoices are available ... - resetStats(event); - - } - - } - - /** - * Setzt alle Statistikwerte zurück - */ - private void resetStats(AnalyticEvent event) { - logger.info(" ├──reset stats for " + getDbtNr() + "...."); - stats = null; - - totalAllCurrency1 = 0; - totalAllCurrency2 = 0; - totalOpenCurrency1 = 0; - totalOpenCurrency2 = 0; - totalDueCurrency1 = 0; - totalDueCurrency2 = 0; - totalDunningCurrency1 = 0; - totalDunningCurrency2 = 0; - - // chartData = null; - chartData = "{}"; - - averagePaymentDue = 0; - averagePaymentDays = 0; - - event.getWorkitem().removeItem("analytic.invoices.count.all"); - event.getWorkitem().removeItem("analytic.invoices.count.open"); - event.getWorkitem().removeItem("analytic.invoices.count.due"); - event.getWorkitem().removeItem("analytic.invoices.count.dunning"); - - event.getWorkitem().removeItem("analytic.payment.avg.due"); - event.getWorkitem().removeItem("analytic.payment.avg.days"); - event.getWorkitem().removeItem("analytic.invoices.trend"); - - } - - private void getDurchschnittZahlungsziel() { - if (stats == null) { - averagePaymentDue = 0; - return; - } - int count = 0; - long gesamt = 0; - for (Map.Entry entry : stats.entrySet()) { - DebitorStatistikData statData = entry.getValue(); - if (statData.count > 0) { - gesamt = gesamt + statData.getAverageDueDays(); - count++; - } - } - averagePaymentDue = Math.round((double) gesamt / count); - } - - private void getDurchschnittZahlungsdauer() { - if (stats == null) { - averagePaymentDays = 0; - return; - } - int count = 0; - long gesamt = 0; - for (Map.Entry entry : stats.entrySet()) { - DebitorStatistikData statData = entry.getValue(); - if (statData.count > 0) { - gesamt = gesamt + statData.getAveragePaymentDays(); - count++; - } - } - averagePaymentDays = Math.round((double) gesamt / count); - } - - private String formatCurrency(Double value) { - - DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.getDefault()); - symbols.setGroupingSeparator('.'); - symbols.setDecimalSeparator(','); - DecimalFormat formatter = new DecimalFormat("#,##0.00", symbols); - - return formatter.format(value); - } - - /** - * Hilfsmethode die das fuehrende K/D aus der Debitorennummer entfernt - * - * @return - */ - private String getDbtNr() { - String dbtNr = workflowController.getWorkitem().getItemValueString("dbtr.number"); - if (dbtNr.startsWith("D") || dbtNr.startsWith("K")) { - dbtNr = dbtNr.substring(1); - } - return dbtNr; - } - - /** - * Sucht alle Rechnugnen aus einem Zeitraum und sammelt Zahlungsziel und - * Zahlungszeitspanne gruppiert nach monaten - * - */ - public Map loadStats() { - - if (stats != null) { - return stats; - } - - logger.info(" ├──load stats for " + getDbtNr() + "...."); - // recompute - stats = new TreeMap(); - if (getDbtNr().isEmpty()) { - return stats; - } - - // Letzen 12 Monate - LocalDate endDate = LocalDate.now(); - LocalDate startDate = endDate.minusMonths(12); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); - String startDateStr = startDate.format(formatter); - String endDateStr = endDate.format(formatter); - - String query = "(type:workitemarchive) AND " - + " invoice.date:[" + startDateStr + " TO " + endDateStr + "] AND " - + "dbtr.number:" + getDbtNr() - + " AND $modelversion:rechnungsausgang-* AND ($taskid:[5900 TO 5999])"; - - logger.fine("query = " + query); - try { - List invoices = documentService - .findStubs(query, 9999, 0, "$created", false); - - for (ItemCollection invoice : invoices) { - try { - - logger.fine("Rechnung: " + invoice.getUniqueID()); - Date invoiceDate = invoice.getItemValueDate("invoice.date"); - Date invoiceDueDate = invoice.getItemValueDate("invoice.duedate"); - Date paymentDate = findPaymentDateByWorkitem(invoice); - if (paymentDate == null) { - logger.fine("Es wurde kein Zahlungseingang gefunden"); - continue; - } - - if (invoiceDate != null && paymentDate != null && invoiceDueDate != null) { - // do we have a stats object? - DebitorStatistikData statData = stats.get(getYearMonth(invoiceDate)); - if (statData == null) { - // create a new one - statData = new DebitorStatistikData(invoiceDate); - } - // jetzt irgendwas ausrechnen - statData.compute(invoiceDate, invoiceDueDate, paymentDate); - // .. und wieder speichern - stats.put(statData.toString(), statData); - - } else { - logger.warning("No payment Date found for invoice " + invoice.getUniqueID()); - } - - } catch (ParseException e) { - logger.warning("Unable to parse payment Date for invoice " + invoice.getUniqueID()); - } - } - - } catch (QueryException e) { - logger.severe("Failed to get statistic: " + e.getMessage()); - } - // Step 2: Sort the list based on the alphanumeric order of the keys - completeMissingMonths(); - - // berechne zahluntsziel durchscnitt - getDurchschnittZahlungsziel(); - getDurchschnittZahlungsdauer(); - return stats; - - } - - /** - * Finds the payment.date for a invoice. - * - * Wir suchen alle zugeordneten Zahlungseingägne und nehmen den letzten. - * - * WICHTIG: Es muss ggf. der index neu aufgebaut werden, da payment.date nun ein - * index feld ist - * - * @param invoice - * @return - * @throws ParseException - */ - public Date findPaymentDateByWorkitem(ItemCollection invoice) throws ParseException { - - // Wir selektieren alle Zahlungseingänge interessieren uns aber nur für den - // letzten - String sQuery = " (type:\"workitem\" OR type:\"workitemarchive\") " + // - " AND ($modelversion:zahlungseingang-*) AND ($workitemref:\"" - + invoice.getUniqueID() + "\" )"; - - List workitems = null; - - try { - workitems = documentService.findStubs(sQuery, 99, 0, - "payment.date", true); - if (workitems.size() > 0) { - return workitems.get(0).getItemValueDate("payment.date"); - } - } catch (QueryException e) { - - e.printStackTrace(); - } - - // no date found! - return null; - - } - - /** - * Diese Methode baut die Datenstruktur für das Chart Diagram zusammen - * - * - *
-		{
-		 labels: ["January", "February", "March", "April", "May", "June", "July"],
-		 datasets: [{
-			label: 'Dataset 1',
-			//backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
-			//borderColor: window.chartColors.red,
-			borderWidth: 1,
-			data: [
-				70, 70, 70, 70, 79, 50, 50
-			]
-		}, {
-			label: 'Dataset 2',
-			//backgroundColor: color(window.chartColors.blue).alpha(0.5).rgbString(),
-			//borderColor: window.chartColors.blue,
-			borderWidth: 1,
-			data: [
-				70, 70, 170, 7, 79, 50, 50
-			]
-		}]
-	   }
-	 * 
- * - * @return - */ - public String buildChartData() { - - loadStats(); - logger.info(" ├──build chart for " + getDbtNr() + "...."); - // build a list of all lables.... - List statusLabels = new ArrayList(); - - Set keys = stats.keySet(); - for (String _key : keys) { - statusLabels.add(_key); - } - - String result = "{\n \"type\": \"bar\",\n \"data\": { "; - - // Lables - result = result + "\"labels\" : [ "; - result = result + statusLabels.stream().collect(Collectors.joining("\",\"", "\"", "\"")); - result = result + "],"; - result = result + "\"datasets\": ["; - - // Datasets 1 - result = result + "{\"label\": \"Days for payment\",\"borderWidth\": 1,"; - result = result + " \"borderColor\": [\"#3B6B82\"],"; - result = result + " \"backgroundColor\" : [\"#CFE9F5\"], \"fill\": true,\"tension\": 0.5,"; - - result = result + "\"data\": ["; - for (Map.Entry entry : stats.entrySet()) { - result = result + entry.getValue().getAverageDueDays() + ","; - } - // cut last comma - if (result.endsWith(",")) { - result = result.substring(0, result.length() - 1); - } - result = result + "]"; - result = result + "}, "; - - // Datasets 2 - result = result + "{\"label\": \"Payment duration\",\"borderWidth\": 1,"; - result = result - + " \"borderColor\": [\"#E73B65\"],\"backgroundColor\" : [\"#70B088\" ], \"tension\": 0.5,\"fill\": true,"; - - result = result - + " \"trendlineLinear\": { \"colorMin\": \"red\", \"colorMax\": \"green\", \"lineStyle\": \"dotted\", \"width\": 2 , \"projection\": true },"; - - result = result + "\"data\": ["; - for (Map.Entry entry : stats.entrySet()) { - result = result + entry.getValue().getAveragePaymentDays() + ","; - } - // cut last comma - if (result.endsWith(",")) { - result = result.substring(0, result.length() - 1); - } - result = result + "]"; - result = result + "} "; - - // ende - result = result + "] }"; - - result = result + "}"; - - return result; - } - - /** - * Hilfsmethode ergänzt die fehlenden Monate - * - * @param yearMonths - * @return - */ - private void completeMissingMonths() { - if (stats == null || stats.size() < 3) { - return; - } - - // Convert the list of strings to a list of LocalDate objects - List dates = new ArrayList<>(); - Set yearMonths = this.stats.keySet(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM"); - for (String yearMonth : yearMonths) { - dates.add(YearMonth.parse(yearMonth, formatter).atDay(1)); - } - - // Find the lowest and highest dates - LocalDate lowestDate = Collections.min(dates); - LocalDate highestDate = Collections.max(dates); - - LocalDate current = lowestDate; - current = current.plusMonths(1); - while (!current.isAfter(highestDate)) { - - // existiert der monat? - String formattedDate = current.format(formatter); - DebitorStatistikData entry = stats.get(formattedDate); - if (entry == null) { - // add missing entry - stats.put(formattedDate, new DebitorStatistikData(current.getYear(), current.getMonthValue())); - } - current = current.plusMonths(1); - - } - - } - - /** - * returns - * - * 202304 from a given date - */ - public String getYearMonth(Date date) { - - // Create a Calendar instance and set the date - Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - - // Get the year from the Calendar object - int year = calendar.get(Calendar.YEAR); - // Get the month from the Calendar object - int month = calendar.get(Calendar.MONTH); - // Increment the month by 1 since Calendar months are zero-based - month++; - // Convert the month to a String with leading "0" if necessary - return "" + year + (month < 10 ? "0" + month : "" + month); - - } + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(AGLAnalyticControllerDebitor.class.getName()); + + @Inject + protected DocumentService documentService; + + @Inject + protected AGLConfigController aglConfigController; + + @Inject + protected WorkflowController workflowController; + + @Inject + TeamService teamService; + + TreeMap stats = null; + + List invoices = null; + int countAll = 0; + int countOpen = 0; + int countDue = 0; + int countDunning = 0; + + double totalAllCurrency1 = 0; + double totalAllCurrency2 = 0; + + double totalOpenCurrency1 = 0; + double totalOpenCurrency2 = 0; + + double totalDueCurrency1 = 0; + double totalDueCurrency2 = 0; + + double totalDunningCurrency1 = 0; + double totalDunningCurrency2 = 0; + + double averagePaymentDue = 0; + double averagePaymentDays = 0; + + String chartData = ""; + + public void onEvent(@Observes AnalyticEvent event) { + + if (!event.getWorkitem().getModelVersion().startsWith("businesspartner") + && !event.getWorkitem().getModelVersion().startsWith("analyse-debitor")) { + // no op + return; + } + + String dbtrNumber = event.getWorkitem().getItemValueString("dbtr.number"); + logger.fine("Analytic Event : " + dbtrNumber); + String dbtrNumberLast = event.getWorkitem().getItemValueString("dbtr.number.last"); + + // Recompute only if last dbtr.number has changed or no values yet computed + + // load all invoices? + if (invoices == null || (!dbtrNumber.isEmpty() && !dbtrNumber.equals(dbtrNumberLast))) { + resetStats(event); + event.getWorkitem().setItemValue("dbtr.number.last", dbtrNumber); + logger.info("Analyse new debitor data for : " + dbtrNumber); + loadRechnungen(event); + chartData = buildChartData(); + } + + // use cache? + if (event.getWorkitem().hasItem(event.getKey())) { + // logger.info(" use cache for " + event.getKey()); + // no op + return; + } + + String link = "/pages/workitems/worklist.xhtml" + "?phrase=" + getDbtNr(); + logger.fine("process ref=" + workflowController.getWorkitem().getItemValueString("process.ref")); + ItemCollection process = documentService + .load(workflowController.getWorkitem().getItemValueString("process.ref")); + if (process != null) { + link = link + "&processref=" + process.getUniqueID() + "&phrase=" + getDbtNr(); + } + if ("analytic.invoices.count.all".equals(event.getKey())) { + event.setValue("" + countAll); + event.setLabel(formatTotals(totalAllCurrency1, totalAllCurrency2)); + event.setDescription("Total invoices"); + event.getWorkitem().setItemValue("invoices.total", event.getValue()); + event.setLink(link); + } + + if ("analytic.invoices.count.open".equals(event.getKey())) { + event.setValue("" + countOpen); + event.setLabel(formatTotals(totalOpenCurrency1, totalOpenCurrency2)); + event.setDescription("Total invoices not yet in due"); + event.getWorkitem().setItemValue("invoices.total.open", event.getValue()); + event.setLink(link); + } + + if ("analytic.invoices.count.due".equals(event.getKey())) { + event.setValue("" + countDue); + event.setLabel(formatTotals(totalDueCurrency1, totalDueCurrency2)); + event.setDescription("Total invoices in due"); + event.getWorkitem().setItemValue("invoices.total.due", event.getValue()); + event.setLink(link); + } + if ("analytic.invoices.count.dunning".equals(event.getKey())) { + event.setValue("" + countDunning); + event.setLabel(formatTotals(totalDunningCurrency1, totalDunningCurrency2)); + event.setDescription("Total invoices in dunning"); + event.getWorkitem().setItemValue("invoices.total.dunning", event.getValue()); + event.setLink(link); + } + + if ("analytic.payment.avg.due".equals(event.getKey())) { + event.setValue("" + averagePaymentDue); + event.setLabel("days"); + event.setDescription("Average terms of credit in the last 12 months."); + event.getWorkitem().setItemValue("payment.avg.due", event.getValue()); + event.setLink(link); + } + if ("analytic.payment.avg.days".equals(event.getKey())) { + event.setValue("" + averagePaymentDays); + event.setLabel("days"); + event.setDescription("Average duration for payment during the last 12 months."); + event.getWorkitem().setItemValue("payment.avg.days", event.getValue()); + event.setLink(link); + } + + if ("analytic.invoices.trend".equals(event.getKey())) { + event.setValue(chartData); + event.setLabel("Payment duration in days"); + event.setDescription("Payment practice by week"); + } + } + + /** + * Formatiert zwei währungen untereinander + * + * @param totalCurrency1 + * @param totalCurrency2 + * @return + */ + private String formatTotals(double totalCurrency1, double totalCurrency2) { + + List currencies = aglConfigController.getWorkitem().getItemValueList("currency.out", String.class); + if (currencies.size() < 2) { + currencies.add(currencies.get(0)); + } + String label = "" + formatCurrency(totalCurrency1) + " " + currencies.get(0) + + ""; + label = label + "
" + formatCurrency(totalCurrency2) + " " + currencies.get(1) + + ""; + + return label; + } + + private void loadRechnungen(AnalyticEvent event) { + if (getDbtNr().isEmpty()) { + invoices = new ArrayList(); + stats = null; + } else { + logger.info(" ├──load invoices for " + getDbtNr() + "...."); + String query = "(type:workitem) AND dbtr.number:" + getDbtNr() + " AND $modelversion:rechnungsausgang-*"; + try { + logger.info(" ├──refresh invoice stats for " + getDbtNr() + "...."); + invoices = documentService.find(query, 999, 0, "$created", false); + calculateStats(event); + } catch (QueryException e) { + logger.warning("Failed to query invoices: " + query + " - Error: " + e.getMessage()); + invoices = new ArrayList(); + } + } + + } + + /** + * Läd die statistik daten zu einem debitor aus den aktuellen Rechnungen + */ + private void calculateStats(AnalyticEvent event) { + countAll = 0; + countOpen = 0; + countDue = 0; + countDunning = 0; + logger.info(" ├──calculate stats for " + getDbtNr() + "...."); + // do we have data? + if (invoices != null && invoices.size() > 0) { + countAll = invoices.size(); + + for (ItemCollection invoice : invoices) { + + // währung 1 oder 2? + if (invoice.getItemValueDouble("invoice.rate") == 0) { + totalAllCurrency1 = totalAllCurrency1 + invoice.getItemValueDouble("invoice.saldo"); + } else { + totalAllCurrency2 = totalAllCurrency2 + invoice.getItemValueDouble("invoice.saldo"); + } + + int task = invoice.getTaskID(); + + // Open + if (task >= 5000 && task <= 5099) { + countOpen++; + // währung 1 oder 2? + if (invoice.getItemValueDouble("invoice.rate") == 0) { + totalOpenCurrency1 = totalOpenCurrency1 + invoice.getItemValueDouble("invoice.saldo"); + } else { + totalOpenCurrency2 = totalOpenCurrency2 + invoice.getItemValueDouble("invoice.saldo"); + } + } + + // Due + if (task >= 5100 && task <= 5199) { + countDue++; + // währung 1 oder 2? + if (invoice.getItemValueDouble("invoice.rate") == 0) { + totalDueCurrency1 = totalDueCurrency1 + invoice.getItemValueDouble("invoice.saldo"); + } else { + totalDueCurrency2 = totalDueCurrency2 + invoice.getItemValueDouble("invoice.saldo"); + } + } + + // Dunning + if (task >= 5200) { + countDunning++; + // währung 1 oder 2? + if (invoice.getItemValueDouble("invoice.rate") == 0) { + totalDunningCurrency1 = totalDunningCurrency1 + invoice.getItemValueDouble("invoice.saldo"); + } else { + totalDunningCurrency2 = totalDunningCurrency2 + invoice.getItemValueDouble("invoice.saldo"); + } + } + } + + totalAllCurrency1 = InvoiceUtil.round(totalAllCurrency1); + totalAllCurrency2 = InvoiceUtil.round(totalAllCurrency2); + totalOpenCurrency1 = InvoiceUtil.round(totalOpenCurrency1); + totalOpenCurrency2 = InvoiceUtil.round(totalOpenCurrency2); + totalDueCurrency1 = InvoiceUtil.round(totalDueCurrency1); + totalDueCurrency2 = InvoiceUtil.round(totalDueCurrency2); + totalDunningCurrency1 = InvoiceUtil.round(totalDunningCurrency1); + totalDunningCurrency2 = InvoiceUtil.round(totalDunningCurrency2); + + } else { + // reset data because no invoices are available ... + resetStats(event); + + } + + } + + /** + * Setzt alle Statistikwerte zurück + */ + private void resetStats(AnalyticEvent event) { + logger.info(" ├──reset stats for " + getDbtNr() + "...."); + stats = null; + + totalAllCurrency1 = 0; + totalAllCurrency2 = 0; + totalOpenCurrency1 = 0; + totalOpenCurrency2 = 0; + totalDueCurrency1 = 0; + totalDueCurrency2 = 0; + totalDunningCurrency1 = 0; + totalDunningCurrency2 = 0; + + // chartData = null; + chartData = "{}"; + + averagePaymentDue = 0; + averagePaymentDays = 0; + + event.getWorkitem().removeItem("analytic.invoices.count.all"); + event.getWorkitem().removeItem("analytic.invoices.count.open"); + event.getWorkitem().removeItem("analytic.invoices.count.due"); + event.getWorkitem().removeItem("analytic.invoices.count.dunning"); + + event.getWorkitem().removeItem("analytic.payment.avg.due"); + event.getWorkitem().removeItem("analytic.payment.avg.days"); + event.getWorkitem().removeItem("analytic.invoices.trend"); + + } + + private void getDurchschnittZahlungsziel() { + if (stats == null) { + averagePaymentDue = 0; + return; + } + int count = 0; + long gesamt = 0; + for (Map.Entry entry : stats.entrySet()) { + DebitorStatistikData statData = entry.getValue(); + if (statData.count > 0) { + gesamt = gesamt + statData.getAverageDueDays(); + count++; + } + } + averagePaymentDue = Math.round((double) gesamt / count); + } + + private void getDurchschnittZahlungsdauer() { + if (stats == null) { + averagePaymentDays = 0; + return; + } + int count = 0; + long gesamt = 0; + for (Map.Entry entry : stats.entrySet()) { + DebitorStatistikData statData = entry.getValue(); + if (statData.count > 0) { + gesamt = gesamt + statData.getAveragePaymentDays(); + count++; + } + } + averagePaymentDays = Math.round((double) gesamt / count); + } + + private String formatCurrency(Double value) { + + DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.getDefault()); + symbols.setGroupingSeparator('.'); + symbols.setDecimalSeparator(','); + DecimalFormat formatter = new DecimalFormat("#,##0.00", symbols); + + return formatter.format(value); + } + + /** + * Hilfsmethode die das fuehrende K/D aus der Debitorennummer entfernt + * + * @return + */ + private String getDbtNr() { + String dbtNr = workflowController.getWorkitem().getItemValueString("dbtr.number"); + if (dbtNr.startsWith("D") || dbtNr.startsWith("K")) { + dbtNr = dbtNr.substring(1); + } + return dbtNr; + } + + /** + * Sucht alle Rechnugnen aus einem Zeitraum und sammelt Zahlungsziel und + * Zahlungszeitspanne gruppiert nach monaten + * + */ + public Map loadStats() { + + if (stats != null) { + return stats; + } + + logger.info(" ├──load stats for " + getDbtNr() + "...."); + // recompute + stats = new TreeMap(); + if (getDbtNr().isEmpty()) { + return stats; + } + + // Letzen 12 Monate + LocalDate endDate = LocalDate.now(); + LocalDate startDate = endDate.minusMonths(12); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); + String startDateStr = startDate.format(formatter); + String endDateStr = endDate.format(formatter); + + String query = "(type:workitemarchive) AND " + " invoice.date:[" + startDateStr + " TO " + endDateStr + "] AND " + + "dbtr.number:" + getDbtNr() + " AND $modelversion:rechnungsausgang-* AND ($taskid:[5900 TO 5999])"; + + logger.fine("query = " + query); + try { + List invoices = documentService.findStubs(query, 9999, 0, "$created", false); + + for (ItemCollection invoice : invoices) { + try { + + logger.fine("Rechnung: " + invoice.getUniqueID()); + Date invoiceDate = invoice.getItemValueDate("invoice.date"); + Date invoiceDueDate = invoice.getItemValueDate("invoice.duedate"); + Date paymentDate = findPaymentDateByWorkitem(invoice); + if (paymentDate == null) { + logger.fine("Es wurde kein Zahlungseingang gefunden"); + continue; + } + + if (invoiceDate != null && paymentDate != null && invoiceDueDate != null) { + // do we have a stats object? + DebitorStatistikData statData = stats.get(getYearMonth(invoiceDate)); + if (statData == null) { + // create a new one + statData = new DebitorStatistikData(invoiceDate); + } + // jetzt irgendwas ausrechnen + statData.compute(invoiceDate, invoiceDueDate, paymentDate); + // .. und wieder speichern + stats.put(statData.toString(), statData); + + } else { + logger.warning("No payment Date found for invoice " + invoice.getUniqueID()); + } + + } catch (ParseException e) { + logger.warning("Unable to parse payment Date for invoice " + invoice.getUniqueID()); + } + } + + } catch (QueryException e) { + logger.severe("Failed to get statistic: " + e.getMessage()); + } + // Step 2: Sort the list based on the alphanumeric order of the keys + completeMissingMonths(); + + // berechne zahluntsziel durchscnitt + getDurchschnittZahlungsziel(); + getDurchschnittZahlungsdauer(); + return stats; + + } + + /** + * Finds the payment.date for a invoice. + * + * Wir suchen alle zugeordneten Zahlungseingägne und nehmen den letzten. + * + * WICHTIG: Es muss ggf. der index neu aufgebaut werden, da payment.date nun ein + * index feld ist + * + * @param invoice + * @return + * @throws ParseException + */ + public Date findPaymentDateByWorkitem(ItemCollection invoice) throws ParseException { + + // Wir selektieren alle Zahlungseingänge interessieren uns aber nur für den + // letzten + String sQuery = " (type:\"workitem\" OR type:\"workitemarchive\") " + // + " AND ($modelversion:zahlungseingang-*) AND ($workitemref:\"" + invoice.getUniqueID() + "\" )"; + + List workitems = null; + + try { + workitems = documentService.findStubs(sQuery, 99, 0, "payment.date", true); + if (workitems.size() > 0) { + return workitems.get(0).getItemValueDate("payment.date"); + } + } catch (QueryException e) { + + e.printStackTrace(); + } + + // no date found! + return null; + + } + + /** + * Diese Methode baut die Datenstruktur für das Chart Diagram zusammen + * + * + *
+    	{
+    	 labels: ["January", "February", "March", "April", "May", "June", "July"],
+    	 datasets: [{
+    		label: 'Dataset 1',
+    		//backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
+    		//borderColor: window.chartColors.red,
+    		borderWidth: 1,
+    		data: [
+    			70, 70, 70, 70, 79, 50, 50
+    		]
+    	}, {
+    		label: 'Dataset 2',
+    		//backgroundColor: color(window.chartColors.blue).alpha(0.5).rgbString(),
+    		//borderColor: window.chartColors.blue,
+    		borderWidth: 1,
+    		data: [
+    			70, 70, 170, 7, 79, 50, 50
+    		]
+    	}]
+       }
+     * 
+ * + * @return + */ + public String buildChartData() { + + loadStats(); + logger.info(" ├──build chart for " + getDbtNr() + "...."); + // build a list of all lables.... + List statusLabels = new ArrayList(); + + Set keys = stats.keySet(); + for (String _key : keys) { + statusLabels.add(_key); + } + + String result = "{\n \"type\": \"bar\",\n \"data\": { "; + + // Lables + result = result + "\"labels\" : [ "; + result = result + statusLabels.stream().collect(Collectors.joining("\",\"", "\"", "\"")); + result = result + "],"; + result = result + "\"datasets\": ["; + + // Datasets 1 + result = result + "{\"label\": \"Days for payment\",\"borderWidth\": 1,"; + result = result + " \"borderColor\": [\"#3B6B82\"],"; + result = result + " \"backgroundColor\" : [\"#CFE9F5\"], \"fill\": true,\"tension\": 0.5,"; + + result = result + "\"data\": ["; + for (Map.Entry entry : stats.entrySet()) { + result = result + entry.getValue().getAverageDueDays() + ","; + } + // cut last comma + if (result.endsWith(",")) { + result = result.substring(0, result.length() - 1); + } + result = result + "]"; + result = result + "}, "; + + // Datasets 2 + result = result + "{\"label\": \"Payment duration\",\"borderWidth\": 1,"; + result = result + + " \"borderColor\": [\"#E73B65\"],\"backgroundColor\" : [\"#70B088\" ], \"tension\": 0.5,\"fill\": true,"; + + result = result + + " \"trendlineLinear\": { \"colorMin\": \"red\", \"colorMax\": \"green\", \"lineStyle\": \"dotted\", \"width\": 2 , \"projection\": true },"; + + result = result + "\"data\": ["; + for (Map.Entry entry : stats.entrySet()) { + result = result + entry.getValue().getAveragePaymentDays() + ","; + } + // cut last comma + if (result.endsWith(",")) { + result = result.substring(0, result.length() - 1); + } + result = result + "]"; + result = result + "} "; + + // ende + result = result + "] }"; + + result = result + "}"; + + return result; + } + + /** + * Hilfsmethode ergänzt die fehlenden Monate + * + * @param yearMonths + * @return + */ + private void completeMissingMonths() { + if (stats == null || stats.size() < 3) { + return; + } + + // Convert the list of strings to a list of LocalDate objects + List dates = new ArrayList<>(); + Set yearMonths = this.stats.keySet(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM"); + for (String yearMonth : yearMonths) { + dates.add(YearMonth.parse(yearMonth, formatter).atDay(1)); + } + + // Find the lowest and highest dates + LocalDate lowestDate = Collections.min(dates); + LocalDate highestDate = Collections.max(dates); + + LocalDate current = lowestDate; + current = current.plusMonths(1); + while (!current.isAfter(highestDate)) { + + // existiert der monat? + String formattedDate = current.format(formatter); + DebitorStatistikData entry = stats.get(formattedDate); + if (entry == null) { + // add missing entry + stats.put(formattedDate, new DebitorStatistikData(current.getYear(), current.getMonthValue())); + } + current = current.plusMonths(1); + + } + + } + + /** + * returns + * + * 202304 from a given date + */ + public String getYearMonth(Date date) { + + // Create a Calendar instance and set the date + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + + // Get the year from the Calendar object + int year = calendar.get(Calendar.YEAR); + // Get the month from the Calendar object + int month = calendar.get(Calendar.MONTH); + // Increment the month by 1 since Calendar months are zero-based + month++; + // Convert the month to a String with leading "0" if necessary + return "" + year + (month < 10 ? "0" + month : "" + month); + + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerOPListe.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerOPListe.java index d34c954..f918881 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerOPListe.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerOPListe.java @@ -23,9 +23,8 @@ import jakarta.inject.Named; /** * Der AGLAnalyticControllerOPListe berechnet Analyse Daten über alle offenen - * Rechnungen - * und bietet im Workflow noch eine Excel-Export Funktion - * + * Rechnungen und bietet im Workflow noch eine Excel-Export Funktion + * * @author rsoika * */ @@ -33,176 +32,173 @@ import jakarta.inject.Named; @ConversationScoped public class AGLAnalyticControllerOPListe implements Serializable { - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(AnalyticController.class.getName()); + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(AnalyticController.class.getName()); - @Inject - protected DocumentService documentService; + @Inject + protected DocumentService documentService; - @Inject - protected AGLConfigController aglConfigController; + @Inject + protected AGLConfigController aglConfigController; - @Inject - protected WorkflowController workflowController; + @Inject + protected WorkflowController workflowController; - @Inject - TeamService teamService; + @Inject + TeamService teamService; - @Inject - ResourceBundleHandler resourceBundleHandler; + @Inject + ResourceBundleHandler resourceBundleHandler; - TreeMap stats = null; + TreeMap stats = null; - String departmentID; - String departmentIDLast = null; - // String departmentName; - ItemCollection department; + String departmentID; + String departmentIDLast = null; + // String departmentName; + ItemCollection department; - int countAll = 0; - int countOpen = 0; - int countDue = 0; - int countDunning = 0; - boolean refreshCalculation = false; + int countAll = 0; + int countOpen = 0; + int countDue = 0; + int countDunning = 0; + boolean refreshCalculation = false; - public void onEvent(@Observes AnalyticEvent event) { + public void onEvent(@Observes AnalyticEvent event) { - if (!event.getWorkitem().getModelVersion().contains("analyse-opliste-")) { - // no op - return; - } + if (!event.getWorkitem().getModelVersion().contains("analyse-opliste-")) { + // no op + return; + } - department = null; - departmentID = event.getWorkitem().getItemValueString("space.ref"); - if (departmentIDLast == null || !departmentID.equals(departmentIDLast)) { - department = documentService.load(departmentID); - if (department != null) { - event.getWorkitem().setItemValue("space.name", department.getItemValueString("name")); - } - departmentIDLast = departmentID; - resetStats(event); - calculateStats(); - refreshCalculation = true; - } + department = null; + departmentID = event.getWorkitem().getItemValueString("space.ref"); + if (departmentIDLast == null || !departmentID.equals(departmentIDLast)) { + department = documentService.load(departmentID); + if (department != null) { + event.getWorkitem().setItemValue("space.name", department.getItemValueString("name")); + } + departmentIDLast = departmentID; + resetStats(event); + calculateStats(); + refreshCalculation = true; + } - // use cache? - if (event.getWorkitem().hasItem(event.getKey())) { - // logger.info(" use cache for " + event.getKey()); - // no op - return; - } - String link = "/pages/workitems/worklist.xhtml"; - logger.fine("process ref=" + workflowController.getWorkitem().getItemValueString("process.ref")); - ItemCollection process = documentService - .load(workflowController.getWorkitem().getItemValueString("process.ref")); - if (process != null) { - link = link + "?processref=" + process.getUniqueID() // + "&modelversion=rechnungsausgang*" - + "&spaceref=" + departmentID; - } - if ("analytic.invoices.count.all".equals(event.getKey())) { - event.setValue("" + countAll); - event.setLabel(resourceBundleHandler.findMessage("oplist.invoices")); - event.setDescription(resourceBundleHandler.findMessage("oplist.invoices.description.all")); - event.getWorkitem().setItemValue("invoices.total", event.getValue()); - event.setLink(link); - } + // use cache? + if (event.getWorkitem().hasItem(event.getKey())) { + // logger.info(" use cache for " + event.getKey()); + // no op + return; + } + String link = "/pages/workitems/worklist.xhtml"; + logger.fine("process ref=" + workflowController.getWorkitem().getItemValueString("process.ref")); + ItemCollection process = documentService + .load(workflowController.getWorkitem().getItemValueString("process.ref")); + if (process != null) { + link = link + "?processref=" + process.getUniqueID() // + "&modelversion=rechnungsausgang*" + + "&spaceref=" + departmentID; + } + if ("analytic.invoices.count.all".equals(event.getKey())) { + event.setValue("" + countAll); + event.setLabel(resourceBundleHandler.findMessage("oplist.invoices")); + event.setDescription(resourceBundleHandler.findMessage("oplist.invoices.description.all")); + event.getWorkitem().setItemValue("invoices.total", event.getValue()); + event.setLink(link); + } - if ("analytic.invoices.count.open".equals(event.getKey())) { - event.setValue("" + (countOpen)); - event.setLabel(resourceBundleHandler.findMessage("oplist.invoices")); - event.setDescription(resourceBundleHandler.findMessage("oplist.invoices.description.open")); - event.getWorkitem().setItemValue("invoices.total.open", event.getValue()); - event.setLink(link); - } + if ("analytic.invoices.count.open".equals(event.getKey())) { + event.setValue("" + (countOpen)); + event.setLabel(resourceBundleHandler.findMessage("oplist.invoices")); + event.setDescription(resourceBundleHandler.findMessage("oplist.invoices.description.open")); + event.getWorkitem().setItemValue("invoices.total.open", event.getValue()); + event.setLink(link); + } - if ("analytic.invoices.count.due".equals(event.getKey())) { - event.setValue("" + countDue); - event.setLabel(resourceBundleHandler.findMessage("oplist.invoices")); - event.setDescription(resourceBundleHandler.findMessage("oplist.invoices.description.due")); - event.getWorkitem().setItemValue("invoices.total.due", event.getValue()); - event.setLink(link); - } - if ("analytic.invoices.count.dunning".equals(event.getKey())) { - event.setValue("" + countDunning); - event.setLabel(resourceBundleHandler.findMessage("oplist.invoices")); - event.setDescription(resourceBundleHandler.findMessage("oplist.invoices.description.dunning")); - event.getWorkitem().setItemValue("invoices.total.dunning", event.getValue()); - event.setLink(link); - } + if ("analytic.invoices.count.due".equals(event.getKey())) { + event.setValue("" + countDue); + event.setLabel(resourceBundleHandler.findMessage("oplist.invoices")); + event.setDescription(resourceBundleHandler.findMessage("oplist.invoices.description.due")); + event.getWorkitem().setItemValue("invoices.total.due", event.getValue()); + event.setLink(link); + } + if ("analytic.invoices.count.dunning".equals(event.getKey())) { + event.setValue("" + countDunning); + event.setLabel(resourceBundleHandler.findMessage("oplist.invoices")); + event.setDescription(resourceBundleHandler.findMessage("oplist.invoices.description.dunning")); + event.getWorkitem().setItemValue("invoices.total.dunning", event.getValue()); + event.setLink(link); + } - } + } - /** - * Reset cached analytic keys - */ - private void resetStats(AnalyticEvent event) { - event.getWorkitem().removeItem("analytic.invoices.count.all"); - event.getWorkitem().removeItem("analytic.invoices.count.open"); - event.getWorkitem().removeItem("analytic.invoices.count.due"); - event.getWorkitem().removeItem("analytic.invoices.count.dunning"); + /** + * Reset cached analytic keys + */ + private void resetStats(AnalyticEvent event) { + event.getWorkitem().removeItem("analytic.invoices.count.all"); + event.getWorkitem().removeItem("analytic.invoices.count.open"); + event.getWorkitem().removeItem("analytic.invoices.count.due"); + event.getWorkitem().removeItem("analytic.invoices.count.dunning"); - } + } - /** - * Läd die statistik daten zu einem department die Anzahl an aktuellen - * Rechnungen zu ermitteln - * - * int countAll = 0; - * int countOpen = 0; - * int countDue = 0; - * int countDunning = 0; - * - */ - private void calculateStats() { - long l = System.currentTimeMillis(); + /** + * Läd die statistik daten zu einem department die Anzahl an aktuellen + * Rechnungen zu ermitteln + * + * int countAll = 0; int countOpen = 0; int countDue = 0; int countDunning = 0; + * + */ + private void calculateStats() { + long l = System.currentTimeMillis(); - countAll = 0; - countDue = 0; - countDunning = 0; - logger.info(" ├──calculate stats for " + departmentID + "...."); + countAll = 0; + countDue = 0; + countDunning = 0; + logger.info(" ├──calculate stats for " + departmentID + "...."); - try { + try { - if (departmentID == null || !departmentID.isEmpty()) { - countAll = documentService.count("(type:workitem) AND $uniqueidref:" + departmentID - + " AND $modelversion:rechnungsausgang-*"); + if (departmentID == null || !departmentID.isEmpty()) { + countAll = documentService.count( + "(type:workitem) AND $uniqueidref:" + departmentID + " AND $modelversion:rechnungsausgang-*"); - countDue = documentService.count("(type:workitem) AND $uniqueidref:" + departmentID - + " AND $modelversion:rechnungsausgang-* AND $taskid:[5100 TO 5199]"); - countDunning = documentService.count("(type:workitem) AND $uniqueidref:" + departmentID - + " AND $modelversion:rechnungsausgang-* AND $taskid:[5200 TO 5399]"); + countDue = documentService.count("(type:workitem) AND $uniqueidref:" + departmentID + + " AND $modelversion:rechnungsausgang-* AND $taskid:[5100 TO 5199]"); + countDunning = documentService.count("(type:workitem) AND $uniqueidref:" + departmentID + + " AND $modelversion:rechnungsausgang-* AND $taskid:[5200 TO 5399]"); - countOpen = countAll - countDue - countDunning; - } else { + countOpen = countAll - countDue - countDunning; + } else { - // countAll=documentService.count("(type:workitem OR type:workitemarchive) AND - // $modelversion:rechnungsausgang-*") + // countAll=documentService.count("(type:workitem OR type:workitemarchive) AND + // $modelversion:rechnungsausgang-*") - countAll = documentService.count("(type:workitem) AND $modelversion:rechnungsausgang-*"); + countAll = documentService.count("(type:workitem) AND $modelversion:rechnungsausgang-*"); - countDue = documentService - .count("(type:workitem) AND $modelversion:rechnungsausgang-* AND $taskid:[5100 TO 5199]"); + countDue = documentService + .count("(type:workitem) AND $modelversion:rechnungsausgang-* AND $taskid:[5100 TO 5199]"); - countDunning = documentService - .count("(type:workitem) AND $modelversion:rechnungsausgang-* AND $taskid:[5200 TO 5399]"); - countOpen = countAll - countDue - countDunning; - } + countDunning = documentService + .count("(type:workitem) AND $modelversion:rechnungsausgang-* AND $taskid:[5200 TO 5399]"); + countOpen = countAll - countDue - countDunning; + } - } catch (QueryException e) { - logger.warning("Failed to query invoices: " + e.getMessage()); + } catch (QueryException e) { + logger.warning("Failed to query invoices: " + e.getMessage()); - } - logger.info(" ├──calculate stats took " + (System.currentTimeMillis() - l) + "ms"); + } + logger.info(" ├──calculate stats took " + (System.currentTimeMillis() - l) + "ms"); - } + } - private String formatCurrency(Double value) { + private String formatCurrency(Double value) { - DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.getDefault()); - symbols.setGroupingSeparator('.'); - symbols.setDecimalSeparator(','); - DecimalFormat formatter = new DecimalFormat("#,##0.00", symbols); + DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.getDefault()); + symbols.setGroupingSeparator('.'); + symbols.setDecimalSeparator(','); + DecimalFormat formatter = new DecimalFormat("#,##0.00", symbols); - return formatter.format(value); - } + return formatter.format(value); + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerSteuer.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerSteuer.java index 59d09ac..2f3682a 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerSteuer.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticControllerSteuer.java @@ -28,7 +28,7 @@ import jakarta.json.JsonObjectBuilder; /** * Der AGLAnalyticController berechnet verschiedene Analyse Daten - * + * * @author rsoika * */ @@ -36,497 +36,477 @@ import jakarta.json.JsonObjectBuilder; @RequestScoped public class AGLAnalyticControllerSteuer implements Serializable { - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(AnalyticController.class.getName()); - - @Inject - protected DocumentService documentService; - - @Inject - TeamService teamService; - - @Inject - protected WorkflowController workflowController; - - @Inject - - protected DebitorStatistikController debitorStatistikController; - - List steuerbescheide = null; - - public void onEvent(@Observes AnalyticEvent event) { - - if (!"workitem".equals(event.getWorkitem().getType()) - || !event.getWorkitem().getModelVersion().startsWith("analyse-steuer")) { - // no op - return; - } - - ItemCollection process = documentService - .load(workflowController.getWorkitem().getItemValueString("process.ref")); - String link = "/pages/workitems/worklist.xhtml"; - if (process != null) { - link = link + "?processref=" + process.getUniqueID() - + "&workflowgroup=Steuerbescheid"; - } - if ("analytic.steuer.eust".equals(event.getKey())) { - event.setValue(computeSteuerbescheideEUST()); - event.setLabel("EUR"); - event.setDescription("Gesamt Einfuhrumsatzsteuer (EUSt)"); - event.getWorkitem().setItemValue("eust.total", event.getValue()); - event.setLink(link); - } - if ("analytic.steuer.zoll".equals(event.getKey())) { - event.setValue(computeSteuerbescheideZoll()); - event.setLabel("EUR"); - event.setDescription("Gesamt Zölle (ZOLLEU)"); - event.setLink(link); - } - - if ("analytic.steuer.total".equals(event.getKey())) { - event.setValue(computeSteuerbescheideTotal()); - event.setLabel("EUR"); - event.setDescription("Alle Steuerbescheide die noch nicht abgerechnet wurden."); - event.setLink(link); - } - - if ("analytic.steuer.saldo".equals(event.getKey())) { - event.setValue(computeSteuerbescheideSaldo()); - event.setLabel("EUR"); - event.setDescription("Saldo aller Steuerbescheide die noch nicht abgerechnet wurden."); - event.setLink(link); - } - - if ("analytic.steuer.due".equals(event.getKey())) { - event.setValue(computeSteuerbescheideSaldoDue()); - event.setLabel("EUR"); - event.setDescription("Saldo aller überfälligen Steuerbescheide die noch nicht abgerechnet wurden."); - // event.setLink(link + "&task=Überfällig"); - event.setLink(link + "&task=1200"); - } - - // Charts - - if ("analytic.steuer.trend".equals(event.getKey())) { - event.setValue(accumulateSteuerbescheideByWeek()); - event.setLabel("Zoll & EUST"); - event.setDescription("Fälligkeiten nach KW"); - } - - if ("analytic.steuer.trend.abteilung".equals(event.getKey())) { - event.setValue(accumulateSteuerbescheideAbteilungByWeek()); - event.setLabel("Nach Abteilungen"); - event.setDescription("Offene Salden nach KW pro Abteilung"); - } - - } - - /** - * Berechnet die Summe aller offenen EUST Beträge - * // tax.code=[0], amount=[3885.16], activity.type=[EUST], - * // filenumber=[LA-PAP-2408-024]} - * - * @return - */ - private double computeSteuerbescheideEUST() { - logger.fine("compute EUST..."); - List list = getSteuerbescheide(); - double result = 0; - - for (ItemCollection steuerItemCol : list) { - result = result + getZollBySteuerBeleg(steuerItemCol, "EUST"); - - } - return InvoiceUtil.round(result); - } - - /** - * Berechnet die Summe aller offenen Zoll Beträge - * - * @return - */ - private double computeSteuerbescheideZoll() { - logger.fine("compute Zoll..."); - List list = getSteuerbescheide(); - double result = 0; - for (ItemCollection steuerItemCol : list) { - result = result + getZollBySteuerBeleg(steuerItemCol, "ZOLL"); - } - return InvoiceUtil.round(result); - } - - /** - * Berechnet die Summe aller überfälligen steuer Beträge $taskID=1200 - * - * @return - */ - private double computeSteuerbescheideSaldo() { - logger.fine("compute saldo..."); - List list = getSteuerbescheide(); - double result = 0; - for (ItemCollection steuerItemCol : list) { - result = result + steuerItemCol.getItemValueDouble("invoice.saldo"); - } - return InvoiceUtil.round(result); - - } - - /** - * Berechnet die Summe aller überfälligen steuer Beträge $taskID=1200 - * - * @return - */ - private double computeSteuerbescheideTotal() { - logger.fine("compute total..."); - List list = getSteuerbescheide(); - double result = 0; - for (ItemCollection steuerItemCol : list) { - result = result + steuerItemCol.getItemValueDouble("invoice.total.net"); - } - return InvoiceUtil.round(result); - } - - /** - * Berechnet die Summe aller überfälligen steuer Beträge $taskID=1200 - * - * @return - */ - private double computeSteuerbescheideSaldoDue() { - logger.fine("compute EUST..."); - List list = getSteuerbescheide(); - double result = 0; - for (ItemCollection steuerItemCol : list) { - if (steuerItemCol.getTaskID() == 1200) { - result = result + steuerItemCol.getItemValueDouble("invoice.saldo"); - } - } - return InvoiceUtil.round(result); - - } - - /** - * Berechnet die summ zoll/eust für einen Beleg - * - * @param steuerItemCol - * @return - */ - private double getZollBySteuerBeleg(ItemCollection steuerItemCol, String type) { - double result = 0; - List positionsTabelle = InvoiceUtil.explodeChildList(steuerItemCol); - for (ItemCollection pos : positionsTabelle) { - if (type.equalsIgnoreCase(pos.getItemValueString("activity.type"))) { - result = result + pos.getItemValueDouble("amount"); - } - } - return result; - } - - private List getSteuerbescheide() { - if (steuerbescheide != null) { - return steuerbescheide; - } - // compute steuerbescheide.... - logger.info("Load Steuerbescheide for analytics...."); - String query = "(type:workitem ) AND ($workflowgroup:\"Steuerbescheid\")"; - try { - steuerbescheide = documentService.find(query, 999, 0, "$created", false); - return steuerbescheide; - } catch (QueryException e) { - logger.severe("Failed to get invoices: " + e.getMessage()); - } - - return null; - } - - /** - * Berechnet Chart daten aller Steuerbescheide bei KW - * - *
-	 * 
-	   chartCfg = {
-		  type: 'bar',
-		  data: {
-			datasets: [{
-			  data: [20, 10],
-			}],
-			labels: ['1', '2']
-		  }
-	    }
-	 * 
-	 * 
- * - * @return - */ - private String accumulateSteuerbescheideByWeek() { - logger.fine("accumulate steuer by week..."); - - List list = getSteuerbescheide(); - Map saldoByWeek = new HashMap<>(); - Calendar calendar = Calendar.getInstance(); - int earliestWeek = Integer.MAX_VALUE; - int latestWeek = Integer.MIN_VALUE; - - // Step 1: Group saldo by week - for (ItemCollection steuerItemCol : list) { - double saldo = steuerItemCol.getItemValueDouble("invoice.saldo"); - double total = steuerItemCol.getItemValueDouble("invoice.total.net"); - Date date = steuerItemCol.getItemValueDate("invoice.date"); - - // Get the week number and year from the date - calendar.setTime(date); - int weekNumber = calendar.get(Calendar.WEEK_OF_YEAR); - int year = calendar.get(Calendar.YEAR); - int yearWeek = year * 100 + weekNumber; // Unique key for each year + week combination - - // Accumulate the saldo per week - SteuerData steuerDataByWeek = saldoByWeek.getOrDefault(yearWeek, new SteuerData()); - double eust = getZollBySteuerBeleg(steuerItemCol, "EUST"); - double zoll = getZollBySteuerBeleg(steuerItemCol, "ZOLL"); - steuerDataByWeek.add(eust, zoll, total, saldo, ""); - - saldoByWeek.put(yearWeek, steuerDataByWeek); - - // Track the earliest and latest weeks - earliestWeek = Math.min(earliestWeek, yearWeek); - latestWeek = Math.max(latestWeek, yearWeek); - } - - // Step 2: Prepare the JSON structure and ensure we have all weeks from earliest - // to latest - JsonArrayBuilder dataBuilderSaldo = Json.createArrayBuilder(); - JsonArrayBuilder dataBuilderEust = Json.createArrayBuilder(); - JsonArrayBuilder dataBuilderZoll = Json.createArrayBuilder(); - JsonArrayBuilder labelsBuilder = Json.createArrayBuilder(); - - Calendar startCal = Calendar.getInstance(); - startCal.set(Calendar.YEAR, earliestWeek / 100); - startCal.set(Calendar.WEEK_OF_YEAR, earliestWeek % 100); - - Calendar endCal = Calendar.getInstance(); - endCal.set(Calendar.YEAR, latestWeek / 100); - endCal.set(Calendar.WEEK_OF_YEAR, latestWeek % 100); - - while (!startCal.after(endCal)) { - int weekNumber = startCal.get(Calendar.WEEK_OF_YEAR); - int year = startCal.get(Calendar.YEAR); - int yearWeek = year * 100 + weekNumber; - - // Add the saldo for the week or 0.0 if no data - SteuerData steuerData = saldoByWeek.getOrDefault(yearWeek, new SteuerData()); - dataBuilderSaldo.add(steuerData.saldo); - dataBuilderEust.add(steuerData.eust); - dataBuilderZoll.add(steuerData.zoll); - labelsBuilder.add(weekNumber + "/" + year); // Label as 'week/year' - - // Move to the next week - startCal.add(Calendar.WEEK_OF_YEAR, 1); - } - - // Step 3: Prepare the JSON structure - JsonObjectBuilder jsonBuilder = Json.createObjectBuilder() - .add("type", "bar") - .add("data", Json.createObjectBuilder() - .add("datasets", Json.createArrayBuilder() - - .add(Json.createObjectBuilder() // First dataset for 'tax' - .add("label", "Zoll") - .add("backgroundColor", "#96f") - .add("borderWidth", 1) - .add("data", dataBuilderZoll)) - .add(Json.createObjectBuilder() // Second dataset for 'eust' - .add("label", "EUSt") - .add("backgroundColor", "#36a2eb") - .add("borderWidth", 1) - .add("data", dataBuilderEust)) - .add(Json.createObjectBuilder() // Third dataset for 'Saldo' - .add("label", "Saldo") - .add("backgroundColor", "#ff6384") - .add("borderWidth", 1) - .add("data", dataBuilderSaldo))) - - .add("labels", labelsBuilder)); - - // Step 4: Return the JSON structure as a string - JsonObject chartCfg = jsonBuilder.build(); - return chartCfg.toString(); - } - - /** - * Berechnet Chart daten aller Steuerbescheide bei KW - * - *
-	 * 
-	   chartCfg = {
-		  type: 'bar',
-		  data: {
-			datasets: [{
-			  data: [20, 10],
-			}],
-			labels: ['1', '2']
-		  }
-	    }
-	 * 
-	 * 
- * - * @return - */ - private String accumulateSteuerbescheideAbteilungByWeek() { - logger.fine("accumulate steuer by Abteilung week..."); - - String[] colors = { - "#36a2eb", - "#ff6384", - "#4bc0c0", - "#ff9f40", - "#96f", - "#ffcd56", - "#c9cbcf" - }; - - List list = getSteuerbescheide(); - Map totalByWeek = new HashMap<>(); - Calendar calendar = Calendar.getInstance(); - int earliestWeek = Integer.MAX_VALUE; - int latestWeek = Integer.MIN_VALUE; - - List abteilungsListe = new ArrayList<>(); - - // Step 1: Group total by week - for (ItemCollection steuerItemCol : list) { - String abteilung = steuerItemCol.getItemValueString("space.name"); - if (abteilung.isEmpty()) { - abteilung = "Keine Zuordnung"; - } - if (!abteilungsListe.contains(abteilung)) { - abteilungsListe.add(abteilung); - } - double saldo = steuerItemCol.getItemValueDouble("invoice.saldo"); - double total = steuerItemCol.getItemValueDouble("invoice.total.net"); - Date date = steuerItemCol.getItemValueDate("invoice.date"); - - logger.fine(".... " + steuerItemCol.getItemValueString("$workflowSummary") + " = " + total); - // Get the week number and year from the date - calendar.setTime(date); - int weekNumber = calendar.get(Calendar.WEEK_OF_YEAR); - int year = calendar.get(Calendar.YEAR); - int yearWeek = year * 100 + weekNumber; - String key = abteilung + "~" + yearWeek;// Unique key Import Forest~202405 - - // Accumulate the total per week - SteuerData steuerDataByWeek = totalByWeek.getOrDefault(key, new SteuerData()); - double eust = getZollBySteuerBeleg(steuerItemCol, "EUST"); - double zoll = getZollBySteuerBeleg(steuerItemCol, "ZOLL"); - steuerDataByWeek.add(eust, zoll, total, saldo, abteilung); - - totalByWeek.put(key, steuerDataByWeek); - - // Track the earliest and latest weeks - earliestWeek = Math.min(earliestWeek, yearWeek); - latestWeek = Math.max(latestWeek, yearWeek); - } - - // Step 2: Prepare the JSON structure and ensure we have all weeks from earliest - // to latest - JsonArrayBuilder labelsBuilder = Json.createArrayBuilder(); - Calendar startCal = Calendar.getInstance(); - startCal.set(Calendar.YEAR, earliestWeek / 100); - startCal.set(Calendar.WEEK_OF_YEAR, earliestWeek % 100); - - Calendar endCal = Calendar.getInstance(); - endCal.set(Calendar.YEAR, latestWeek / 100); - endCal.set(Calendar.WEEK_OF_YEAR, latestWeek % 100); - - // Build labels - je eines pro woche - while (!startCal.after(endCal)) { - int weekNumber = startCal.get(Calendar.WEEK_OF_YEAR); - int year = startCal.get(Calendar.YEAR); - labelsBuilder.add(weekNumber + "/" + year); // Label as 'week/year' - // Move to the next week - startCal.add(Calendar.WEEK_OF_YEAR, 1); - } - - // Step 3: Baue die Balken pro abrteilung - JsonArrayBuilder datasets = Json.createArrayBuilder(); - - JsonObjectBuilder jsonBuilder = Json.createObjectBuilder() - .add("type", "bar"); - - JsonObjectBuilder dataDing = Json.createObjectBuilder(); - int iColor = 0; - for (String _abteilung : abteilungsListe) { - JsonArrayBuilder dataBuilderTotals = Json.createArrayBuilder(); - - startCal = Calendar.getInstance(); - startCal.set(Calendar.YEAR, earliestWeek / 100); - startCal.set(Calendar.WEEK_OF_YEAR, earliestWeek % 100); - - endCal = Calendar.getInstance(); - endCal.set(Calendar.YEAR, latestWeek / 100); - endCal.set(Calendar.WEEK_OF_YEAR, latestWeek % 100); - - while (!startCal.after(endCal)) { - int weekNumber = startCal.get(Calendar.WEEK_OF_YEAR); - int year = startCal.get(Calendar.YEAR); - int yearWeek = year * 100 + weekNumber; - String key = _abteilung + "~" + yearWeek;// Unique key Import Forest~202405 - // Add the total for the week or 0.0 if no data - SteuerData steuerData = totalByWeek.getOrDefault(key, new SteuerData()); - logger.fine(" " + yearWeek + "=" + steuerData.total); - dataBuilderTotals.add(steuerData.total); - // Move to the next week - startCal.add(Calendar.WEEK_OF_YEAR, 1); - } - - datasets.add(Json.createObjectBuilder() // First dataset for 'tax' - .add("label", _abteilung) - .add("backgroundColor", colors[iColor]) - .add("borderWidth", 1) - .add("data", dataBuilderTotals)); - - iColor++; - if (iColor > colors.length) { - iColor = 0; - } - } - - dataDing.add("datasets", datasets); - dataDing.add("labels", labelsBuilder); - - jsonBuilder.add("data", dataDing); - - // Step 4: Return the JSON structure as a string - JsonObject chartCfg = jsonBuilder.build(); - return chartCfg.toString(); - } - - /** - * Helper method to hold zoll and eust totals in an object. Needed to build - * totals per week. - */ - class SteuerData { - double total = 0.0; - double zoll = 0.0; - double eust = 0.0; - double saldo = 0.0; - String abteilung = ""; - - public SteuerData() { - // default constructor - } - - /** - * Adds new zoll and eust values and updates the total - * - * @param eustNew eust value to be added - * @param zollNew zoll value to be added - */ - public void add(double eustNew, double zollNew, double totalNew, double saldoNew, String abteilungNew) { - eust = eust + eustNew; - zoll = zoll + zollNew; - saldo = saldo + saldoNew; - total = total + totalNew; - abteilung = abteilungNew; - } - - } + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(AnalyticController.class.getName()); + + @Inject + protected DocumentService documentService; + + @Inject + TeamService teamService; + + @Inject + protected WorkflowController workflowController; + + @Inject + + protected DebitorStatistikController debitorStatistikController; + + List steuerbescheide = null; + + public void onEvent(@Observes AnalyticEvent event) { + + if (!"workitem".equals(event.getWorkitem().getType()) + || !event.getWorkitem().getModelVersion().startsWith("analyse-steuer")) { + // no op + return; + } + + ItemCollection process = documentService + .load(workflowController.getWorkitem().getItemValueString("process.ref")); + String link = "/pages/workitems/worklist.xhtml"; + if (process != null) { + link = link + "?processref=" + process.getUniqueID() + "&workflowgroup=Steuerbescheid"; + } + if ("analytic.steuer.eust".equals(event.getKey())) { + event.setValue(computeSteuerbescheideEUST()); + event.setLabel("EUR"); + event.setDescription("Gesamt Einfuhrumsatzsteuer (EUSt)"); + event.getWorkitem().setItemValue("eust.total", event.getValue()); + event.setLink(link); + } + if ("analytic.steuer.zoll".equals(event.getKey())) { + event.setValue(computeSteuerbescheideZoll()); + event.setLabel("EUR"); + event.setDescription("Gesamt Zölle (ZOLLEU)"); + event.setLink(link); + } + + if ("analytic.steuer.total".equals(event.getKey())) { + event.setValue(computeSteuerbescheideTotal()); + event.setLabel("EUR"); + event.setDescription("Alle Steuerbescheide die noch nicht abgerechnet wurden."); + event.setLink(link); + } + + if ("analytic.steuer.saldo".equals(event.getKey())) { + event.setValue(computeSteuerbescheideSaldo()); + event.setLabel("EUR"); + event.setDescription("Saldo aller Steuerbescheide die noch nicht abgerechnet wurden."); + event.setLink(link); + } + + if ("analytic.steuer.due".equals(event.getKey())) { + event.setValue(computeSteuerbescheideSaldoDue()); + event.setLabel("EUR"); + event.setDescription("Saldo aller überfälligen Steuerbescheide die noch nicht abgerechnet wurden."); + // event.setLink(link + "&task=Überfällig"); + event.setLink(link + "&task=1200"); + } + + // Charts + + if ("analytic.steuer.trend".equals(event.getKey())) { + event.setValue(accumulateSteuerbescheideByWeek()); + event.setLabel("Zoll & EUST"); + event.setDescription("Fälligkeiten nach KW"); + } + + if ("analytic.steuer.trend.abteilung".equals(event.getKey())) { + event.setValue(accumulateSteuerbescheideAbteilungByWeek()); + event.setLabel("Nach Abteilungen"); + event.setDescription("Offene Salden nach KW pro Abteilung"); + } + + } + + /** + * Berechnet die Summe aller offenen EUST Beträge // tax.code=[0], + * amount=[3885.16], activity.type=[EUST], // filenumber=[LA-PAP-2408-024]} + * + * @return + */ + private double computeSteuerbescheideEUST() { + logger.fine("compute EUST..."); + List list = getSteuerbescheide(); + double result = 0; + + for (ItemCollection steuerItemCol : list) { + result = result + getZollBySteuerBeleg(steuerItemCol, "EUST"); + + } + return InvoiceUtil.round(result); + } + + /** + * Berechnet die Summe aller offenen Zoll Beträge + * + * @return + */ + private double computeSteuerbescheideZoll() { + logger.fine("compute Zoll..."); + List list = getSteuerbescheide(); + double result = 0; + for (ItemCollection steuerItemCol : list) { + result = result + getZollBySteuerBeleg(steuerItemCol, "ZOLL"); + } + return InvoiceUtil.round(result); + } + + /** + * Berechnet die Summe aller überfälligen steuer Beträge $taskID=1200 + * + * @return + */ + private double computeSteuerbescheideSaldo() { + logger.fine("compute saldo..."); + List list = getSteuerbescheide(); + double result = 0; + for (ItemCollection steuerItemCol : list) { + result = result + steuerItemCol.getItemValueDouble("invoice.saldo"); + } + return InvoiceUtil.round(result); + + } + + /** + * Berechnet die Summe aller überfälligen steuer Beträge $taskID=1200 + * + * @return + */ + private double computeSteuerbescheideTotal() { + logger.fine("compute total..."); + List list = getSteuerbescheide(); + double result = 0; + for (ItemCollection steuerItemCol : list) { + result = result + steuerItemCol.getItemValueDouble("invoice.total.net"); + } + return InvoiceUtil.round(result); + } + + /** + * Berechnet die Summe aller überfälligen steuer Beträge $taskID=1200 + * + * @return + */ + private double computeSteuerbescheideSaldoDue() { + logger.fine("compute EUST..."); + List list = getSteuerbescheide(); + double result = 0; + for (ItemCollection steuerItemCol : list) { + if (steuerItemCol.getTaskID() == 1200) { + result = result + steuerItemCol.getItemValueDouble("invoice.saldo"); + } + } + return InvoiceUtil.round(result); + + } + + /** + * Berechnet die summ zoll/eust für einen Beleg + * + * @param steuerItemCol + * @return + */ + private double getZollBySteuerBeleg(ItemCollection steuerItemCol, String type) { + double result = 0; + List positionsTabelle = InvoiceUtil.explodeChildList(steuerItemCol); + for (ItemCollection pos : positionsTabelle) { + if (type.equalsIgnoreCase(pos.getItemValueString("activity.type"))) { + result = result + pos.getItemValueDouble("amount"); + } + } + return result; + } + + private List getSteuerbescheide() { + if (steuerbescheide != null) { + return steuerbescheide; + } + // compute steuerbescheide.... + logger.info("Load Steuerbescheide for analytics...."); + String query = "(type:workitem ) AND ($workflowgroup:\"Steuerbescheid\")"; + try { + steuerbescheide = documentService.find(query, 999, 0, "$created", false); + return steuerbescheide; + } catch (QueryException e) { + logger.severe("Failed to get invoices: " + e.getMessage()); + } + + return null; + } + + /** + * Berechnet Chart daten aller Steuerbescheide bei KW + * + *
+     *
+       chartCfg = {
+    	  type: 'bar',
+    	  data: {
+    		datasets: [{
+    		  data: [20, 10],
+    		}],
+    		labels: ['1', '2']
+    	  }
+        }
+     *
+     * 
+ * + * @return + */ + private String accumulateSteuerbescheideByWeek() { + logger.fine("accumulate steuer by week..."); + + List list = getSteuerbescheide(); + Map saldoByWeek = new HashMap<>(); + Calendar calendar = Calendar.getInstance(); + int earliestWeek = Integer.MAX_VALUE; + int latestWeek = Integer.MIN_VALUE; + + // Step 1: Group saldo by week + for (ItemCollection steuerItemCol : list) { + double saldo = steuerItemCol.getItemValueDouble("invoice.saldo"); + double total = steuerItemCol.getItemValueDouble("invoice.total.net"); + Date date = steuerItemCol.getItemValueDate("invoice.date"); + + // Get the week number and year from the date + calendar.setTime(date); + int weekNumber = calendar.get(Calendar.WEEK_OF_YEAR); + int year = calendar.get(Calendar.YEAR); + int yearWeek = year * 100 + weekNumber; // Unique key for each year + week combination + + // Accumulate the saldo per week + SteuerData steuerDataByWeek = saldoByWeek.getOrDefault(yearWeek, new SteuerData()); + double eust = getZollBySteuerBeleg(steuerItemCol, "EUST"); + double zoll = getZollBySteuerBeleg(steuerItemCol, "ZOLL"); + steuerDataByWeek.add(eust, zoll, total, saldo, ""); + + saldoByWeek.put(yearWeek, steuerDataByWeek); + + // Track the earliest and latest weeks + earliestWeek = Math.min(earliestWeek, yearWeek); + latestWeek = Math.max(latestWeek, yearWeek); + } + + // Step 2: Prepare the JSON structure and ensure we have all weeks from earliest + // to latest + JsonArrayBuilder dataBuilderSaldo = Json.createArrayBuilder(); + JsonArrayBuilder dataBuilderEust = Json.createArrayBuilder(); + JsonArrayBuilder dataBuilderZoll = Json.createArrayBuilder(); + JsonArrayBuilder labelsBuilder = Json.createArrayBuilder(); + + Calendar startCal = Calendar.getInstance(); + startCal.set(Calendar.YEAR, earliestWeek / 100); + startCal.set(Calendar.WEEK_OF_YEAR, earliestWeek % 100); + + Calendar endCal = Calendar.getInstance(); + endCal.set(Calendar.YEAR, latestWeek / 100); + endCal.set(Calendar.WEEK_OF_YEAR, latestWeek % 100); + + while (!startCal.after(endCal)) { + int weekNumber = startCal.get(Calendar.WEEK_OF_YEAR); + int year = startCal.get(Calendar.YEAR); + int yearWeek = year * 100 + weekNumber; + + // Add the saldo for the week or 0.0 if no data + SteuerData steuerData = saldoByWeek.getOrDefault(yearWeek, new SteuerData()); + dataBuilderSaldo.add(steuerData.saldo); + dataBuilderEust.add(steuerData.eust); + dataBuilderZoll.add(steuerData.zoll); + labelsBuilder.add(weekNumber + "/" + year); // Label as 'week/year' + + // Move to the next week + startCal.add(Calendar.WEEK_OF_YEAR, 1); + } + + // Step 3: Prepare the JSON structure + JsonObjectBuilder jsonBuilder = Json + .createObjectBuilder().add("type", "bar").add("data", Json + .createObjectBuilder().add("datasets", Json.createArrayBuilder() + + .add(Json.createObjectBuilder() // First dataset for 'tax' + .add("label", "Zoll").add("backgroundColor", "#96f").add("borderWidth", 1) + .add("data", dataBuilderZoll)) + .add(Json.createObjectBuilder() // Second dataset for 'eust' + .add("label", "EUSt").add("backgroundColor", "#36a2eb").add("borderWidth", 1) + .add("data", dataBuilderEust)) + .add(Json.createObjectBuilder() // Third dataset for 'Saldo' + .add("label", "Saldo").add("backgroundColor", "#ff6384").add("borderWidth", 1) + .add("data", dataBuilderSaldo))) + + .add("labels", labelsBuilder)); + + // Step 4: Return the JSON structure as a string + JsonObject chartCfg = jsonBuilder.build(); + return chartCfg.toString(); + } + + /** + * Berechnet Chart daten aller Steuerbescheide bei KW + * + *
+     *
+       chartCfg = {
+    	  type: 'bar',
+    	  data: {
+    		datasets: [{
+    		  data: [20, 10],
+    		}],
+    		labels: ['1', '2']
+    	  }
+        }
+     *
+     * 
+ * + * @return + */ + private String accumulateSteuerbescheideAbteilungByWeek() { + logger.fine("accumulate steuer by Abteilung week..."); + + String[] colors = { "#36a2eb", "#ff6384", "#4bc0c0", "#ff9f40", "#96f", "#ffcd56", "#c9cbcf" }; + + List list = getSteuerbescheide(); + Map totalByWeek = new HashMap<>(); + Calendar calendar = Calendar.getInstance(); + int earliestWeek = Integer.MAX_VALUE; + int latestWeek = Integer.MIN_VALUE; + + List abteilungsListe = new ArrayList<>(); + + // Step 1: Group total by week + for (ItemCollection steuerItemCol : list) { + String abteilung = steuerItemCol.getItemValueString("space.name"); + if (abteilung.isEmpty()) { + abteilung = "Keine Zuordnung"; + } + if (!abteilungsListe.contains(abteilung)) { + abteilungsListe.add(abteilung); + } + double saldo = steuerItemCol.getItemValueDouble("invoice.saldo"); + double total = steuerItemCol.getItemValueDouble("invoice.total.net"); + Date date = steuerItemCol.getItemValueDate("invoice.date"); + + logger.fine(".... " + steuerItemCol.getItemValueString("$workflowSummary") + " = " + total); + // Get the week number and year from the date + calendar.setTime(date); + int weekNumber = calendar.get(Calendar.WEEK_OF_YEAR); + int year = calendar.get(Calendar.YEAR); + int yearWeek = year * 100 + weekNumber; + String key = abteilung + "~" + yearWeek;// Unique key Import Forest~202405 + + // Accumulate the total per week + SteuerData steuerDataByWeek = totalByWeek.getOrDefault(key, new SteuerData()); + double eust = getZollBySteuerBeleg(steuerItemCol, "EUST"); + double zoll = getZollBySteuerBeleg(steuerItemCol, "ZOLL"); + steuerDataByWeek.add(eust, zoll, total, saldo, abteilung); + + totalByWeek.put(key, steuerDataByWeek); + + // Track the earliest and latest weeks + earliestWeek = Math.min(earliestWeek, yearWeek); + latestWeek = Math.max(latestWeek, yearWeek); + } + + // Step 2: Prepare the JSON structure and ensure we have all weeks from earliest + // to latest + JsonArrayBuilder labelsBuilder = Json.createArrayBuilder(); + Calendar startCal = Calendar.getInstance(); + startCal.set(Calendar.YEAR, earliestWeek / 100); + startCal.set(Calendar.WEEK_OF_YEAR, earliestWeek % 100); + + Calendar endCal = Calendar.getInstance(); + endCal.set(Calendar.YEAR, latestWeek / 100); + endCal.set(Calendar.WEEK_OF_YEAR, latestWeek % 100); + + // Build labels - je eines pro woche + while (!startCal.after(endCal)) { + int weekNumber = startCal.get(Calendar.WEEK_OF_YEAR); + int year = startCal.get(Calendar.YEAR); + labelsBuilder.add(weekNumber + "/" + year); // Label as 'week/year' + // Move to the next week + startCal.add(Calendar.WEEK_OF_YEAR, 1); + } + + // Step 3: Baue die Balken pro abrteilung + JsonArrayBuilder datasets = Json.createArrayBuilder(); + + JsonObjectBuilder jsonBuilder = Json.createObjectBuilder().add("type", "bar"); + + JsonObjectBuilder dataDing = Json.createObjectBuilder(); + int iColor = 0; + for (String _abteilung : abteilungsListe) { + JsonArrayBuilder dataBuilderTotals = Json.createArrayBuilder(); + + startCal = Calendar.getInstance(); + startCal.set(Calendar.YEAR, earliestWeek / 100); + startCal.set(Calendar.WEEK_OF_YEAR, earliestWeek % 100); + + endCal = Calendar.getInstance(); + endCal.set(Calendar.YEAR, latestWeek / 100); + endCal.set(Calendar.WEEK_OF_YEAR, latestWeek % 100); + + while (!startCal.after(endCal)) { + int weekNumber = startCal.get(Calendar.WEEK_OF_YEAR); + int year = startCal.get(Calendar.YEAR); + int yearWeek = year * 100 + weekNumber; + String key = _abteilung + "~" + yearWeek;// Unique key Import Forest~202405 + // Add the total for the week or 0.0 if no data + SteuerData steuerData = totalByWeek.getOrDefault(key, new SteuerData()); + logger.fine(" " + yearWeek + "=" + steuerData.total); + dataBuilderTotals.add(steuerData.total); + // Move to the next week + startCal.add(Calendar.WEEK_OF_YEAR, 1); + } + + datasets.add(Json.createObjectBuilder() // First dataset for 'tax' + .add("label", _abteilung).add("backgroundColor", colors[iColor]).add("borderWidth", 1) + .add("data", dataBuilderTotals)); + + iColor++; + if (iColor > colors.length) { + iColor = 0; + } + } + + dataDing.add("datasets", datasets); + dataDing.add("labels", labelsBuilder); + + jsonBuilder.add("data", dataDing); + + // Step 4: Return the JSON structure as a string + JsonObject chartCfg = jsonBuilder.build(); + return chartCfg.toString(); + } + + /** + * Helper method to hold zoll and eust totals in an object. Needed to build + * totals per week. + */ + class SteuerData { + double total = 0.0; + double zoll = 0.0; + double eust = 0.0; + double saldo = 0.0; + String abteilung = ""; + + public SteuerData() { + // default constructor + } + + /** + * Adds new zoll and eust values and updates the total + * + * @param eustNew eust value to be added + * @param zollNew zoll value to be added + */ + public void add(double eustNew, double zollNew, double totalNew, double saldoNew, String abteilungNew) { + eust = eust + eustNew; + zoll = zoll + zollNew; + saldo = saldo + saldoNew; + total = total + totalNew; + abteilung = abteilungNew; + } + + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticExcelAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticExcelAdapter.java index 8af8716..0c6cb79 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticExcelAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticExcelAdapter.java @@ -32,274 +32,260 @@ import jakarta.inject.Inject; * Textblock und fügt die offenen Steuerbescheid Daten ein * * Der Adapter wird im Modell wie folgt konfiguriert - * + * *
  * {@code
         
             textblock-ref
             
             filename
-         
+        
    }
  * 
- * + * *

* Der Adapter erweitert den POIAdapter somit können Felder aktualisiert werden * (siehe POIFineReplaceAdapter). - * + * *

* Der Adapter kopiert die Steuerbescheiddaten in neue Zeilen, welche ab - * Zeilennummer - * 16 eingefügt werden. + * Zeilennummer 16 eingefügt werden. *

- * + * * Abschliessend wird die Zelle 'TOTAL' noch aktualisiert. - * - * + * + * * @version 1.0 * @author rsoika */ public class AGLAnalyticExcelAdapter extends POIFindReplaceAdapter { - private static Logger logger = Logger.getLogger(AGLAnalyticExcelAdapter.class.getName()); + private static Logger logger = Logger.getLogger(AGLAnalyticExcelAdapter.class.getName()); - @Inject - WorkflowService workflowService; + @Inject + WorkflowService workflowService; - @Inject - InvoiceService opListExportService; + @Inject + InvoiceService opListExportService; - @Inject - DocumentService documentService; + @Inject + DocumentService documentService; - @Inject - ConfigService configService; + @Inject + ConfigService configService; - /** - * This method finds or create the Zahlungsavis and adds a reference - * ($workitemref) to the current invoice. - * - * @throws PluginException - */ - @SuppressWarnings("unchecked") - @Override - public ItemCollection execute(ItemCollection document, ItemCollection event) - throws AdapterException, PluginException { + /** + * This method finds or create the Zahlungsavis and adds a reference + * ($workitemref) to the current invoice. + * + * @throws PluginException + */ + @SuppressWarnings("unchecked") + @Override + public ItemCollection execute(ItemCollection document, ItemCollection event) + throws AdapterException, PluginException { - // ermittle die Hauptwährungen - ItemCollection configItemCollection = configService.loadConfiguration("AGL_CONFIGURATION"); - List currencyList = configItemCollection.getItemValue("currency.out"); - if (currencyList == null || currencyList.size() < 2) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, - "missing Currency configuration - please check parameters"); - } - - // read the Steuerbescheide options - ItemCollection steuerbescheideConfig = workflowService.evalWorkflowResult(event, "steuerbescheide", - document, - false); - if (steuerbescheideConfig == null || !steuerbescheideConfig.hasItem("excel-export")) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), CONFIG_ERROR, - "missing Steuerbescheide configuration in model event - please check model configuration"); - } - List excelDefList = steuerbescheideConfig.getItemValue("excel-export"); - ItemCollection excelDefCollection = XMLParser.parseItemStructure(excelDefList.get(0)); - - String textblock = excelDefCollection.getItemValueString("textblock"); - String template = excelDefCollection.getItemValueString("template"); - String targetName = excelDefCollection.getItemValueString("target-name"); - - // adapt text.... - targetName = workflowService.adaptText(targetName, document); - - try { - appendExcelTemplate(document, textblock, template, targetName); - insertInvoiceRows(document, targetName); - - logger.info("... loading poi configuration.."); - // Process POI instructions - // read the config - ItemCollection poiConfig = workflowService.evalWorkflowResult(event, "poi-update", document, - false); - if (poiConfig == null || !poiConfig.hasItem("findreplace")) { - throw new PluginException(POIFindReplaceAdapter.class.getSimpleName(), CONFIG_ERROR, - "missing poi configuration"); - } - List replaceDevList = poiConfig.getItemValue("findreplace"); - String eval = poiConfig.getItemValueString("eval"); - - logger.info("... update template with normal poi information.."); - this.updateFileData(document.getFileData(targetName), document, replaceDevList, eval); - - } catch (PluginException | IOException | QueryException e) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, - "failed to update steuerbescheide: " + e.getMessage()); - } - logger.info("... completed!"); - return document; + // ermittle die Hauptwährungen + ItemCollection configItemCollection = configService.loadConfiguration("AGL_CONFIGURATION"); + List currencyList = configItemCollection.getItemValue("currency.out"); + if (currencyList == null || currencyList.size() < 2) { + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, + "missing Currency configuration - please check parameters"); } - /** - * This helper method inserts a row for each invoice at row 16 - * into the excel template file - *

- * The method copies the Row A16 as a reference row - *

- * The named cell 'TOTAL' should contain the summary formula. It will be - * evaluated at the end. - * - * @throws PluginException - * @throws QueryException - */ - private void insertInvoiceRows(ItemCollection document, String fileName) - throws PluginException, QueryException { + // read the Steuerbescheide options + ItemCollection steuerbescheideConfig = workflowService.evalWorkflowResult(event, "steuerbescheide", document, + false); + if (steuerbescheideConfig == null || !steuerbescheideConfig.hasItem("excel-export")) { + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), CONFIG_ERROR, + "missing Steuerbescheide configuration in model event - please check model configuration"); + } + List excelDefList = steuerbescheideConfig.getItemValue("excel-export"); + ItemCollection excelDefCollection = XMLParser.parseItemStructure(excelDefList.get(0)); - // load dummy rechnungen - List invoices = loadSteuerbescheide(); + String textblock = excelDefCollection.getItemValueString("textblock"); + String template = excelDefCollection.getItemValueString("template"); + String targetName = excelDefCollection.getItemValueString("target-name"); - FileData fileData = document.getFileData(fileName); + // adapt text.... + targetName = workflowService.adaptText(targetName, document); - // load XSSFWorkbook - XSSFWorkbook doc = null; - try (InputStream imputStream = new ByteArrayInputStream(fileData.getContent())) { - doc = new XSSFWorkbook(imputStream); - // NOTE: we only take the first sheet ! - XSSFSheet sheet = doc.getSheetAt(0); - CellReference invoiceRefCell = new CellReference("A11"); - XSSFRow referenceRowInvoice = sheet.getRow(invoiceRefCell.getRow()); - int referenceRowPos = 10; - int rowPos = referenceRowPos; - int lastRow = 2999; - logger.finest("Last rownum=" + lastRow); + try { + appendExcelTemplate(document, textblock, template, targetName); + insertInvoiceRows(document, targetName); - // jetzt füge alle Rechnungen an. - int totalRowCount = invoices.size(); - sheet.shiftRows(referenceRowPos, lastRow, totalRowCount, true, true); - for (ItemCollection invoice : invoices) { - logger.fine("......add invoice " + invoice.getUniqueID()); - // now create a new line.. - XSSFRow row = sheet.createRow(rowPos); - row.copyRowFrom(referenceRowInvoice, new CellCopyPolicy()); - // insert values - row.getCell(0) - .setCellValue(invoice.getItemValueDate("$created")); - row.getCell(1, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueString("invoice.number")); - row.getCell(2, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueDate("invoice.date")); - row.getCell(3, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueDate("invoice.booking_date")); + logger.info("... loading poi configuration.."); + // Process POI instructions + // read the config + ItemCollection poiConfig = workflowService.evalWorkflowResult(event, "poi-update", document, false); + if (poiConfig == null || !poiConfig.hasItem("findreplace")) { + throw new PluginException(POIFindReplaceAdapter.class.getSimpleName(), CONFIG_ERROR, + "missing poi configuration"); + } + List replaceDevList = poiConfig.getItemValue("findreplace"); + String eval = poiConfig.getItemValueString("eval"); - // Änderung Frau Mwangi am 5.12.2025 - // Nun soll anstatt des Departments der Anmelder ausgegeben werden - // row.getCell(4, MissingCellPolicy.CREATE_NULL_AS_BLANK) - // .setCellValue(invoice.getItemValueString("space.name")); - row.getCell(4, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueString("steuer.anmelder")); + logger.info("... update template with normal poi information.."); + this.updateFileData(document.getFileData(targetName), document, replaceDevList, eval); - row.getCell(5, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueString("invoice.text")); - row.getCell(6, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueString("invoice.atc.number")); - row.getCell(7, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueDouble("invoice.total.net")); - row.getCell(8, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueDouble("invoice.saldo")); - row.getCell(9, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueDouble("steuer.eust")); - row.getCell(10, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueDate("steuer.eust.due")); - row.getCell(11, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueDouble("steuer.zoll")); - row.getCell(12, MissingCellPolicy.CREATE_NULL_AS_BLANK) - .setCellValue(invoice.getItemValueDate("steuer.zoll.due")); + } catch (PluginException | IOException | QueryException e) { + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, + "failed to update steuerbescheide: " + e.getMessage()); + } + logger.info("... completed!"); + return document; + } - rowPos++; - } + /** + * This helper method inserts a row for each invoice at row 16 into the excel + * template file + *

+ * The method copies the Row A16 as a reference row + *

+ * The named cell 'TOTAL' should contain the summary formula. It will be + * evaluated at the end. + * + * @throws PluginException + * @throws QueryException + */ + private void insertInvoiceRows(ItemCollection document, String fileName) throws PluginException, QueryException { - // Leerzeile - // categoryRow = sheet.createRow(rowPos); - rowPos++; + // load dummy rechnungen + List invoices = loadSteuerbescheide(); - // write back the file - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - doc.write(byteArrayOutputStream); + FileData fileData = document.getFileData(fileName); - byte[] newContent = byteArrayOutputStream.toByteArray(); - FileData fileDataNew = new FileData(fileData.getName(), newContent, fileData.getContentType(), - null); - // update the fileData - document.addFileData(fileDataNew); + // load XSSFWorkbook + XSSFWorkbook doc = null; + try (InputStream imputStream = new ByteArrayInputStream(fileData.getContent())) { + doc = new XSSFWorkbook(imputStream); + // NOTE: we only take the first sheet ! + XSSFSheet sheet = doc.getSheetAt(0); + CellReference invoiceRefCell = new CellReference("A11"); + XSSFRow referenceRowInvoice = sheet.getRow(invoiceRefCell.getRow()); + int referenceRowPos = 10; + int rowPos = referenceRowPos; + int lastRow = 2999; + logger.finest("Last rownum=" + lastRow); - logger.finest("......new document added"); + // jetzt füge alle Rechnungen an. + int totalRowCount = invoices.size(); + sheet.shiftRows(referenceRowPos, lastRow, totalRowCount, true, true); + for (ItemCollection invoice : invoices) { + logger.fine("......add invoice " + invoice.getUniqueID()); + // now create a new line.. + XSSFRow row = sheet.createRow(rowPos); + row.copyRowFrom(referenceRowInvoice, new CellCopyPolicy()); + // insert values + row.getCell(0).setCellValue(invoice.getItemValueDate("$created")); + row.getCell(1, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueString("invoice.number")); + row.getCell(2, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueDate("invoice.date")); + row.getCell(3, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueDate("invoice.booking_date")); + // Änderung Frau Mwangi am 5.12.2025 + // Nun soll anstatt des Departments der Anmelder ausgegeben werden + // row.getCell(4, MissingCellPolicy.CREATE_NULL_AS_BLANK) + // .setCellValue(invoice.getItemValueString("space.name")); + row.getCell(4, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueString("steuer.anmelder")); + + row.getCell(5, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueString("invoice.text")); + row.getCell(6, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueString("invoice.atc.number")); + row.getCell(7, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueDouble("invoice.total.net")); + row.getCell(8, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueDouble("invoice.saldo")); + row.getCell(9, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueDouble("steuer.eust")); + row.getCell(10, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueDate("steuer.eust.due")); + row.getCell(11, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueDouble("steuer.zoll")); + row.getCell(12, MissingCellPolicy.CREATE_NULL_AS_BLANK) + .setCellValue(invoice.getItemValueDate("steuer.zoll.due")); + + rowPos++; + } + + // Leerzeile + // categoryRow = sheet.createRow(rowPos); + rowPos++; + + // write back the file + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + doc.write(byteArrayOutputStream); + + byte[] newContent = byteArrayOutputStream.toByteArray(); + FileData fileDataNew = new FileData(fileData.getName(), newContent, fileData.getContentType(), null); + // update the fileData + document.addFileData(fileDataNew); + + logger.finest("......new document added"); + + } catch (IOException e) { + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, + "failed to update opliste: " + e.getMessage()); + } finally { + if (doc != null) { + try { + doc.close(); } catch (IOException e) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, - "failed to update opliste: " + e.getMessage()); - } finally { - if (doc != null) { - try { - doc.close(); - } catch (IOException e) { - logger.severe("Failed to close workbook: " + e.getMessage()); - e.printStackTrace(); - } - } + logger.severe("Failed to close workbook: " + e.getMessage()); + e.printStackTrace(); } + } + } + } + + /** + * This method loads a text-block for a specified ref and appends the named + * fileData object of this document. + * + * @param document + * @throws PluginException + */ + private void appendExcelTemplate(ItemCollection document, String textblockRef, String template, String targetName) + throws PluginException { + + if ((template == null || template.isEmpty()) || (textblockRef == null || textblockRef.isEmpty())) { + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, + "invalid steuerbescheide configuration in model event - textblock/template reference not defined!"); } - /** - * This method loads a text-block for a specified ref and appends the named - * fileData object of this document. - * - * @param document - * @throws PluginException - */ - private void appendExcelTemplate(ItemCollection document, String textblockRef, String template, - String targetName) - throws PluginException { - - if ((template == null || template.isEmpty()) || (textblockRef == null || textblockRef.isEmpty())) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, - "invalid steuerbescheide configuration in model event - textblock/template reference not defined!"); - } - - // load the text block - FileData fileData = opListExportService.loadTextBlockFileData(textblockRef, template); - - // do we found the document? - if (fileData == null) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, - "invalid steuerbescheide configuration in model event - template=" + template - + " not found!"); - } - fileData.setName(targetName); - // append document - logger.info("...append new steuerbescheide Template: " + targetName); - document.addFileData(fileData); + // load the text block + FileData fileData = opListExportService.loadTextBlockFileData(textblockRef, template); + // do we found the document? + if (fileData == null) { + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, + "invalid steuerbescheide configuration in model event - template=" + template + " not found!"); } + fileData.setName(targetName); + // append document + logger.info("...append new steuerbescheide Template: " + targetName); + document.addFileData(fileData); - /** - * Diese Methode läd alle offenen Steuerbescheide - * - * @return - */ - private List loadSteuerbescheide() { - List result = new ArrayList<>(); - try { - result = documentService.find( - "$modelversion:steuerbescheid-* AND type:workitem", 9999, 0, - "invoice.number", false); - } catch (QueryException e) { - e.printStackTrace(); - } - return result; + } + + /** + * Diese Methode läd alle offenen Steuerbescheide + * + * @return + */ + private List loadSteuerbescheide() { + List result = new ArrayList<>(); + try { + result = documentService.find("$modelversion:steuerbescheid-* AND type:workitem", 9999, 0, "invoice.number", + false); + } catch (QueryException e) { + e.printStackTrace(); } + return result; + } } \ No newline at end of file diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticExcelAdapterDebitor.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticExcelAdapterDebitor.java index 2cd4b18..a761ca0 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticExcelAdapterDebitor.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticExcelAdapterDebitor.java @@ -35,30 +35,29 @@ import jakarta.inject.Inject; * Textblock und fügt die offenen Recnungen ein * * Der Adapter wird im Modell wie folgt konfiguriert - * + * *

  * {@code
         
             textblock-ref
             
             filename
-         
+        
    }
  * 
- * + * *

* Der Adapter erweitert den POIAdapter somit können Felder aktualisiert werden * (siehe POIFineReplaceAdapter). - * + * *

* Der Adapter kopiert die ausgangsrechnungen in neue Zeilen, welche ab - * Zeilennummer - * 16 eingefügt werden. + * Zeilennummer 16 eingefügt werden. *

- * + * * Abschliessend wird die Zelle 'TOTAL' noch aktualisiert. - * - * + * + * * @version 1.0 * @author rsoika */ @@ -80,7 +79,7 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { /** * This method - * + * * @throws PluginException */ @SuppressWarnings("unchecked") @@ -92,9 +91,7 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { List currencyList = invoiceService.getCurrenciesOut(); // read the options - ItemCollection ausgangsrechnungConfig = workflowService.evalWorkflowResult(event, "soa", - document, - false); + ItemCollection ausgangsrechnungConfig = workflowService.evalWorkflowResult(event, "soa", document, false); if (ausgangsrechnungConfig == null || !ausgangsrechnungConfig.hasItem("excel-export")) { throw new PluginException(AGLAnalyticExcelAdapterDebitor.class.getSimpleName(), CONFIG_ERROR, "missing soa configuration in model event - please check model configuration"); @@ -129,14 +126,12 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { doc.write(byteArrayOutputStream); byte[] newContent = byteArrayOutputStream.toByteArray(); - FileData fileDataNew = new FileData(fileData.getName(), newContent, fileData.getContentType(), - null); + FileData fileDataNew = new FileData(fileData.getName(), newContent, fileData.getContentType(), null); document.addFileData(fileDataNew); logger.finest("......new document added"); } catch (IOException | QueryException e) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "failed to update opliste: " + e.getMessage()); } finally { if (doc != null) { @@ -153,8 +148,7 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { processPOIUpdate(document, event, targetName); } catch (PluginException e) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "failed to update steuerbescheide: " + e.getMessage()); } logger.info("... completed!"); @@ -166,7 +160,7 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { * the Total and Saldo Colum for each currency * * The template provided only the columns for the first currency. - * + * * @param sheet * @param currencyList */ @@ -201,7 +195,7 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { /** * Hilfsmethode die das fuehrende K/D aus der Debitorennummer entfernt - * + * * @return */ private String getDbtNr(ItemCollection workitem) { @@ -213,14 +207,14 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { } /** - * This helper method inserts a row for each invoice at row 16 - * into the excel template file + * This helper method inserts a row for each invoice at row 16 into the excel + * template file *

* The method copies the Row A16 as a reference row *

* The named cell 'TOTAL' should contain the summary formula. It will be * evaluated at the end. - * + * * @throws PluginException * @throws QueryException */ @@ -249,8 +243,7 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { XSSFRow row = sheet.createRow(rowPos); row.copyRowFrom(referenceRowInvoice, new CellCopyPolicy()); // insert values - row.getCell(0) - .setCellValue(invoice.getItemValueDate("$created")); + row.getCell(0).setCellValue(invoice.getItemValueDate("$created")); row.getCell(1, MissingCellPolicy.CREATE_NULL_AS_BLANK) .setCellValue(invoice.getItemValueString("invoice.number")); row.getCell(2, MissingCellPolicy.CREATE_NULL_AS_BLANK) @@ -293,7 +286,7 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { /** * Findet die Total Spalte für eine Währung - * + * * @param currency * @return */ @@ -313,17 +306,15 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { /** * This method loads a text-block for a specified ref and appends the named * fileData object of this document. - * + * * @param document * @throws PluginException */ private FileData appendExcelTemplate(ItemCollection document, String textblockRef, String template, - String targetName) - throws PluginException { + String targetName) throws PluginException { if ((template == null || template.isEmpty()) || (textblockRef == null || textblockRef.isEmpty())) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "invalid SOA configuration in model event - textblock/template reference not defined!"); } @@ -332,10 +323,8 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { // do we found the document? if (fileData == null) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, - "invalid SOA configuration in model event - template=" + template - + " not found!"); + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, + "invalid SOA configuration in model event - template=" + template + " not found!"); } fileData.setName(targetName); // append document @@ -346,18 +335,15 @@ public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter { /** * Diese Methode läd alle offenen Steuerbescheide - * + * * @return */ private List loadInvoices(String dbtrNumber) { List result = new ArrayList<>(); - String query = "(type:workitem) AND dbtr.number:" + dbtrNumber - + " AND $modelversion:rechnungsausgang-*"; + String query = "(type:workitem) AND dbtr.number:" + dbtrNumber + " AND $modelversion:rechnungsausgang-*"; try { - result = documentService.find( - query, 9999, 0, - "invoice.number", false); + result = documentService.find(query, 9999, 0, "invoice.number", false); } catch (QueryException e) { e.printStackTrace(); } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLConfigController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLConfigController.java index 09cbac6..a2212bd 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLConfigController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLConfigController.java @@ -1,51 +1,51 @@ -/******************************************************************************* - * Imixs Workflow Technology - * Copyright (C) 2003, 2008 Imixs Software Solutions GmbH, - * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You can receive a copy of the GNU General Public - * License at http://www.gnu.org/licenses/gpl.html - * - * Contributors: - * Imixs Software Solutions GmbH - initial API and implementation - * Ralph Soika - * - *******************************************************************************/ -package com.alexanderlogistics; - -import org.imixs.workflow.office.config.ConfigController; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Named; - -/** - * The Custom Config Controller for TAXES and Invoice typs - * - * - * @author rsoika - * @version 1.0 - */ - -@Named("aglConfigController") -// @RequestScoped -@ApplicationScoped -public class AGLConfigController extends ConfigController { - - private static final long serialVersionUID = 1L; - - public AGLConfigController() { - super(); - setName("AGL_CONFIGURATION"); - } - -} +/******************************************************************************* + * Imixs Workflow Technology + * Copyright (C) 2003, 2008 Imixs Software Solutions GmbH, + * http://www.imixs.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You can receive a copy of the GNU General Public + * License at http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Imixs Software Solutions GmbH - initial API and implementation + * Ralph Soika + * + *******************************************************************************/ +package com.alexanderlogistics; + +import org.imixs.workflow.office.config.ConfigController; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Named; + +/** + * The Custom Config Controller for TAXES and Invoice typs + * + * + * @author rsoika + * @version 1.0 + */ + +@Named("aglConfigController") +// @RequestScoped +@ApplicationScoped +public class AGLConfigController extends ConfigController { + + private static final long serialVersionUID = 1L; + + public AGLConfigController() { + super(); + setName("AGL_CONFIGURATION"); + } + +} diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerController.java index e22ec66..749cc60 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerController.java @@ -1,433 +1,427 @@ -/******************************************************************************* - * Imixs Workflow Technology - * Copyright (C) 2003, 2008 Imixs Software Solutions GmbH, - * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You can receive a copy of the GNU General Public - * License at http://www.gnu.org/licenses/gpl.html - * - * Contributors: - * Imixs Software Solutions GmbH - initial API and implementation - * Ralph Soika - * - *******************************************************************************/ -package com.alexanderlogistics; - -import java.io.IOException; -import java.io.Serializable; -import java.io.StringWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.imixs.workflow.ItemCollection; -import org.imixs.workflow.engine.DocumentService; -import org.imixs.workflow.exceptions.AccessDeniedException; -import org.imixs.workflow.exceptions.QueryException; -import org.imixs.workflow.faces.data.WorkflowController; -import org.imixs.workflow.faces.data.WorkflowEvent; - -import jakarta.enterprise.context.ConversationScoped; -import jakarta.enterprise.event.Observes; -import jakarta.faces.context.FacesContext; -import jakarta.inject.Inject; -import jakarta.inject.Named; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; - -/** - * Der BusinessPartnerController stellt Methoden für die BusinessPartner Forms - * sowie für die das Suche-Widget 'businesspartner_search' bereit. - * - * - * @author rsoika - * @version 1.0 - */ -@Named -@ConversationScoped -public class BusinessPartnerController implements Serializable { - - private static final long serialVersionUID = 1L; - - protected List ibanList = null; - protected List invoicesOut = null; - protected List invoicesIn = null; - protected List dunnings = null; - - @Inject - protected WorkflowController workflowController; - - @Inject - DocumentService documentService; - - @Inject - BusinessPartnerService businessPartnerService; - - private List searchResult = null; - - private static Logger logger = Logger.getLogger(BusinessPartnerController.class.getName()); - - public List getInvoicesOut() { - if (invoicesOut == null) { - invoicesOut = new ArrayList<>(); - } - return invoicesOut; - } - - public List getInvoicesIn() { - if (invoicesIn == null) { - invoicesIn = new ArrayList<>(); - } - return invoicesIn; - } - - public List getDunnings() { - if (dunnings == null) { - dunnings = new ArrayList<>(); - } - return dunnings; - } - - /** - * This method searches a text phrase within the list of DATEV kreditoren - *

- * JSF Integration: - * - * {@code - - * } - */ - public void search(String options) { - List resultList = null; - searchResult = new ArrayList(); - // get the param from faces context.... - FacesContext fc = FacesContext.getCurrentInstance(); - String phrase = fc.getExternalContext().getRequestParameterMap().get("phrase"); - if (phrase == null) { - return; - } - - logger.fine("search prase '" + phrase + "'"); - if (phrase == null || phrase.length() < 2) { - return; - } - logger.fine("search for=" + phrase); - Pattern pattern = null; - resultList = businessPartnerService.search(phrase); - // Compile the regex pattern if available - if (options.contains("regexPattern=")) { - String regexPattern = options.substring(options.indexOf("regexPattern=") + 13); - logger.fine("regex=" + regexPattern); - pattern = Pattern.compile(regexPattern); - } - - // Filter by Regex and convert ItemCollection into a BusinessPartnerSearchEntry - for (ItemCollection businessPartner : resultList) { - String name = businessPartner.getItemValueString("name"); - // Prüfen, ob der name der Regex entspricht - if (pattern != null) { - Matcher matcher = pattern.matcher(name); - if (!matcher.matches()) { - // Kein passendes Konto - continue; - } - } - String display = businessPartner.getItemValueString("$workflowsummary"); - - display = display.replace("\"", ""); - display = display.replace("'", ""); - - searchResult.add(new BusinessPartnerSearchEntry(name, display, buildJsonData(businessPartner))); - - } - } - - /** - * Diese Helper Method liefert true wenn in den Options von Form Part 'reRender' - * angegeben wurde. - * Damit löst die Suchmaske businesspartner_search.xhtml eine komplette rerender - * Phase der Form aus. - * Dies ist z.b. für den workflow 'analyse-debitor.bpmn' nützlich - * - * @param options - * @return - */ - public boolean isRerenderMode(String options) { - return options.toLowerCase().contains("rerender"); - } - - /** - * Die Resultliste wird als eine Liste einen Arrays zurückgegeben. Der Erste - * Eintrag - * - * @return - */ - public List getSearchResult() { - return searchResult; - } - - public ItemCollection getBusinessPartnerByID(String bpid) { - ItemCollection partner = businessPartnerService.getBusinessPartnerByID(bpid); - return partner; - } - - /** - * WorkflowEvent listener resolved additional data. - * - * For businesspartner the method converts the IBAN child list embeded HashMaps - * into ItemCollections and reconvert them before processing - * - * for invoices the method resolve the partner.id and partner.name - * - * @param workflowEvent - * @throws AccessDeniedException - */ - public void onWorkflowEvent(@Observes WorkflowEvent workflowEvent) throws AccessDeniedException { - - int eventType = workflowEvent.getEventType(); - ItemCollection workitem = workflowEvent.getWorkitem(); - if (workitem == null) { - return; - } - if (workitem.getModelVersion().startsWith("businesspartner-")) { - // reset orderItems if workItem has changed - if (WorkflowEvent.WORKITEM_CHANGED == eventType || WorkflowEvent.WORKITEM_CREATED == eventType) { - // reset state - ibanList = BusinessPartnerService.explodeBanks(workitem); - loadStats(workitem); - } - - // before the workitem is saved we update the field txtOrderItems - if (WorkflowEvent.WORKITEM_BEFORE_PROCESS == eventType) { - BusinessPartnerService.implodeBanks(workitem, ibanList); - } - - if (WorkflowEvent.WORKITEM_AFTER_PROCESS == eventType) { - // reset state - ibanList = BusinessPartnerService.explodeBanks(workitem); - } - } - - if (workitem.getModelVersion().startsWith("rechnungsausgang-")) { - // resolve partner.id and partner.name - if (workitem.getItemValueString("partner.id").isEmpty()) { - // try to resolve the data - ItemCollection bpWorkitem = getBusinessPartnerByID(InvoiceUtil.getBPID(workitem)); - if (bpWorkitem != null) { - workitem.setItemValue("partner.id", bpWorkitem.getItemValueString("partner.id")); - workitem.setItemValue("partner.name", bpWorkitem.getItemValueString("partner.name")); - } - } - } - - if (workitem.getModelVersion().startsWith("rechnungseingang-")) { - // resolve partner.id and partner.name - if (workitem.getItemValueString("partner.id").isEmpty()) { - // try to resolve the data - ItemCollection bpWorkitem = getBusinessPartnerByID(InvoiceUtil.getBPID(workitem)); - if (bpWorkitem != null) { - workitem.setItemValue("partner.id", bpWorkitem.getItemValueString("partner.id")); - workitem.setItemValue("partner.name", bpWorkitem.getItemValueString("partner.name")); - } - } - } - - if (workitem.getModelVersion().startsWith("zahlungseingang-")) { - // resolve partner.id and partner.name - if (workitem.getItemValueString("partner.id").isEmpty()) { - // try to resolve the data - ItemCollection bpWorkitem = getBusinessPartnerByID( - InvoiceUtil.buildBPID(workitem.getItemValueString("dbtr.number"))); - if (bpWorkitem != null) { - workitem.setItemValue("partner.id", bpWorkitem.getItemValueString("partner.id")); - workitem.setItemValue("partner.name", bpWorkitem.getItemValueString("partner.name")); - } - } - } - - } - - /** - * Hilfsmethode die die offenen Rechnungen läd. Wird von WorktiemChanged Event - * aufgerufen. - * - * @param workitem - */ - private void loadStats(ItemCollection workitem) { - - try { - // Ausgangsrechnungen - String dbtrNumber = workitem.getItemValueString("dbtr.number"); - if (!dbtrNumber.isEmpty()) { - invoicesOut = businessPartnerService.findOutgoingInvoices(dbtrNumber); - // Mahnungen - String query = "(type:workitem) AND ($modelversion:mahnlauf-*) " - + " AND (dbtr.number:" + dbtrNumber + ")"; - dunnings = documentService.findStubs(query, 999, 0, "invoice.number", false); - } - - // Eingangsrechnungen - String cdtrNumber = workitem.getItemValueString("cdtr.number"); - if (!cdtrNumber.isEmpty()) { - String query = "(type:workitem) AND ($modelversion:rechnungseingang-*) " - + " AND (cdtr.number:" + cdtrNumber + ")"; - invoicesIn = documentService.findStubs(query, 999, 0, "invoice.number", false); - } - - } catch (QueryException e) { - logger.severe("failed to load stats: " + e.getMessage()); - } - - } - - public List getIbanList() { - return ibanList; - } - - public void addIBAN() { - if (ibanList == null) { - ibanList = new ArrayList(); - } - ItemCollection source = new ItemCollection(); - ibanList.add(source); - } - - /** - * Removes an dbtr item from the list - * - * @param name - name of dbtr - */ - public void removeIBAN(String id) { - if (id != null && ibanList != null) { - for (ItemCollection cdtr : ibanList) { - if (id.equals(cdtr.getItemValueString("id"))) { - ibanList.remove(cdtr); - break; - } - } - } - } - - /** - * This JSF backing method is called by businesspartner_serach.xhtml after the - * user has selected a new businesspartner. - *

- * The method lookups the given BusinessPartner and updates the metadata for - * dbtr/cdtr depending on the workflow type. - * A form will than rerender the opList section. - */ - public void updateMetaData() { - FacesContext fc = FacesContext.getCurrentInstance(); - String bpid = fc.getExternalContext().getRequestParameterMap().get("partnerID"); - logger.info("....update metadata.....bpid = " + bpid); - ItemCollection businessPartner = businessPartnerService.getBusinessPartnerByID(bpid); - if (businessPartner != null) { - - // Eingangsrechnung - if (InvoiceUtil.isCreditorInvoice(workflowController.getWorkitem())) { - workflowController.getWorkitem().setItemValue("cdtr.number", - businessPartner.getItemValueString("cdtr.number")); - workflowController.getWorkitem().setItemValue("cdtr.name", - businessPartner.getItemValueString("partner.name")); - workflowController.getWorkitem().setItemValue("cdtr.mail", - businessPartner.getItemValue("cdtr.mail")); - } - - // Ausgangsrechnung - if (InvoiceUtil.isDebitorInvoice(workflowController.getWorkitem())) { - workflowController.getWorkitem().setItemValue("dbtr.number", - businessPartner.getItemValueString("dbtr.number")); - workflowController.getWorkitem().setItemValue("dbtr.name", - businessPartner.getItemValueString("partner.name")); - workflowController.getWorkitem().setItemValue("dbtr.mail", - businessPartner.getItemValue("dbtr.mail")); - } - - // Zahlungseingang - if (workflowController.getWorkitem().getModelVersion().startsWith("zahlungseingang")) { - workflowController.getWorkitem().setItemValue("dbtr.number", - businessPartner.getItemValueString("dbtr.number")); - workflowController.getWorkitem().setItemValue("dbtr.name", - businessPartner.getItemValueString("partner.name")); - workflowController.getWorkitem().setItemValue("dbtr.mail", - businessPartner.getItemValue("dbtr.mail")); - } - - // Analyse Debitor - if (workflowController.getWorkitem().getModelVersion().startsWith("analyse-debitor")) { - workflowController.getWorkitem().setItemValue("dbtr.number", - businessPartner.getItemValueString("dbtr.number")); - workflowController.getWorkitem().setItemValue("dbtr.name", - businessPartner.getItemValueString("partner.name")); - workflowController.getWorkitem().setItemValue("dbtr.mail", - businessPartner.getItemValue("dbtr.mail")); - } - - } - searchResult = null; - - } - - /** - * Hilfsmethode die eine JSON Struktur mit allen relevanten Daten für einen - * BusinessPartner erzeugt. - * - * @return - */ - private String buildJsonData(ItemCollection businessPartner) { - JsonObjectBuilder objectBuilder = Json.createObjectBuilder(). // - add("name", jsonVal(businessPartner.getItemValueString("name"))). // - add("cdtr.number", jsonVal(businessPartner.getItemValueString("cdtr.number"))). // - add("dbtr.number", jsonVal(businessPartner.getItemValueString("dbtr.number"))). // - add("partner.name", jsonVal(businessPartner.getItemValueString("partner.name"))); - - // get iban list - // {iban=[Dxxxxxx1], name=[Bank 1], id=[bank1], bic=[CITIDEFF]} - ibanList = BusinessPartnerService.getBanks(businessPartner); - int i = 1; - for (ItemCollection iban : ibanList) { - objectBuilder.add("iban" + 1, jsonVal(iban.getItemValueString("iban"))). // - add("bic" + 1, jsonVal(iban.getItemValueString("bic")));// - } - - JsonObject jsonObject = objectBuilder.build(); - - String jsonString = "{}"; - try (Writer writer = new StringWriter()) { - Json.createWriter(writer).write(jsonObject); - jsonString = writer.toString(); - } catch (IOException e) { - logger.warning("Unable to build json structure"); - } - return jsonString; - } - - /** - * Helper method to remove " and ' characters - causing problems - * - * @param val - * @return - */ - public static String jsonVal(String val) { - val = val.replace("\"", ""); - val = val.replace("'", ""); - return val; - } - -} +/******************************************************************************* + * Imixs Workflow Technology + * Copyright (C) 2003, 2008 Imixs Software Solutions GmbH, + * http://www.imixs.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You can receive a copy of the GNU General Public + * License at http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Imixs Software Solutions GmbH - initial API and implementation + * Ralph Soika + * + *******************************************************************************/ +package com.alexanderlogistics; + +import java.io.IOException; +import java.io.Serializable; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.imixs.workflow.ItemCollection; +import org.imixs.workflow.engine.DocumentService; +import org.imixs.workflow.exceptions.AccessDeniedException; +import org.imixs.workflow.exceptions.QueryException; +import org.imixs.workflow.faces.data.WorkflowController; +import org.imixs.workflow.faces.data.WorkflowEvent; + +import jakarta.enterprise.context.ConversationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.faces.context.FacesContext; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; + +/** + * Der BusinessPartnerController stellt Methoden für die BusinessPartner Forms + * sowie für die das Suche-Widget 'businesspartner_search' bereit. + * + * + * @author rsoika + * @version 1.0 + */ +@Named +@ConversationScoped +public class BusinessPartnerController implements Serializable { + + private static final long serialVersionUID = 1L; + + protected List ibanList = null; + protected List invoicesOut = null; + protected List invoicesIn = null; + protected List dunnings = null; + + @Inject + protected WorkflowController workflowController; + + @Inject + DocumentService documentService; + + @Inject + BusinessPartnerService businessPartnerService; + + private List searchResult = null; + + private static Logger logger = Logger.getLogger(BusinessPartnerController.class.getName()); + + public List getInvoicesOut() { + if (invoicesOut == null) { + invoicesOut = new ArrayList<>(); + } + return invoicesOut; + } + + public List getInvoicesIn() { + if (invoicesIn == null) { + invoicesIn = new ArrayList<>(); + } + return invoicesIn; + } + + public List getDunnings() { + if (dunnings == null) { + dunnings = new ArrayList<>(); + } + return dunnings; + } + + /** + * This method searches a text phrase within the list of DATEV kreditoren + *

+ * JSF Integration: + * + * {@code + } + */ + public void search(String options) { + List resultList = null; + searchResult = new ArrayList(); + // get the param from faces context.... + FacesContext fc = FacesContext.getCurrentInstance(); + String phrase = fc.getExternalContext().getRequestParameterMap().get("phrase"); + if (phrase == null) { + return; + } + + logger.fine("search prase '" + phrase + "'"); + if (phrase == null || phrase.length() < 2) { + return; + } + logger.fine("search for=" + phrase); + Pattern pattern = null; + resultList = businessPartnerService.search(phrase); + // Compile the regex pattern if available + if (options.contains("regexPattern=")) { + String regexPattern = options.substring(options.indexOf("regexPattern=") + 13); + logger.fine("regex=" + regexPattern); + pattern = Pattern.compile(regexPattern); + } + + // Filter by Regex and convert ItemCollection into a BusinessPartnerSearchEntry + for (ItemCollection businessPartner : resultList) { + String name = businessPartner.getItemValueString("name"); + // Prüfen, ob der name der Regex entspricht + if (pattern != null) { + Matcher matcher = pattern.matcher(name); + if (!matcher.matches()) { + // Kein passendes Konto + continue; + } + } + String display = businessPartner.getItemValueString("$workflowsummary"); + + display = display.replace("\"", ""); + display = display.replace("'", ""); + + searchResult.add(new BusinessPartnerSearchEntry(name, display, buildJsonData(businessPartner))); + + } + } + + /** + * Diese Helper Method liefert true wenn in den Options von Form Part 'reRender' + * angegeben wurde. Damit löst die Suchmaske businesspartner_search.xhtml eine + * komplette rerender Phase der Form aus. Dies ist z.b. für den workflow + * 'analyse-debitor.bpmn' nützlich + * + * @param options + * @return + */ + public boolean isRerenderMode(String options) { + return options.toLowerCase().contains("rerender"); + } + + /** + * Die Resultliste wird als eine Liste einen Arrays zurückgegeben. Der Erste + * Eintrag + * + * @return + */ + public List getSearchResult() { + return searchResult; + } + + public ItemCollection getBusinessPartnerByID(String bpid) { + ItemCollection partner = businessPartnerService.getBusinessPartnerByID(bpid); + return partner; + } + + /** + * WorkflowEvent listener resolved additional data. + * + * For businesspartner the method converts the IBAN child list embeded HashMaps + * into ItemCollections and reconvert them before processing + * + * for invoices the method resolve the partner.id and partner.name + * + * @param workflowEvent + * @throws AccessDeniedException + */ + public void onWorkflowEvent(@Observes WorkflowEvent workflowEvent) throws AccessDeniedException { + + int eventType = workflowEvent.getEventType(); + ItemCollection workitem = workflowEvent.getWorkitem(); + if (workitem == null) { + return; + } + if (workitem.getModelVersion().startsWith("businesspartner-")) { + // reset orderItems if workItem has changed + if (WorkflowEvent.WORKITEM_CHANGED == eventType || WorkflowEvent.WORKITEM_CREATED == eventType) { + // reset state + ibanList = BusinessPartnerService.explodeBanks(workitem); + loadStats(workitem); + } + + // before the workitem is saved we update the field txtOrderItems + if (WorkflowEvent.WORKITEM_BEFORE_PROCESS == eventType) { + BusinessPartnerService.implodeBanks(workitem, ibanList); + } + + if (WorkflowEvent.WORKITEM_AFTER_PROCESS == eventType) { + // reset state + ibanList = BusinessPartnerService.explodeBanks(workitem); + } + } + + if (workitem.getModelVersion().startsWith("rechnungsausgang-")) { + // resolve partner.id and partner.name + if (workitem.getItemValueString("partner.id").isEmpty()) { + // try to resolve the data + ItemCollection bpWorkitem = getBusinessPartnerByID(InvoiceUtil.getBPID(workitem)); + if (bpWorkitem != null) { + workitem.setItemValue("partner.id", bpWorkitem.getItemValueString("partner.id")); + workitem.setItemValue("partner.name", bpWorkitem.getItemValueString("partner.name")); + } + } + } + + if (workitem.getModelVersion().startsWith("rechnungseingang-")) { + // resolve partner.id and partner.name + if (workitem.getItemValueString("partner.id").isEmpty()) { + // try to resolve the data + ItemCollection bpWorkitem = getBusinessPartnerByID(InvoiceUtil.getBPID(workitem)); + if (bpWorkitem != null) { + workitem.setItemValue("partner.id", bpWorkitem.getItemValueString("partner.id")); + workitem.setItemValue("partner.name", bpWorkitem.getItemValueString("partner.name")); + } + } + } + + if (workitem.getModelVersion().startsWith("zahlungseingang-")) { + // resolve partner.id and partner.name + if (workitem.getItemValueString("partner.id").isEmpty()) { + // try to resolve the data + ItemCollection bpWorkitem = getBusinessPartnerByID( + InvoiceUtil.buildBPID(workitem.getItemValueString("dbtr.number"))); + if (bpWorkitem != null) { + workitem.setItemValue("partner.id", bpWorkitem.getItemValueString("partner.id")); + workitem.setItemValue("partner.name", bpWorkitem.getItemValueString("partner.name")); + } + } + } + + } + + /** + * Hilfsmethode die die offenen Rechnungen läd. Wird von WorktiemChanged Event + * aufgerufen. + * + * @param workitem + */ + private void loadStats(ItemCollection workitem) { + + try { + // Ausgangsrechnungen + String dbtrNumber = workitem.getItemValueString("dbtr.number"); + if (!dbtrNumber.isEmpty()) { + invoicesOut = businessPartnerService.findOutgoingInvoices(dbtrNumber); + // Mahnungen + String query = "(type:workitem) AND ($modelversion:mahnlauf-*) " + " AND (dbtr.number:" + dbtrNumber + + ")"; + dunnings = documentService.findStubs(query, 999, 0, "invoice.number", false); + } + + // Eingangsrechnungen + String cdtrNumber = workitem.getItemValueString("cdtr.number"); + if (!cdtrNumber.isEmpty()) { + String query = "(type:workitem) AND ($modelversion:rechnungseingang-*) " + " AND (cdtr.number:" + + cdtrNumber + ")"; + invoicesIn = documentService.findStubs(query, 999, 0, "invoice.number", false); + } + + } catch (QueryException e) { + logger.severe("failed to load stats: " + e.getMessage()); + } + + } + + public List getIbanList() { + return ibanList; + } + + public void addIBAN() { + if (ibanList == null) { + ibanList = new ArrayList(); + } + ItemCollection source = new ItemCollection(); + ibanList.add(source); + } + + /** + * Removes an dbtr item from the list + * + * @param name - name of dbtr + */ + public void removeIBAN(String id) { + if (id != null && ibanList != null) { + for (ItemCollection cdtr : ibanList) { + if (id.equals(cdtr.getItemValueString("id"))) { + ibanList.remove(cdtr); + break; + } + } + } + } + + /** + * This JSF backing method is called by businesspartner_serach.xhtml after the + * user has selected a new businesspartner. + *

+ * The method lookups the given BusinessPartner and updates the metadata for + * dbtr/cdtr depending on the workflow type. A form will than rerender the + * opList section. + */ + public void updateMetaData() { + FacesContext fc = FacesContext.getCurrentInstance(); + String bpid = fc.getExternalContext().getRequestParameterMap().get("partnerID"); + logger.info("....update metadata.....bpid = " + bpid); + ItemCollection businessPartner = businessPartnerService.getBusinessPartnerByID(bpid); + if (businessPartner != null) { + + // Eingangsrechnung + if (InvoiceUtil.isCreditorInvoice(workflowController.getWorkitem())) { + workflowController.getWorkitem().setItemValue("cdtr.number", + businessPartner.getItemValueString("cdtr.number")); + workflowController.getWorkitem().setItemValue("cdtr.name", + businessPartner.getItemValueString("partner.name")); + workflowController.getWorkitem().setItemValue("cdtr.mail", businessPartner.getItemValue("cdtr.mail")); + } + + // Ausgangsrechnung + if (InvoiceUtil.isDebitorInvoice(workflowController.getWorkitem())) { + workflowController.getWorkitem().setItemValue("dbtr.number", + businessPartner.getItemValueString("dbtr.number")); + workflowController.getWorkitem().setItemValue("dbtr.name", + businessPartner.getItemValueString("partner.name")); + workflowController.getWorkitem().setItemValue("dbtr.mail", businessPartner.getItemValue("dbtr.mail")); + } + + // Zahlungseingang + if (workflowController.getWorkitem().getModelVersion().startsWith("zahlungseingang")) { + workflowController.getWorkitem().setItemValue("dbtr.number", + businessPartner.getItemValueString("dbtr.number")); + workflowController.getWorkitem().setItemValue("dbtr.name", + businessPartner.getItemValueString("partner.name")); + workflowController.getWorkitem().setItemValue("dbtr.mail", businessPartner.getItemValue("dbtr.mail")); + } + + // Analyse Debitor + if (workflowController.getWorkitem().getModelVersion().startsWith("analyse-debitor")) { + workflowController.getWorkitem().setItemValue("dbtr.number", + businessPartner.getItemValueString("dbtr.number")); + workflowController.getWorkitem().setItemValue("dbtr.name", + businessPartner.getItemValueString("partner.name")); + workflowController.getWorkitem().setItemValue("dbtr.mail", businessPartner.getItemValue("dbtr.mail")); + } + + } + searchResult = null; + + } + + /** + * Hilfsmethode die eine JSON Struktur mit allen relevanten Daten für einen + * BusinessPartner erzeugt. + * + * @return + */ + private String buildJsonData(ItemCollection businessPartner) { + JsonObjectBuilder objectBuilder = Json.createObjectBuilder(). // + add("name", jsonVal(businessPartner.getItemValueString("name"))). // + add("cdtr.number", jsonVal(businessPartner.getItemValueString("cdtr.number"))). // + add("dbtr.number", jsonVal(businessPartner.getItemValueString("dbtr.number"))). // + add("partner.name", jsonVal(businessPartner.getItemValueString("partner.name"))); + + // get iban list + // {iban=[Dxxxxxx1], name=[Bank 1], id=[bank1], bic=[CITIDEFF]} + ibanList = BusinessPartnerService.getBanks(businessPartner); + int i = 1; + for (ItemCollection iban : ibanList) { + objectBuilder.add("iban" + 1, jsonVal(iban.getItemValueString("iban"))). // + add("bic" + 1, jsonVal(iban.getItemValueString("bic")));// + } + + JsonObject jsonObject = objectBuilder.build(); + + String jsonString = "{}"; + try (Writer writer = new StringWriter()) { + Json.createWriter(writer).write(jsonObject); + jsonString = writer.toString(); + } catch (IOException e) { + logger.warning("Unable to build json structure"); + } + return jsonString; + } + + /** + * Helper method to remove " and ' characters - causing problems + * + * @param val + * @return + */ + public static String jsonVal(String val) { + val = val.replace("\"", ""); + val = val.replace("'", ""); + return val; + } + +} diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerSearchEntry.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerSearchEntry.java index dcd7234..227c42b 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerSearchEntry.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerSearchEntry.java @@ -5,44 +5,44 @@ package com.alexanderlogistics; * Sucheregenisses nach kreditoren an das Frontend. Die Klasse besteht aus einem * Key, einer DisplayZeile für die Darstellung und einen JSON String der * beliebige Daten enthalten kann. - * + * * @author rsoika * */ public class BusinessPartnerSearchEntry { - private String key; - private String display; - private String data; + private String key; + private String display; + private String data; - public BusinessPartnerSearchEntry(String key, String display, String data) { - super(); - this.key = key; - this.display = display; - this.data = data; - } + public BusinessPartnerSearchEntry(String key, String display, String data) { + super(); + this.key = key; + this.display = display; + this.data = data; + } - public String getKey() { - return key; - } + public String getKey() { + return key; + } - public void setKey(String key) { - this.key = key; - } + public void setKey(String key) { + this.key = key; + } - public String getDisplay() { - return display; - } + public String getDisplay() { + return display; + } - public void setDisplay(String display) { - this.display = display; - } + public void setDisplay(String display) { + this.display = display; + } - public String getData() { - return data; - } + public String getData() { + return data; + } - public void setData(String data) { - this.data = data; - } + public void setData(String data) { + this.data = data; + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerService.java index dc77c7d..b5bd30e 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerService.java @@ -1,26 +1,26 @@ /******************************************************************************* - * Imixs Workflow - * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, + * Imixs Workflow + * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Project: + * + * Project: * http://www.imixs.org * http://java.net/projects/imixs-workflow - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika - Software Developer *******************************************************************************/ @@ -51,15 +51,15 @@ import jakarta.annotation.PostConstruct; import jakarta.annotation.security.DeclareRoles; import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RunAs; -import jakarta.ejb.Singleton; +import jakarta.ejb.Stateless; import jakarta.inject.Inject; /** * Der BusinessPartnerService stellt Methoden für den Zugriff auf Business * Partner bereit. - * + * * @author rsoika - * + * */ @DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", @@ -68,7 +68,7 @@ import jakarta.inject.Inject; @RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) -@Singleton +@Stateless @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") public class BusinessPartnerService { @@ -121,13 +121,12 @@ public class BusinessPartnerService { /** * Diese Methode aktualisiert das business Partner object. Sie wird von den - * Invoice Plugins genutzt. - * Die Methode wirft eine PluginException falls das BusinessPartner Objekt - * gesperrt oder in verification ist! - * + * Invoice Plugins genutzt. Die Methode wirft eine PluginException falls das + * BusinessPartner Objekt gesperrt oder in verification ist! + * * Gleichzeitig wird bei Rechnungen in Abhängigkeit der Rechnungsart die * dbtr./cdtr. items sowie mail und country aktualisiert. - * + * * @param partnerID * @param workitem * @throws PluginException @@ -145,8 +144,18 @@ public class BusinessPartnerService { workitem.setItemValue("partner.id", partnerID); } + if (partnerID == null || partnerID.isEmpty()) { + return; + } // load Business Partner ItemCollection businessPartner = getBusinessPartnerByID(partnerID); + if (businessPartner == null) { + return; + } + // because the method can be called multiple times within one worklfow + // processing cycle + // we need to load the object within the current transaction context + businessPartner = documentService.load(businessPartner.getUniqueID()); if (businessPartner != null) { workitem.setItemValue("partner.name", businessPartner.getItemValueString("partner.name")); @@ -166,13 +175,11 @@ public class BusinessPartnerService { // Validate Business Partner Status! if (businessPartner.getTaskID() == TASK_VERIFICATION) { - throw new PluginException(InvoicePlugin.class.getName(), - ERROR_BUSINESSPARTNER_VERIFICATION, + throw new PluginException(InvoicePlugin.class.getName(), ERROR_BUSINESSPARTNER_VERIFICATION, "Businesspartner is in verification. Invoice can't be processed!"); } if (businessPartner.getTaskID() == TASK_LOCKED) { - throw new PluginException(InvoicePlugin.class.getName(), - ERROR_BUSINESSPARTNER_LOCKED, + throw new PluginException(InvoicePlugin.class.getName(), ERROR_BUSINESSPARTNER_LOCKED, "Businesspartner is locked. Invoice can't be processed!"); } // update business partner status... @@ -195,7 +202,7 @@ public class BusinessPartnerService { /** * Returns the stubs of all outgoing invoices - * + * * @param _dbtrNumber * @return */ @@ -224,9 +231,8 @@ public class BusinessPartnerService { /** * Diese Methode sucht einen Business Partner anhand seiner BPID - * - * @param bpid - * - business partner id phrase + * + * @param bpid - business partner id phrase * @return - matching business partner */ public ItemCollection getBusinessPartnerByID(String bpid) { @@ -250,9 +256,8 @@ public class BusinessPartnerService { /** * Diese Methode sucht Business Partner anhand einer Suchphrase - * - * @param phrase - * - search phrase + * + * @param phrase - search phrase * @return - list of matching business partners */ public List search(String phrase) { @@ -283,7 +288,7 @@ public class BusinessPartnerService { /** * Packt die Liste der Bank Details (ItemCollections) in ein Workitem - * + * * @param workitem */ public static void implodeBanks(ItemCollection workitem, List ibanList) { @@ -304,7 +309,7 @@ public class BusinessPartnerService { /** * Enpackt die iban Map Liste von eiem Worktiem in eine Liste von ItemCollection - * + * * @param workitem */ public static List explodeBanks(ItemCollection workitem) { @@ -325,7 +330,7 @@ public class BusinessPartnerService { /** * Liefert die ChildItems mit den IBAN daten - * + * * @param workitem * @return */ @@ -345,7 +350,7 @@ public class BusinessPartnerService { /** * Diese Methode übertragt neue IBAN/BIC Pare in den Kreditor Banken Bereich - * + * * @param cdtrNumber * @param iban * @param bic @@ -374,7 +379,7 @@ public class BusinessPartnerService { /** * Diese Methode gibt true zurück wenn die angegebene IBAN/BIC Kombination als * bank bereits bekannt ist - * + * * @param cdtrNumber * @param iban * @param bic @@ -386,8 +391,7 @@ public class BusinessPartnerService { List bankenListe = explodeBanks(businessPartner); for (ItemCollection bank : bankenListe) { - if (iban.equals(bank.getItemValueString("iban")) - && bic.equals(bank.getItemValueString("bic"))) { + if (iban.equals(bank.getItemValueString("iban")) && bic.equals(bank.getItemValueString("bic"))) { // known! return true; } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftExportAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftExportAdapter.java index eca0fdc..2db1e2e 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftExportAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftExportAdapter.java @@ -25,18 +25,18 @@ import jakarta.inject.Inject; *

* report = report definition to transfers the workitem into the cargosoft xml * structure - * + * *

  * {@code
         cargosoft
-       
+
    }
  * 
*

* Because we also export the attachment data to cargosoft, the adaper lookups * the conente of the attachment in the snapshot of the origin workitem - * - * + * + * * @version 1.0 * @author rsoika */ @@ -113,8 +113,8 @@ public class CargosoftExportAdapter implements SignalAdapter { // finally append the success event id document.event(EVENT_SUCCESS); - } catch (PluginException | IOException - | jakarta.xml.bind.JAXBException | javax.xml.transform.TransformerException e) { + } catch (PluginException | IOException | jakarta.xml.bind.JAXBException + | javax.xml.transform.TransformerException e) { logger.severe("cargosoft export failed: " + e.getMessage()); document.setItemValue("cargosoft.error", e.getMessage()); document.event(EVENT_FAILURE); @@ -130,8 +130,8 @@ public class CargosoftExportAdapter implements SignalAdapter { * Change 14.10.2021 - Wir senden maximal 5MB and Daten, da die Cargosoft * Schnittstelle größere Datensätze mit großen Dateianhängen einfach verschluckt * ohne eine Fehlermeldugn zu liefern. - * - * + * + * * @param document * @return */ diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftSplitAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftSplitAdapter.java index 6ddc396..51eb38f 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftSplitAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CargosoftSplitAdapter.java @@ -32,7 +32,7 @@ import jakarta.inject.Inject; * ein neue Sequencenummer die im Hautpworkitem gespeichert wird. *

* Der Adapter wird im Modell wie folgt konfiguriert - * + * *

  * {@code
         
@@ -41,14 +41,14 @@ import jakarta.inject.Inject;
             100
             (?!txtworkflowhistory)(^[a-zA-Z]|^_)
         
-       
+
    }
  * 
*

* Because we also export the attachment data to cargosoft, the adaper lookups * the conente of the attachment in the snapshot of the origin workitem - * - * + * + * * @version 1.0 * @author rsoika */ @@ -65,7 +65,7 @@ public class CargosoftSplitAdapter implements SignalAdapter { /** * This method computes the cargosoft export data file - * + * * @throws PluginException */ @SuppressWarnings("rawtypes") @@ -189,21 +189,21 @@ public class CargosoftSplitAdapter implements SignalAdapter { *

* Example: {@code * txttitle,txtfirstname - * + * * txttitle|newitem1,txtfirstname|newitem2 - * + * * } - * + * *

* Optional also reg expressions are supported. In this case mapping of the item * name is not supported. *

* Example: {@code * (^artikel$|^invoice$),txtTitel|txtNewTitel - * - * + * + * * } A reg expression must be includes in brackets. - * + * */ protected void copyItemList(String items, ItemCollection source, ItemCollection target) { // clone the field list... diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CustomSearchController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CustomSearchController.java index c8eafa0..757b127 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CustomSearchController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/CustomSearchController.java @@ -1,285 +1,283 @@ -/******************************************************************************* - * Imixs Workflow Technology - * Copyright (C) 2003, 2008 Imixs Software Solutions GmbH, - * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You can receive a copy of the GNU General Public - * License at http://www.gnu.org/licenses/gpl.html - * - * Contributors: - * Imixs Software Solutions GmbH - initial API and implementation - * Ralph Soika - * - *******************************************************************************/ -package com.alexanderlogistics; - -import java.io.Serializable; -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.logging.Logger; - -import org.imixs.marty.team.TeamController; -import org.imixs.workflow.ItemCollection; -import org.imixs.workflow.office.views.SearchController; -import org.imixs.workflow.office.views.SearchEvent; - -import jakarta.enterprise.context.SessionScoped; -import jakarta.enterprise.event.Observes; -import jakarta.inject.Inject; -import jakarta.inject.Named; - -/** - * Der CustomSearchController reagiert auf SeachEvents und erweitert das Search - * Query um Rechnungbetrag von-bis. - * - * @author rsoika - * @version 1.0 - */ - -@Named -@SessionScoped -// @RequestScoped -public class CustomSearchController implements Serializable { - - private static final long serialVersionUID = 1L; - - private static Logger logger = Logger.getLogger(CustomSearchController.class.getName()); - - @Inject - SearchController searchController; - - @Inject - TeamController teamController; - - boolean isArchiveSearch = false; - - public void init() { - isArchiveSearch = true; - } - - /** - * Diese Suche schränkt immer auf Archive und Rechnugnscontrolling ein!! - * - * @param searchEvent - */ - public void onSearchEvent(@Observes SearchEvent searchEvent) { - - if (!isArchiveSearch) { - // Keine Anpassung für andere Prozesse - return; - } - - String query = searchEvent.getQuery(); - float invoiceTotal = searchEvent.getSearchFilter().getItemValueFloat("invoice.total"); - Date invoiceDate = searchEvent.getSearchFilter().getItemValueDate("invoice.date"); - Date dueDate = searchEvent.getSearchFilter().getItemValueDate("invoice.duedate"); - Date closingDate = searchEvent.getSearchFilter().getItemValueDate("invoice.closingdate"); - - String cdtrNumber = searchEvent.getSearchFilter().getItemValueString("cdtr.number").trim(); - String invoiceType = searchEvent.getSearchFilter().getItemValueString("invoice.type"); - String invoiceCurrency = searchEvent.getSearchFilter().getItemValueString("invoice.currency").trim(); - - String invoiceNumber = searchEvent.getSearchFilter().getItemValueString("invoice.number"); - - String paymentType = searchEvent.getSearchFilter().getItemValueString("payment.type").trim(); - String sequenceNumber = searchEvent.getSearchFilter().getItemValueString("sequencenumber").trim(); - - query += " (type:\"workitemarchive\") "; - // process ref= Rechnugnscontorling Prozess - ItemCollection process = teamController.getProcessByName("Rechnungscontrolling"); - if (process == null) { - process = teamController.getProcessByName("Invoice Controlling"); - } - if (process != null) { - query += " AND ($uniqueidref:\"" + process.getUniqueID() + "\") "; - } - - // invoice.total - if (invoiceTotal != 0) { - // format form and to with at least one digit - DecimalFormat decimalFormat = new DecimalFormat("#.00"); - query += " AND (invoice.total:" + decimalFormat.format(invoiceTotal) + ")"; - - } - - // invoice.date - if (invoiceDate != null) { - query += " AND (invoice.date:" + CustomSearchController.getRangeDay(invoiceDate) + ")"; - - } - - // invoice.duedate - if (dueDate != null) { - query += " AND (invoice.duedate:" + CustomSearchController.getRangeDay(dueDate) + ")"; - - } - - // invoice.closingdate - if (closingDate != null) { - query += " AND ($lasteventdate:" + CustomSearchController.getRangeMonth(closingDate) + ")"; - } - - if (invoiceNumber != null && !invoiceNumber.isEmpty()) { - query += " AND (invoice.number:" + invoiceNumber + ")"; - } - - if (paymentType != null && !paymentType.isEmpty()) { - query += " AND (payment.type:" + paymentType + ")"; - } - if (sequenceNumber != null && !sequenceNumber.isEmpty()) { - query += " AND (sequencenumber:" + sequenceNumber + ")"; - } - - if (invoiceCurrency != null && !invoiceCurrency.isEmpty()) { - query += " AND (invoice.currency:" + invoiceCurrency + ")"; - } - - // Rechnungsart - if (invoiceType != null && !"0".equals(invoiceType)) { - if ("1".equals(invoiceType)) { - query += " AND ($workflowgroup:\"Rechnungseingang\" AND NOT payment.type:credit)"; - } - if ("2".equals(invoiceType)) { - query += " AND ($workflowgroup:Sachrechnung)"; - } - if ("3".equals(invoiceType)) { - query += " AND (($workflowgroup:Rechnungseingang) AND (payment.type:credit))"; - } - if ("4".equals(invoiceType)) { - query += " AND ($workflowgroup:\"Gutschrift Abgleich\")"; - } - } - - // dbtr.number - if (cdtrNumber != null && !cdtrNumber.isEmpty()) { - cdtrNumber = cdtrNumber.trim().toUpperCase(); - if (!cdtrNumber.startsWith("K") && !cdtrNumber.startsWith("D")) { - query += " AND (cdtr.number:K" + cdtrNumber + " OR dbtr.number:" + cdtrNumber + ")"; - } else { - if (cdtrNumber.startsWith("K")) { - // suche nach Kreditor (Eingangsrechnung) - query += " AND (cdtr.number:" + cdtrNumber + ")"; - } else { - // suche nach Debitor (Ausgangsrechnung) - if (cdtrNumber.startsWith("D")) { - cdtrNumber = cdtrNumber.substring(1); - } - query += " AND (dbtr.number:" + cdtrNumber + ")"; - } - } - } - - logger.fine("Custom Query=" + query); - // Udpate query - searchEvent.setQuery(query); - - } - - /** - * Sonder AGL Lösung um im Archiv zu bleiben. - * - */ - public String refreshAGLArchive() { - String phrase = searchController.getSearchFilter().getItemValueString("phrase"); - searchController.refreshSearch(); - searchController.getSearchFilter().setItemValue("type", "workitemarchive"); - String action = "/pages/workitems/agl_archive.xhtml?faces-redirect=true&phrase=" + phrase; - return action; - - // String phrase = - // searchController.getSearchFilter().getItemValueString("phrase"); - - // searchController.getSearchFilter().setItemValue("type", "workitemarchive"); - // try { - // phrase = URLEncoder.encode(phrase, "UTF-8"); - // } catch (UnsupportedEncodingException e) { - // logger.severe("unable to encode search phrase!"); - // e.printStackTrace(); - // } - // String action = - // "/pages/workitems/agl_archive.xhtml?faces-redirect=true&phrase=" + phrase; - // searchController.setPageIndex(0); - // return action; - } - - /** - * This method reset the search filter and the search pageIndex and creates a - * new bookmarkable search link to the worklist including the current search - * phrase. - * - */ - public String resetSearch() { - searchController.reset(); - return refreshAGLArchive(); - } - - private static String getRangeDay(Date dueDate) { - // Setze die Uhrzeit auf Mitternacht (00:00) - Calendar startCalendar = Calendar.getInstance(); - startCalendar.setTime(dueDate); - startCalendar.set(Calendar.HOUR_OF_DAY, 0); - startCalendar.set(Calendar.MINUTE, 0); - startCalendar.set(Calendar.SECOND, 0); - startCalendar.set(Calendar.MILLISECOND, 0); - - Date startDate = startCalendar.getTime(); - - // Setze die Uhrzeit auf 23:59:59.999 - Calendar endCalendar = Calendar.getInstance(); - endCalendar.setTime(dueDate); - endCalendar.set(Calendar.HOUR_OF_DAY, 23); - endCalendar.set(Calendar.MINUTE, 59); - endCalendar.set(Calendar.SECOND, 59); - endCalendar.set(Calendar.MILLISECOND, 999); - - Date endDate = endCalendar.getTime(); - SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMddHHmm"); - String dateRange = "[" + dateformat.format(startDate) + " TO " - + dateformat.format(endDate) + "]"; - - return dateRange; - } - - private static String getRangeMonth(Date dueDate) { - // Setze die Uhrzeit auf Mitternacht (00:00) - Calendar startCalendar = Calendar.getInstance(); - startCalendar.setTime(dueDate); - startCalendar.set(Calendar.HOUR_OF_DAY, 0); - startCalendar.set(Calendar.MINUTE, 0); - startCalendar.set(Calendar.SECOND, 0); - startCalendar.set(Calendar.MILLISECOND, 0); - startCalendar.set(Calendar.DAY_OF_MONTH, 1); - - Date startDate = startCalendar.getTime(); - - // Setze die Uhrzeit auf 23:59:59.999 - Calendar endCalendar = Calendar.getInstance(); - endCalendar.setTime(dueDate); - endCalendar.set(Calendar.HOUR_OF_DAY, 23); - endCalendar.set(Calendar.MINUTE, 59); - endCalendar.set(Calendar.SECOND, 59); - endCalendar.set(Calendar.MILLISECOND, 999); - endCalendar.set(Calendar.DAY_OF_MONTH, 1); - endCalendar.add(Calendar.MONTH, 1); - endCalendar.add(Calendar.DAY_OF_MONTH, -1); - - Date endDate = endCalendar.getTime(); - SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMddHHmm"); - String dateRange = "[" + dateformat.format(startDate) + " TO " - + dateformat.format(endDate) + "]"; - - return dateRange; - } -} +/******************************************************************************* + * Imixs Workflow Technology + * Copyright (C) 2003, 2008 Imixs Software Solutions GmbH, + * http://www.imixs.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You can receive a copy of the GNU General Public + * License at http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Imixs Software Solutions GmbH - initial API and implementation + * Ralph Soika + * + *******************************************************************************/ +package com.alexanderlogistics; + +import java.io.Serializable; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.logging.Logger; + +import org.imixs.marty.team.TeamController; +import org.imixs.workflow.ItemCollection; +import org.imixs.workflow.office.views.SearchController; +import org.imixs.workflow.office.views.SearchEvent; + +import jakarta.enterprise.context.SessionScoped; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import jakarta.inject.Named; + +/** + * Der CustomSearchController reagiert auf SeachEvents und erweitert das Search + * Query um Rechnungbetrag von-bis. + * + * @author rsoika + * @version 1.0 + */ + +@Named +@SessionScoped +// @RequestScoped +public class CustomSearchController implements Serializable { + + private static final long serialVersionUID = 1L; + + private static Logger logger = Logger.getLogger(CustomSearchController.class.getName()); + + @Inject + SearchController searchController; + + @Inject + TeamController teamController; + + boolean isArchiveSearch = false; + + public void init() { + isArchiveSearch = true; + } + + /** + * Diese Suche schränkt immer auf Archive und Rechnugnscontrolling ein!! + * + * @param searchEvent + */ + public void onSearchEvent(@Observes SearchEvent searchEvent) { + + if (!isArchiveSearch) { + // Keine Anpassung für andere Prozesse + return; + } + + String query = searchEvent.getQuery(); + float invoiceTotal = searchEvent.getSearchFilter().getItemValueFloat("invoice.total"); + Date invoiceDate = searchEvent.getSearchFilter().getItemValueDate("invoice.date"); + Date dueDate = searchEvent.getSearchFilter().getItemValueDate("invoice.duedate"); + Date closingDate = searchEvent.getSearchFilter().getItemValueDate("invoice.closingdate"); + + String cdtrNumber = searchEvent.getSearchFilter().getItemValueString("cdtr.number").trim(); + String invoiceType = searchEvent.getSearchFilter().getItemValueString("invoice.type"); + String invoiceCurrency = searchEvent.getSearchFilter().getItemValueString("invoice.currency").trim(); + + String invoiceNumber = searchEvent.getSearchFilter().getItemValueString("invoice.number"); + + String paymentType = searchEvent.getSearchFilter().getItemValueString("payment.type").trim(); + String sequenceNumber = searchEvent.getSearchFilter().getItemValueString("sequencenumber").trim(); + + query += " (type:\"workitemarchive\") "; + // process ref= Rechnugnscontorling Prozess + ItemCollection process = teamController.getProcessByName("Rechnungscontrolling"); + if (process == null) { + process = teamController.getProcessByName("Invoice Controlling"); + } + if (process != null) { + query += " AND ($uniqueidref:\"" + process.getUniqueID() + "\") "; + } + + // invoice.total + if (invoiceTotal != 0) { + // format form and to with at least one digit + DecimalFormat decimalFormat = new DecimalFormat("#.00"); + query += " AND (invoice.total:" + decimalFormat.format(invoiceTotal) + ")"; + + } + + // invoice.date + if (invoiceDate != null) { + query += " AND (invoice.date:" + CustomSearchController.getRangeDay(invoiceDate) + ")"; + + } + + // invoice.duedate + if (dueDate != null) { + query += " AND (invoice.duedate:" + CustomSearchController.getRangeDay(dueDate) + ")"; + + } + + // invoice.closingdate + if (closingDate != null) { + query += " AND ($lasteventdate:" + CustomSearchController.getRangeMonth(closingDate) + ")"; + } + + if (invoiceNumber != null && !invoiceNumber.isEmpty()) { + query += " AND (invoice.number:" + invoiceNumber + ")"; + } + + if (paymentType != null && !paymentType.isEmpty()) { + query += " AND (payment.type:" + paymentType + ")"; + } + if (sequenceNumber != null && !sequenceNumber.isEmpty()) { + query += " AND (sequencenumber:" + sequenceNumber + ")"; + } + + if (invoiceCurrency != null && !invoiceCurrency.isEmpty()) { + query += " AND (invoice.currency:" + invoiceCurrency + ")"; + } + + // Rechnungsart + if (invoiceType != null && !"0".equals(invoiceType)) { + if ("1".equals(invoiceType)) { + query += " AND ($workflowgroup:\"Rechnungseingang\" AND NOT payment.type:credit)"; + } + if ("2".equals(invoiceType)) { + query += " AND ($workflowgroup:Sachrechnung)"; + } + if ("3".equals(invoiceType)) { + query += " AND (($workflowgroup:Rechnungseingang) AND (payment.type:credit))"; + } + if ("4".equals(invoiceType)) { + query += " AND ($workflowgroup:\"Gutschrift Abgleich\")"; + } + } + + // dbtr.number + if (cdtrNumber != null && !cdtrNumber.isEmpty()) { + cdtrNumber = cdtrNumber.trim().toUpperCase(); + if (!cdtrNumber.startsWith("K") && !cdtrNumber.startsWith("D")) { + query += " AND (cdtr.number:K" + cdtrNumber + " OR dbtr.number:" + cdtrNumber + ")"; + } else { + if (cdtrNumber.startsWith("K")) { + // suche nach Kreditor (Eingangsrechnung) + query += " AND (cdtr.number:" + cdtrNumber + ")"; + } else { + // suche nach Debitor (Ausgangsrechnung) + if (cdtrNumber.startsWith("D")) { + cdtrNumber = cdtrNumber.substring(1); + } + query += " AND (dbtr.number:" + cdtrNumber + ")"; + } + } + } + + logger.fine("Custom Query=" + query); + // Udpate query + searchEvent.setQuery(query); + + } + + /** + * Sonder AGL Lösung um im Archiv zu bleiben. + * + */ + public String refreshAGLArchive() { + String phrase = searchController.getSearchFilter().getItemValueString("phrase"); + searchController.refreshSearch(); + searchController.getSearchFilter().setItemValue("type", "workitemarchive"); + String action = "/pages/workitems/agl_archive.xhtml?faces-redirect=true&phrase=" + phrase; + return action; + + // String phrase = + // searchController.getSearchFilter().getItemValueString("phrase"); + + // searchController.getSearchFilter().setItemValue("type", "workitemarchive"); + // try { + // phrase = URLEncoder.encode(phrase, "UTF-8"); + // } catch (UnsupportedEncodingException e) { + // logger.severe("unable to encode search phrase!"); + // e.printStackTrace(); + // } + // String action = + // "/pages/workitems/agl_archive.xhtml?faces-redirect=true&phrase=" + phrase; + // searchController.setPageIndex(0); + // return action; + } + + /** + * This method reset the search filter and the search pageIndex and creates a + * new bookmarkable search link to the worklist including the current search + * phrase. + * + */ + public String resetSearch() { + searchController.reset(); + return refreshAGLArchive(); + } + + private static String getRangeDay(Date dueDate) { + // Setze die Uhrzeit auf Mitternacht (00:00) + Calendar startCalendar = Calendar.getInstance(); + startCalendar.setTime(dueDate); + startCalendar.set(Calendar.HOUR_OF_DAY, 0); + startCalendar.set(Calendar.MINUTE, 0); + startCalendar.set(Calendar.SECOND, 0); + startCalendar.set(Calendar.MILLISECOND, 0); + + Date startDate = startCalendar.getTime(); + + // Setze die Uhrzeit auf 23:59:59.999 + Calendar endCalendar = Calendar.getInstance(); + endCalendar.setTime(dueDate); + endCalendar.set(Calendar.HOUR_OF_DAY, 23); + endCalendar.set(Calendar.MINUTE, 59); + endCalendar.set(Calendar.SECOND, 59); + endCalendar.set(Calendar.MILLISECOND, 999); + + Date endDate = endCalendar.getTime(); + SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMddHHmm"); + String dateRange = "[" + dateformat.format(startDate) + " TO " + dateformat.format(endDate) + "]"; + + return dateRange; + } + + private static String getRangeMonth(Date dueDate) { + // Setze die Uhrzeit auf Mitternacht (00:00) + Calendar startCalendar = Calendar.getInstance(); + startCalendar.setTime(dueDate); + startCalendar.set(Calendar.HOUR_OF_DAY, 0); + startCalendar.set(Calendar.MINUTE, 0); + startCalendar.set(Calendar.SECOND, 0); + startCalendar.set(Calendar.MILLISECOND, 0); + startCalendar.set(Calendar.DAY_OF_MONTH, 1); + + Date startDate = startCalendar.getTime(); + + // Setze die Uhrzeit auf 23:59:59.999 + Calendar endCalendar = Calendar.getInstance(); + endCalendar.setTime(dueDate); + endCalendar.set(Calendar.HOUR_OF_DAY, 23); + endCalendar.set(Calendar.MINUTE, 59); + endCalendar.set(Calendar.SECOND, 59); + endCalendar.set(Calendar.MILLISECOND, 999); + endCalendar.set(Calendar.DAY_OF_MONTH, 1); + endCalendar.add(Calendar.MONTH, 1); + endCalendar.add(Calendar.DAY_OF_MONTH, -1); + + Date endDate = endCalendar.getTime(); + SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMddHHmm"); + String dateRange = "[" + dateformat.format(startDate) + " TO " + dateformat.format(endDate) + "]"; + + return dateRange; + } +} diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/DebitorStatistikController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/DebitorStatistikController.java index 1275762..a30698a 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/DebitorStatistikController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/DebitorStatistikController.java @@ -33,7 +33,7 @@ import jakarta.inject.Named; /** * Der InvoiceAnalyseController dient zur Auswertung der Sachprüfungs WOrklfow * Schritte. Die Analyse Seite ist büer das Admin Menü erreichbar. - * + * * @author rsoika * */ @@ -42,542 +42,533 @@ import jakarta.inject.Named; // @SessionScoped public class DebitorStatistikController implements Serializable { - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(DebitorStatistikController.class.getName()); - - @Inject - protected DocumentService documentService; - - TreeMap stats = null; - - @Inject - protected WorkflowController workflowController; - - @Inject - protected SearchController searchController; - - @Inject - protected BusinessPartnerService businessPartnerService; - - /** - * Initialize default behavior initialize an opitonal id from the query string - * - */ - @PostConstruct - public void init() { - // extract the processref and page from the query string - FacesContext fc = FacesContext.getCurrentInstance(); - Map paramMap = fc.getExternalContext().getRequestParameterMap(); - - String id = paramMap.get("dbtr.number"); - if (id != null && !id.isEmpty()) { - workflowController.getWorkitem().setItemValue("dbtr.number", id); - - ItemCollection dbtr = businessPartnerService.getBusinessPartnerByID(InvoiceUtil.buildBPID(id)); - // ItemCollection dbtr = kreditorDebitorService.findDebitor(id); - if (dbtr != null) { - workflowController.getWorkitem().setItemValue("dbtr.name", dbtr.getItemValueString("partner.name")); - } - - } - } - - public ItemCollection getWorkitem() { - return workflowController.getWorkitem(); - } - - public int getZahlungsmoral() { - return 0; - } - - /** - * Gibt eine bookmarkable url zurück. - * Wir vom Aktualisieren button in der Page genutzt - * - * @return - */ - public String getBookmark() { - return "/pages/dbtr-stats.xhtml?dbtr.number=" + getDbtNr() + "&faces-redirect=true"; - } - - /* - * Gesamt - */ - public String getQueryRechnungen() { - String query = "(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\""; - return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " gesamt&query=" + query; - } - - public int getRechnungen() { - int result = 0; - if (getDbtNr().isEmpty()) { - return 0; - } - try { - result = documentService - .count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\""); - } catch (QueryException e) { - logger.severe("Failed to get statistic: " + e.getMessage()); - } - return result; - } - - /* - * Bezahlt - */ - public String getQueryRechnungenBezahlt() { - String query = "(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5900 TO 5999])"; - return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " bezahlt&query=" + query; - } - - public int getRechnungenBezahlt() { - int result = 0; - if (getDbtNr().isEmpty()) { - return 0; - } - try { - result = documentService - .count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5900 TO 5999])"); - } catch (QueryException e) { - logger.severe("Failed to get statistic: " + e.getMessage()); - } - return result; - } - - /* - * Offen - */ - public String getQueryRechnungenOffen() { - String query = "(type:workitem) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5000 TO 5099])"; - return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " offen&query=" + query; - } - - public int getRechnungenOffen() { - int result = 0; - if (getDbtNr().isEmpty()) { - return 0; - } - try { - result = documentService - .count("(type:workitem) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5000 TO 5099])"); - } catch (QueryException e) { - logger.severe("Failed to get statistic: " + e.getMessage()); - } - return result; - } - - /* - * Fällig - */ - public String getQueryRechnungenFaellig() { - String query = "(type:workitem) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5100 TO 5199])"; - return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " fällig&query=" + query; - } - - public int getRechnungenFaellig() { - int result = 0; - if (getDbtNr().isEmpty()) { - return 0; - } - try { - result = documentService - .count("(type:workitem) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5100 TO 5199])"); - } catch (QueryException e) { - logger.severe("Failed to get statistic: " + e.getMessage()); - } - return result; - } - - /* - * In Mahnung - */ - public String getQueryRechnungenGemahnt() { - String query = "(type:workitem) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5200 TO 5299])"; - return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " in Mahnung&query=" + query; - } - - public int getRechnungenGemahnt() { - int result = 0; - if (getDbtNr().isEmpty()) { - return 0; - } - try { - result = documentService - .count("(type:workitem ) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5200 TO 5299])"); - } catch (QueryException e) { - logger.severe("Failed to get statistic: " + e.getMessage()); - } - return result; - } - - /* - * Inkasso - */ - public String getQueryRechnungenInkasso() { - String query = "(type:workitem) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5300 TO 5399])"; - return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " in Inkasso&query=" + query; - } - - public int getRechnungenInkasso() { - int result = 0; - if (getDbtNr().isEmpty()) { - return 0; - } - try { - result = documentService - .count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5300 TO 5399])"); - } catch (QueryException e) { - logger.severe("Failed to get statistic: " + e.getMessage()); - } - return result; - } - - /** - * Hilfsmethode die das fuehrende K/D aus der Debitorennummer entfernt - * - * @return - */ - public String getDbtNr() { - String dbtNr = getWorkitem().getItemValueString("dbtr.number"); - if (dbtNr.startsWith("D") || dbtNr.startsWith("K")) { - dbtNr = dbtNr.substring(1); - } - return dbtNr; - } - - public long getDurchschnittZahlungsziel() { - getStats(); - int count = 0; - long gesamt = 0; - for (Map.Entry entry : stats.entrySet()) { - DebitorStatistikData statData = entry.getValue(); - if (statData.count > 0) { - gesamt = gesamt + statData.getAverageDueDays(); - count++; - } - } - return Math.round((double) gesamt / count); - } - - public long getDurchschnittZahlungsdauer() { - getStats(); - int count = 0; - long gesamt = 0; - for (Map.Entry entry : stats.entrySet()) { - DebitorStatistikData statData = entry.getValue(); - if (statData.count > 0) { - gesamt = gesamt + statData.getAveragePaymentDays(); - count++; - } - } - return Math.round((double) gesamt / count); - } - - /** - * Sucht alle Rechnugnen aus einem Zeitraum und sammelt Zahlungsziel und - * Zahlungszeitspanne gruppiert nach monaten - * - */ - public Map getStats() { - - if (stats != null) { - return stats; - } - - // recompute - stats = new TreeMap(); - if (getDbtNr().isEmpty()) { - return stats; - } - // invoice.date:[20210101 TO 20210201] - DateFormat df = new SimpleDateFormat("yyyymmdd"); - - String query = "(type:workitemarchive) AND invoice.date:[20221201 TO 20500101] AND dbtr.number:" + getDbtNr() - + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5900 TO 5999])"; - - // query = query + " AND (invoice.date:[" + df.format(from) + " TO " + - // df.format(to) + "])"; - logger.fine("query = " + query); - try { - List invoices = documentService - .findStubs(query, 9999, 0, "$created", false); - - for (ItemCollection invoice : invoices) { - try { - - logger.fine("Rechnung: " + invoice.getUniqueID()); - Date invoiceDate = invoice.getItemValueDate("invoice.date"); - Date invoiceDueDate = invoice.getItemValueDate("invoice.duedate"); - Date paymentDate = findPaymentDateByWorkitem(invoice); - if (paymentDate == null) { - logger.warning("Es wurde kein Zahlungseingang gefunden"); - continue; - } - - if (invoiceDate != null && paymentDate != null && invoiceDueDate != null) { - // do we have a stats object? - DebitorStatistikData statData = stats.get(getYearMonth(invoiceDate)); - if (statData == null) { - // create a new one - statData = new DebitorStatistikData(invoiceDate); - } - // jetzt irgendwas ausrechnen - statData.compute(invoiceDate, invoiceDueDate, paymentDate); - // .. und wieder speichern - stats.put(statData.toString(), statData); - - } else { - logger.warning("No payment Date found for invoice " + invoice.getUniqueID()); - } - - } catch (ParseException e) { - logger.warning("Unable to parse payment Date for invoice " + invoice.getUniqueID()); - } - } - - } catch (QueryException e) { - logger.severe("Failed to get statistic: " + e.getMessage()); - } - // Step 2: Sort the list based on the alphanumeric order of the keys - completeMissingMonths(); - return stats; - - } - - /** - * Hilfsmethode ergänzt die fehlenden Monate - * - * @param yearMonths - * @return - */ - private void completeMissingMonths() { - if (stats == null || stats.size() < 3) { - return; - } - - // Convert the list of strings to a list of LocalDate objects - List dates = new ArrayList<>(); - Set yearMonths = this.stats.keySet(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM"); - for (String yearMonth : yearMonths) { - dates.add(YearMonth.parse(yearMonth, formatter).atDay(1)); - } - - // Find the lowest and highest dates - LocalDate lowestDate = Collections.min(dates); - LocalDate highestDate = Collections.max(dates); - - LocalDate current = lowestDate; - current = current.plusMonths(1); - while (!current.isAfter(highestDate)) { - - // existiert der monat? - String formattedDate = current.format(formatter); - DebitorStatistikData entry = stats.get(formattedDate); - if (entry == null) { - // add missing entry - stats.put(formattedDate, new DebitorStatistikData(current.getYear(), current.getMonthValue())); - } - current = current.plusMonths(1); - - } - - } - - /** - * returns - * - * 202304 from a given date - */ - public String getYearMonth(Date date) { - - // Create a Calendar instance and set the date - Calendar calendar = Calendar.getInstance(); - calendar.setTime(date); - - // Get the year from the Calendar object - int year = calendar.get(Calendar.YEAR); - // Get the month from the Calendar object - int month = calendar.get(Calendar.MONTH); - // Increment the month by 1 since Calendar months are zero-based - month++; - // Convert the month to a String with leading "0" if necessary - return "" + year + (month < 10 ? "0" + month : "" + month); - - } - - /** - * Finds the payment.date for a invoice. - * - * Wir suchen alle zugeordneten Zahlungseingägne und nehmen den letzten. - * - * WICHTIG: Es muss ggf. der index neu aufgebaut werden, da payment.date nun ein - * index feld ist - * - * @param invoice - * @return - * @throws ParseException - */ - public Date findPaymentDateByWorkitem(ItemCollection invoice) throws ParseException { - - // Wir selektieren alle Zahlungseingänge interessieren usn aber nur für den - // letzten - String sQuery = " (type:\"workitem\" OR type:\"workitemarchive\") " + // - " AND ($workflowgroup:Zahlungseingang) AND ($workitemref:\"" - + invoice.getUniqueID() + "\" )"; - - List workitems = null; - - try { - workitems = documentService.findStubs(sQuery, 99, 0, - "payment.date", true); - if (workitems.size() > 0) { - return workitems.get(0).getItemValueDate("payment.date"); - } - } catch (QueryException e) { - - e.printStackTrace(); - } - - // no date found! - return null; - - } - - /** - * Finds the date when a workitem last reached the current task 5900 by reading - * the eventLog - * - * This method is not so save as the method findPaymentDateByWorkitem that is - * searching the payment workitem. - * - * 2023-06-28T12:40:17.437|rechnungsausgang-de-1.0|5001.50|5900| - * - * @param invoice - * @return - * @throws ParseException - */ - public Date findPaymentDateByLog(ItemCollection invoice) throws ParseException { - - // Create a SimpleDateFormat instance with the desired format - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); - List logEntries = invoice.getItemValue("$eventlog"); - - // Iterate through the List from the last element to the first - for (int i = logEntries.size() - 1; i >= 0; i--) { - String entry = logEntries.get(i); - - if (entry.endsWith(invoice.getTaskID() + "|")) { - // found! - String dateString = entry.substring(0, entry.indexOf("|") - 1); - // Parse the date string into a Date object - Date date = sdf.parse(dateString); - // Print the Date object - System.out.println("Date: " + date); - return date; - } - } - - // no date found! - return null; - - } - - /** - * Diese Methode baut die Datenstruktur für das Chart Diagram zusammen - * - * - *

-		{
-		 labels: ["January", "February", "March", "April", "May", "June", "July"],
-		 datasets: [{
-			label: 'Dataset 1',
-			//backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
-			//borderColor: window.chartColors.red,
-			borderWidth: 1,
-			data: [
-				70, 70, 70, 70, 79, 50, 50
-			]
-		}, {
-			label: 'Dataset 2',
-			//backgroundColor: color(window.chartColors.blue).alpha(0.5).rgbString(),
-			//borderColor: window.chartColors.blue,
-			borderWidth: 1,
-			data: [
-				70, 70, 170, 7, 79, 50, 50
-			]
-		}]
-	   }
-	 * 
- * - * @return - */ - public String buildChartData() { - - getStats(); - - // build a list of all lables.... - List statusLabels = new ArrayList(); - - Set keys = stats.keySet(); - for (String _key : keys) { - statusLabels.add(_key); - } - - String result = "{"; - - // Lables - result = result + "labels : [ "; - result = result + statusLabels.stream().collect(Collectors.joining("','", "'", "'")); - result = result + "],"; - result = result + "datasets: ["; - - // Datasets 1 - result = result + "{label: 'Zahlungsziel',borderWidth: 1,"; - result = result + " borderColor: [\"#3B6B82\"],"; - result = result + " \"backgroundColor\" : [\"#CFE9F5\"], fill: true,tension: 0.5,"; - - result = result + "data: ["; - for (Map.Entry entry : stats.entrySet()) { - result = result + entry.getValue().getAverageDueDays() + ","; - } - // cut last comma - result = result.substring(0, result.length() - 1); - result = result + "]"; - result = result + "}, "; - - // Datasets 2 - result = result + "{label: 'Zahldauer',borderWidth: 1,"; - result = result + " borderColor: [\"#E73B65\"],\"backgroundColor\" : [\"#70B088\" ], tension: 0.5,fill: true,"; - - result = result - + " trendlineLinear: { colorMin: \"red\", colorMax: \"green\", lineStyle: \"dotted\", width: 2 , projection: true },"; - - result = result + "data: ["; - for (Map.Entry entry : stats.entrySet()) { - result = result + entry.getValue().getAveragePaymentDays() + ","; - } - // cut last comma - result = result.substring(0, result.length() - 1); - result = result + "]"; - result = result + "} "; - - // ende - result = result + "] }"; - - return result; - } + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(DebitorStatistikController.class.getName()); + + @Inject + protected DocumentService documentService; + + TreeMap stats = null; + + @Inject + protected WorkflowController workflowController; + + @Inject + protected SearchController searchController; + + @Inject + protected BusinessPartnerService businessPartnerService; + + /** + * Initialize default behavior initialize an opitonal id from the query string + * + */ + @PostConstruct + public void init() { + // extract the processref and page from the query string + FacesContext fc = FacesContext.getCurrentInstance(); + Map paramMap = fc.getExternalContext().getRequestParameterMap(); + + String id = paramMap.get("dbtr.number"); + if (id != null && !id.isEmpty()) { + workflowController.getWorkitem().setItemValue("dbtr.number", id); + + ItemCollection dbtr = businessPartnerService.getBusinessPartnerByID(InvoiceUtil.buildBPID(id)); + // ItemCollection dbtr = kreditorDebitorService.findDebitor(id); + if (dbtr != null) { + workflowController.getWorkitem().setItemValue("dbtr.name", dbtr.getItemValueString("partner.name")); + } + + } + } + + public ItemCollection getWorkitem() { + return workflowController.getWorkitem(); + } + + public int getZahlungsmoral() { + return 0; + } + + /** + * Gibt eine bookmarkable url zurück. Wir vom Aktualisieren button in der Page + * genutzt + * + * @return + */ + public String getBookmark() { + return "/pages/dbtr-stats.xhtml?dbtr.number=" + getDbtNr() + "&faces-redirect=true"; + } + + /* + * Gesamt + */ + public String getQueryRechnungen() { + String query = "(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\""; + return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " gesamt&query=" + query; + } + + public int getRechnungen() { + int result = 0; + if (getDbtNr().isEmpty()) { + return 0; + } + try { + result = documentService.count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\""); + } catch (QueryException e) { + logger.severe("Failed to get statistic: " + e.getMessage()); + } + return result; + } + + /* + * Bezahlt + */ + public String getQueryRechnungenBezahlt() { + String query = "(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5900 TO 5999])"; + return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " bezahlt&query=" + query; + } + + public int getRechnungenBezahlt() { + int result = 0; + if (getDbtNr().isEmpty()) { + return 0; + } + try { + result = documentService.count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5900 TO 5999])"); + } catch (QueryException e) { + logger.severe("Failed to get statistic: " + e.getMessage()); + } + return result; + } + + /* + * Offen + */ + public String getQueryRechnungenOffen() { + String query = "(type:workitem) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5000 TO 5099])"; + return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " offen&query=" + query; + } + + public int getRechnungenOffen() { + int result = 0; + if (getDbtNr().isEmpty()) { + return 0; + } + try { + result = documentService.count("(type:workitem) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5000 TO 5099])"); + } catch (QueryException e) { + logger.severe("Failed to get statistic: " + e.getMessage()); + } + return result; + } + + /* + * Fällig + */ + public String getQueryRechnungenFaellig() { + String query = "(type:workitem) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5100 TO 5199])"; + return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " fällig&query=" + query; + } + + public int getRechnungenFaellig() { + int result = 0; + if (getDbtNr().isEmpty()) { + return 0; + } + try { + result = documentService.count("(type:workitem) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5100 TO 5199])"); + } catch (QueryException e) { + logger.severe("Failed to get statistic: " + e.getMessage()); + } + return result; + } + + /* + * In Mahnung + */ + public String getQueryRechnungenGemahnt() { + String query = "(type:workitem) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5200 TO 5299])"; + return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " in Mahnung&query=" + query; + } + + public int getRechnungenGemahnt() { + int result = 0; + if (getDbtNr().isEmpty()) { + return 0; + } + try { + result = documentService.count("(type:workitem ) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5200 TO 5299])"); + } catch (QueryException e) { + logger.severe("Failed to get statistic: " + e.getMessage()); + } + return result; + } + + /* + * Inkasso + */ + public String getQueryRechnungenInkasso() { + String query = "(type:workitem) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5300 TO 5399])"; + return "/pages/workitems/worklist?title=Rechnungen " + getDbtNr() + " in Inkasso&query=" + query; + } + + public int getRechnungenInkasso() { + int result = 0; + if (getDbtNr().isEmpty()) { + return 0; + } + try { + result = documentService.count("(type:workitem OR type:workitemarchive) AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5300 TO 5399])"); + } catch (QueryException e) { + logger.severe("Failed to get statistic: " + e.getMessage()); + } + return result; + } + + /** + * Hilfsmethode die das fuehrende K/D aus der Debitorennummer entfernt + * + * @return + */ + public String getDbtNr() { + String dbtNr = getWorkitem().getItemValueString("dbtr.number"); + if (dbtNr.startsWith("D") || dbtNr.startsWith("K")) { + dbtNr = dbtNr.substring(1); + } + return dbtNr; + } + + public long getDurchschnittZahlungsziel() { + getStats(); + int count = 0; + long gesamt = 0; + for (Map.Entry entry : stats.entrySet()) { + DebitorStatistikData statData = entry.getValue(); + if (statData.count > 0) { + gesamt = gesamt + statData.getAverageDueDays(); + count++; + } + } + return Math.round((double) gesamt / count); + } + + public long getDurchschnittZahlungsdauer() { + getStats(); + int count = 0; + long gesamt = 0; + for (Map.Entry entry : stats.entrySet()) { + DebitorStatistikData statData = entry.getValue(); + if (statData.count > 0) { + gesamt = gesamt + statData.getAveragePaymentDays(); + count++; + } + } + return Math.round((double) gesamt / count); + } + + /** + * Sucht alle Rechnugnen aus einem Zeitraum und sammelt Zahlungsziel und + * Zahlungszeitspanne gruppiert nach monaten + * + */ + public Map getStats() { + + if (stats != null) { + return stats; + } + + // recompute + stats = new TreeMap(); + if (getDbtNr().isEmpty()) { + return stats; + } + // invoice.date:[20210101 TO 20210201] + DateFormat df = new SimpleDateFormat("yyyymmdd"); + + String query = "(type:workitemarchive) AND invoice.date:[20221201 TO 20500101] AND dbtr.number:" + getDbtNr() + + " AND $workflowgroup:\"Rechnungsausgang\" AND ($taskid:[5900 TO 5999])"; + + // query = query + " AND (invoice.date:[" + df.format(from) + " TO " + + // df.format(to) + "])"; + logger.fine("query = " + query); + try { + List invoices = documentService.findStubs(query, 9999, 0, "$created", false); + + for (ItemCollection invoice : invoices) { + try { + + logger.fine("Rechnung: " + invoice.getUniqueID()); + Date invoiceDate = invoice.getItemValueDate("invoice.date"); + Date invoiceDueDate = invoice.getItemValueDate("invoice.duedate"); + Date paymentDate = findPaymentDateByWorkitem(invoice); + if (paymentDate == null) { + logger.warning("Es wurde kein Zahlungseingang gefunden"); + continue; + } + + if (invoiceDate != null && paymentDate != null && invoiceDueDate != null) { + // do we have a stats object? + DebitorStatistikData statData = stats.get(getYearMonth(invoiceDate)); + if (statData == null) { + // create a new one + statData = new DebitorStatistikData(invoiceDate); + } + // jetzt irgendwas ausrechnen + statData.compute(invoiceDate, invoiceDueDate, paymentDate); + // .. und wieder speichern + stats.put(statData.toString(), statData); + + } else { + logger.warning("No payment Date found for invoice " + invoice.getUniqueID()); + } + + } catch (ParseException e) { + logger.warning("Unable to parse payment Date for invoice " + invoice.getUniqueID()); + } + } + + } catch (QueryException e) { + logger.severe("Failed to get statistic: " + e.getMessage()); + } + // Step 2: Sort the list based on the alphanumeric order of the keys + completeMissingMonths(); + return stats; + + } + + /** + * Hilfsmethode ergänzt die fehlenden Monate + * + * @param yearMonths + * @return + */ + private void completeMissingMonths() { + if (stats == null || stats.size() < 3) { + return; + } + + // Convert the list of strings to a list of LocalDate objects + List dates = new ArrayList<>(); + Set yearMonths = this.stats.keySet(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM"); + for (String yearMonth : yearMonths) { + dates.add(YearMonth.parse(yearMonth, formatter).atDay(1)); + } + + // Find the lowest and highest dates + LocalDate lowestDate = Collections.min(dates); + LocalDate highestDate = Collections.max(dates); + + LocalDate current = lowestDate; + current = current.plusMonths(1); + while (!current.isAfter(highestDate)) { + + // existiert der monat? + String formattedDate = current.format(formatter); + DebitorStatistikData entry = stats.get(formattedDate); + if (entry == null) { + // add missing entry + stats.put(formattedDate, new DebitorStatistikData(current.getYear(), current.getMonthValue())); + } + current = current.plusMonths(1); + + } + + } + + /** + * returns + * + * 202304 from a given date + */ + public String getYearMonth(Date date) { + + // Create a Calendar instance and set the date + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + + // Get the year from the Calendar object + int year = calendar.get(Calendar.YEAR); + // Get the month from the Calendar object + int month = calendar.get(Calendar.MONTH); + // Increment the month by 1 since Calendar months are zero-based + month++; + // Convert the month to a String with leading "0" if necessary + return "" + year + (month < 10 ? "0" + month : "" + month); + + } + + /** + * Finds the payment.date for a invoice. + * + * Wir suchen alle zugeordneten Zahlungseingägne und nehmen den letzten. + * + * WICHTIG: Es muss ggf. der index neu aufgebaut werden, da payment.date nun ein + * index feld ist + * + * @param invoice + * @return + * @throws ParseException + */ + public Date findPaymentDateByWorkitem(ItemCollection invoice) throws ParseException { + + // Wir selektieren alle Zahlungseingänge interessieren usn aber nur für den + // letzten + String sQuery = " (type:\"workitem\" OR type:\"workitemarchive\") " + // + " AND ($workflowgroup:Zahlungseingang) AND ($workitemref:\"" + invoice.getUniqueID() + "\" )"; + + List workitems = null; + + try { + workitems = documentService.findStubs(sQuery, 99, 0, "payment.date", true); + if (workitems.size() > 0) { + return workitems.get(0).getItemValueDate("payment.date"); + } + } catch (QueryException e) { + + e.printStackTrace(); + } + + // no date found! + return null; + + } + + /** + * Finds the date when a workitem last reached the current task 5900 by reading + * the eventLog + * + * This method is not so save as the method findPaymentDateByWorkitem that is + * searching the payment workitem. + * + * 2023-06-28T12:40:17.437|rechnungsausgang-de-1.0|5001.50|5900| + * + * @param invoice + * @return + * @throws ParseException + */ + public Date findPaymentDateByLog(ItemCollection invoice) throws ParseException { + + // Create a SimpleDateFormat instance with the desired format + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + List logEntries = invoice.getItemValue("$eventlog"); + + // Iterate through the List from the last element to the first + for (int i = logEntries.size() - 1; i >= 0; i--) { + String entry = logEntries.get(i); + + if (entry.endsWith(invoice.getTaskID() + "|")) { + // found! + String dateString = entry.substring(0, entry.indexOf("|") - 1); + // Parse the date string into a Date object + Date date = sdf.parse(dateString); + // Print the Date object + System.out.println("Date: " + date); + return date; + } + } + + // no date found! + return null; + + } + + /** + * Diese Methode baut die Datenstruktur für das Chart Diagram zusammen + * + * + *
+    	{
+    	 labels: ["January", "February", "March", "April", "May", "June", "July"],
+    	 datasets: [{
+    		label: 'Dataset 1',
+    		//backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
+    		//borderColor: window.chartColors.red,
+    		borderWidth: 1,
+    		data: [
+    			70, 70, 70, 70, 79, 50, 50
+    		]
+    	}, {
+    		label: 'Dataset 2',
+    		//backgroundColor: color(window.chartColors.blue).alpha(0.5).rgbString(),
+    		//borderColor: window.chartColors.blue,
+    		borderWidth: 1,
+    		data: [
+    			70, 70, 170, 7, 79, 50, 50
+    		]
+    	}]
+       }
+     * 
+ * + * @return + */ + public String buildChartData() { + + getStats(); + + // build a list of all lables.... + List statusLabels = new ArrayList(); + + Set keys = stats.keySet(); + for (String _key : keys) { + statusLabels.add(_key); + } + + String result = "{"; + + // Lables + result = result + "labels : [ "; + result = result + statusLabels.stream().collect(Collectors.joining("','", "'", "'")); + result = result + "],"; + result = result + "datasets: ["; + + // Datasets 1 + result = result + "{label: 'Zahlungsziel',borderWidth: 1,"; + result = result + " borderColor: [\"#3B6B82\"],"; + result = result + " \"backgroundColor\" : [\"#CFE9F5\"], fill: true,tension: 0.5,"; + + result = result + "data: ["; + for (Map.Entry entry : stats.entrySet()) { + result = result + entry.getValue().getAverageDueDays() + ","; + } + // cut last comma + result = result.substring(0, result.length() - 1); + result = result + "]"; + result = result + "}, "; + + // Datasets 2 + result = result + "{label: 'Zahldauer',borderWidth: 1,"; + result = result + " borderColor: [\"#E73B65\"],\"backgroundColor\" : [\"#70B088\" ], tension: 0.5,fill: true,"; + + result = result + + " trendlineLinear: { colorMin: \"red\", colorMax: \"green\", lineStyle: \"dotted\", width: 2 , projection: true },"; + + result = result + "data: ["; + for (Map.Entry entry : stats.entrySet()) { + result = result + entry.getValue().getAveragePaymentDays() + ","; + } + // cut last comma + result = result.substring(0, result.length() - 1); + result = result + "]"; + result = result + "} "; + + // ende + result = result + "] }"; + + return result; + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/DebitorStatistikData.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/DebitorStatistikData.java index ea07475..f145afe 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/DebitorStatistikData.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/DebitorStatistikData.java @@ -40,7 +40,7 @@ public class DebitorStatistikData { /** * Aktualisiert die statistischen werte - * + * * @param invoiceDate * @param paymentDate */ @@ -90,7 +90,7 @@ public class DebitorStatistikData { /** * returns - * + * * 202304 */ public String toString() { diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/FTPConnector.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/FTPConnector.java index d728880..7438dad 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/FTPConnector.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/FTPConnector.java @@ -1,22 +1,22 @@ /******************************************************************************* * Imixs Workflow Technology - * Copyright (C) 2001, 2008 Imixs Software Solutions GmbH, + * Copyright (C) 2001, 2008 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika *******************************************************************************/ @@ -42,7 +42,7 @@ import jakarta.inject.Inject; /** * The FTPConnector service provides methods to push invoices to cargosoft - * + * * @version 1.0 * @author rsoika */ @@ -83,7 +83,7 @@ public class FTPConnector { /** * This method transfers a snapshot to a ftp server. - * + * * @param fileData object * @throws PluginException */ @@ -158,7 +158,7 @@ public class FTPConnector { /** * This method reads data form the current working directory - * + * * @param snapshot * @throws ArchiveException * @return data @@ -198,7 +198,7 @@ public class FTPConnector { /** * This method changes the current working sub-directy. If no corresponding * directory exits the method creats one. - * + * * @throws ArchiveException */ @SuppressWarnings("unused") diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceAnalyseController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceAnalyseController.java index 2c6e7f9..a9098d9 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceAnalyseController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceAnalyseController.java @@ -22,7 +22,7 @@ import jakarta.inject.Named; /** * Der InvoiceAnalyseController dient zur Auswertung der Sachprüfungs Workflow * Schritte. Die Analyse Seite ist über das Admin Menü erreichbar. - * + * * @author rsoika * */ @@ -30,138 +30,138 @@ import jakarta.inject.Named; @ConversationScoped public class InvoiceAnalyseController implements Serializable { - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(InvoiceAnalyseController.class.getName()); + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(InvoiceAnalyseController.class.getName()); - @Inject - protected DocumentService documentService; + @Inject + protected DocumentService documentService; - @Inject - SearchService searchService; + @Inject + SearchService searchService; - Category sachpruefung; - Category verteilung; - Category buchhaltung; + Category sachpruefung; + Category verteilung; + Category buchhaltung; - private ItemCollection filter; + private ItemCollection filter; - public ItemCollection getFilter() { - if (filter == null) { - filter = new ItemCollection(); - // compute start stop based on current month - YearMonth startYearMonth = YearMonth.now(); - java.time.LocalDate startOfMonthDate = startYearMonth.atDay(1); - java.time.LocalDate endOfMonthDate = startYearMonth.atEndOfMonth(); - filter.setItemValue("start", - java.util.Date.from(startOfMonthDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); - filter.setItemValue("stop", - java.util.Date.from(endOfMonthDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); - } - return filter; - } + public ItemCollection getFilter() { + if (filter == null) { + filter = new ItemCollection(); + // compute start stop based on current month + YearMonth startYearMonth = YearMonth.now(); + java.time.LocalDate startOfMonthDate = startYearMonth.atDay(1); + java.time.LocalDate endOfMonthDate = startYearMonth.atEndOfMonth(); + filter.setItemValue("start", + java.util.Date.from(startOfMonthDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + filter.setItemValue("stop", + java.util.Date.from(endOfMonthDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + } + return filter; + } - public void setFilter(ItemCollection filter) { - this.filter = filter; - } + public void setFilter(ItemCollection filter) { + this.filter = filter; + } - public Category getSachpruefung() { - return sachpruefung; - } + public Category getSachpruefung() { + return sachpruefung; + } - public Category getVerteilung() { - return verteilung; - } + public Category getVerteilung() { + return verteilung; + } - public Category getBuchhaltung() { - return buchhaltung; - } + public Category getBuchhaltung() { + return buchhaltung; + } - public int totalCountByCategory(Category cat) { - int result = 0; + public int totalCountByCategory(Category cat) { + int result = 0; - Collection allCounts = cat.getLabels().values(); - result = allCounts.stream().reduce(0, Integer::sum); + Collection allCounts = cat.getLabels().values(); + result = allCounts.stream().reduce(0, Integer::sum); - return result; - } + return result; + } - /** - * Fuert verschiedene Queries aus um eine Analyse der Sachprufung durchzuführen. - */ - public void analyse() { + /** + * Fuert verschiedene Queries aus um eine Analyse der Sachprufung durchzuführen. + */ + public void analyse() { - logger.info("start analyse...."); - verteilung = null; - sachpruefung = null; - Date start = filter.getItemValueDate("start"); - Date stop = filter.getItemValueDate("stop"); + logger.info("start analyse...."); + verteilung = null; + sachpruefung = null; + Date start = filter.getItemValueDate("start"); + Date stop = filter.getItemValueDate("stop"); - logger.info("...daterange=" + start + " - " + stop); + logger.info("...daterange=" + start + " - " + stop); - // serach date range? - String sDateFrom = "191401070000"; // because * did not work here - String sDateTo = "211401070000"; - SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMddHHmm"); + // serach date range? + String sDateFrom = "191401070000"; // because * did not work here + String sDateTo = "211401070000"; + SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMddHHmm"); - if (start != null) { - Calendar cal = Calendar.getInstance(); - cal.setTime(start); - sDateFrom = dateformat.format(cal.getTime()); - } - if (stop != null) { - Calendar cal = Calendar.getInstance(); - cal.setTime(stop); - cal.add(Calendar.DATE, 1); - sDateTo = dateformat.format(cal.getTime()); - } + if (start != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(start); + sDateFrom = dateformat.format(cal.getTime()); + } + if (stop != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(stop); + cal.add(Calendar.DATE, 1); + sDateTo = dateformat.format(cal.getTime()); + } - // query Verteilung - String query = "(type:workitem OR type:workitemarchive) "; - if (start != null || stop != null) { - query += "AND (taxonomy.verteilung.stop:[" + sDateFrom + " TO " + sDateTo + "]) "; - } - logger.info(query); - List taxResult = searchService.getTaxonomyByQuery(query, "taxonomy.verteilung.stop.by"); - if (taxResult.size() > 0) { - verteilung = taxResult.get(0); + // query Verteilung + String query = "(type:workitem OR type:workitemarchive) "; + if (start != null || stop != null) { + query += "AND (taxonomy.verteilung.stop:[" + sDateFrom + " TO " + sDateTo + "]) "; + } + logger.info(query); + List taxResult = searchService.getTaxonomyByQuery(query, "taxonomy.verteilung.stop.by"); + if (taxResult.size() > 0) { + verteilung = taxResult.get(0); - } + } - // query Sachprüfung - query = "(type:workitem OR type:workitemarchive) "; - if (start != null || stop != null) { - query += "AND (taxonomy.sachpruefung.stop:[" + sDateFrom + " TO " + sDateTo + "]) "; - } - logger.info(query); - taxResult = searchService.getTaxonomyByQuery(query, "taxonomy.sachpruefung.stop.by"); - if (taxResult.size() > 0) { - sachpruefung = taxResult.get(0); + // query Sachprüfung + query = "(type:workitem OR type:workitemarchive) "; + if (start != null || stop != null) { + query += "AND (taxonomy.sachpruefung.stop:[" + sDateFrom + " TO " + sDateTo + "]) "; + } + logger.info(query); + taxResult = searchService.getTaxonomyByQuery(query, "taxonomy.sachpruefung.stop.by"); + if (taxResult.size() > 0) { + sachpruefung = taxResult.get(0); - } + } - // query Buchhaltung - query = "(type:workitem OR type:workitemarchive) "; - if (start != null || stop != null) { - query += "AND (taxonomy.buchhaltung.stop:[" + sDateFrom + " TO " + sDateTo + "]) "; - } - logger.info(query); - taxResult = searchService.getTaxonomyByQuery(query, "taxonomy.buchhaltung.stop.by"); - if (taxResult.size() > 0) { - buchhaltung = taxResult.get(0); + // query Buchhaltung + query = "(type:workitem OR type:workitemarchive) "; + if (start != null || stop != null) { + query += "AND (taxonomy.buchhaltung.stop:[" + sDateFrom + " TO " + sDateTo + "]) "; + } + logger.info(query); + taxResult = searchService.getTaxonomyByQuery(query, "taxonomy.buchhaltung.stop.by"); + if (taxResult.size() > 0) { + buchhaltung = taxResult.get(0); - } + } - } + } - /** - * This method reset the search and input state. - */ - public void reset() { - filter = new ItemCollection(); - sachpruefung = null; - verteilung = null; - buchhaltung = null; - logger.fine("reset"); - } + /** + * This method reset the search and input state. + */ + public void reset() { + filter = new ItemCollection(); + sachpruefung = null; + verteilung = null; + buchhaltung = null; + logger.fine("reset"); + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceDispatchAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceDispatchAdapter.java index f7216d2..1408601 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceDispatchAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceDispatchAdapter.java @@ -16,7 +16,7 @@ import jakarta.inject.Inject; /** * Der InvoiceDispatchAdapter verteilt die vom Abteilungsleiter ausgewählten * Rechnungen - * + * * @version 1.0 * @author rsoika */ @@ -29,7 +29,7 @@ public class InvoiceDispatchAdapter implements SignalAdapter { /** * This method computes the cargosoft export data file - * + * * @throws PluginException */ @Override diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceDispatchController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceDispatchController.java index 6c17746..860020f 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceDispatchController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceDispatchController.java @@ -16,7 +16,7 @@ import jakarta.inject.Named; /** * The InvoiceDispatchController loads open workitems for the current user - * + * * @author rsoika * */ @@ -50,7 +50,7 @@ public class InvoiceDispatchController implements Serializable { * ($modelversion:rechnungseingang-de*) AND ($taskid:5100) AND * ($owner:#{user})"> - * + * */ public void searchInvoices() { long l = System.currentTimeMillis(); diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceOutgoingPlugin.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceOutgoingPlugin.java index 4c2b576..10a452d 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceOutgoingPlugin.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceOutgoingPlugin.java @@ -8,40 +8,40 @@ import jakarta.inject.Inject; /** * Das InvoiceOutgoingPlugin aktualisiert die Debitor E-Mail Adresse - * + * * Auch das feld _img wird in Abhängigkeit vom item 'invoice.protest' gesetzt - * + * * @author rsoika * @version 1.0 - * + * */ public class InvoiceOutgoingPlugin extends AbstractPlugin { - @Inject - BusinessPartnerService businessPartnerService; + @Inject + BusinessPartnerService businessPartnerService; - /** - * - * @throws PluginException - if data is missing - * - **/ - @Override - public ItemCollection run(ItemCollection workitem, ItemCollection event) throws PluginException { + /** + * + * @throws PluginException - if data is missing + * + **/ + @Override + public ItemCollection run(ItemCollection workitem, ItemCollection event) throws PluginException { - // Update BUsiness Partner Data - businessPartnerService.updateBusinessPartnerData(workitem); + // Update BUsiness Partner Data + businessPartnerService.updateBusinessPartnerData(workitem); - // Update Invoice.positions - InvoiceUtil.updateInvoicePositions(workitem); + // Update Invoice.positions + InvoiceUtil.updateInvoicePositions(workitem); - String img = ""; - if (workitem.getItemValueBoolean("invoice.protest")) { - img = img + ""; + String img = ""; + if (workitem.getItemValueBoolean("invoice.protest")) { + img = img + ""; - } - workitem.setItemValue("_img", img); + } + workitem.setItemValue("_img", img); - return workitem; - } + return workitem; + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoicePlugin.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoicePlugin.java index 2704e0e..30b267a 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoicePlugin.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoicePlugin.java @@ -35,403 +35,402 @@ import jakarta.inject.Inject; *

* 2.2.22 - Das Plugin erzeugt auch das Feld _img welches die Images von * Sofortüberweisung, Mahnung und Ablehnung anzeigt. - * - * 26.4.25 - Das Plugin setzt nun auch partner.name und partner.id - * zusätzlich processed es das BP Workitem - * + * + * 26.4.25 - Das Plugin setzt nun auch partner.name und partner.id zusätzlich + * processed es das BP Workitem + * * @author rsoika * @version 1.0 - * + * */ public class InvoicePlugin extends AbstractPlugin { - public static final int TASK_ERFASSUNG = 5001; - public static final int TASK_SACHPRUEFUNG = 5200; - public static final int TASK_SACHPRUEFUNG_DELEGATION = 5210; - public static final int EVENT_FREIGEBEN = 20; + public static final int TASK_ERFASSUNG = 5001; + public static final int TASK_SACHPRUEFUNG = 5200; + public static final int TASK_SACHPRUEFUNG_DELEGATION = 5210; + public static final int EVENT_FREIGEBEN = 20; - public static final String ERROR_MISSING_DATA = "MISSING_DATA"; - public static final String ERROR_DUPPLICATE_INVOICE_NUMBER = "DUPPLICATE_INVOICE_NUMBER"; - public static final String ERROR_NEW_IBANBIC = "NEW_IBANBIC"; + public static final String ERROR_MISSING_DATA = "MISSING_DATA"; + public static final String ERROR_DUPPLICATE_INVOICE_NUMBER = "DUPPLICATE_INVOICE_NUMBER"; + public static final String ERROR_NEW_IBANBIC = "NEW_IBANBIC"; - private static Logger logger = Logger.getLogger(InvoicePlugin.class.getName()); + private static Logger logger = Logger.getLogger(InvoicePlugin.class.getName()); - // Positions Pattern - kann überschrieben werden - // XX-YYY-####-### oder XX-YYYZZZ-####-### - // Default = ([A-Z]{2}-[A-Z]{3}-[0-9]{4}-[0-9]{3}) - // Optional = ([A-Z]{2}-(?:[A-Z]{3}|[A-Z]{6})-[0-9]{4}-[0-9]{3}) - // Nur 6- Stellig = ([A-Z]{2}-(?:[A-Z]{6})-[0-9]{4}-[0-9]{3}) - public final static String INVOICE_POSITIONS_PATTERN_DEFAULT = "([A-Z]{2}-(?:[A-Z]{3}|[A-Z]{6})-[0-9]{4}-[0-9]{3})"; + // Positions Pattern - kann überschrieben werden + // XX-YYY-####-### oder XX-YYYZZZ-####-### + // Default = ([A-Z]{2}-[A-Z]{3}-[0-9]{4}-[0-9]{3}) + // Optional = ([A-Z]{2}-(?:[A-Z]{3}|[A-Z]{6})-[0-9]{4}-[0-9]{3}) + // Nur 6- Stellig = ([A-Z]{2}-(?:[A-Z]{6})-[0-9]{4}-[0-9]{3}) + public final static String INVOICE_POSITIONS_PATTERN_DEFAULT = "([A-Z]{2}-(?:[A-Z]{3}|[A-Z]{6})-[0-9]{4}-[0-9]{3})"; - @Inject - @ConfigProperty(name = "invoice.position.pattern", defaultValue = INVOICE_POSITIONS_PATTERN_DEFAULT) - String invoicePositionsPattern; + @Inject + @ConfigProperty(name = "invoice.position.pattern", defaultValue = INVOICE_POSITIONS_PATTERN_DEFAULT) + String invoicePositionsPattern; - @Inject - BusinessPartnerService businessPartnerService; + @Inject + BusinessPartnerService businessPartnerService; - @Inject - ResourceBundleHandler resourceBundleHandler; + @Inject + ResourceBundleHandler resourceBundleHandler; - /** - * Test childworkitems for empty lines - * - * @throws PluginException - if data is missing - * - **/ - @Override - public ItemCollection run(ItemCollection workitem, ItemCollection event) throws PluginException { + /** + * Test childworkitems for empty lines + * + * @throws PluginException - if data is missing + * + **/ + @Override + public ItemCollection run(ItemCollection workitem, ItemCollection event) throws PluginException { - // Update BUsiness Partner Data - businessPartnerService.updateBusinessPartnerData(workitem); + // Update BUsiness Partner Data + businessPartnerService.updateBusinessPartnerData(workitem); - updateImg(workitem); + updateImg(workitem); - // Update Invoice.positions - InvoiceUtil.updateInvoicePositions(workitem); + // Update Invoice.positions + InvoiceUtil.updateInvoicePositions(workitem); - // skip if validaten tag is required=false - ItemCollection evalItemCollection = this.getWorkflowService().evalWorkflowResult(event, "validation", workitem); - if (evalItemCollection != null) { - // evaluate the validation rules... - if ("false".equalsIgnoreCase(evalItemCollection.getItemValueString("required"))) { - return workitem; - } - } + // skip if validaten tag is required=false + ItemCollection evalItemCollection = this.getWorkflowService().evalWorkflowResult(event, "validation", workitem); + if (evalItemCollection != null) { + // evaluate the validation rules... + if ("false".equalsIgnoreCase(evalItemCollection.getItemValueString("required"))) { + return workitem; + } + } - boolean isPublicEvent = !("0".equals(event.getItemValueString("keypublicresult"))); + boolean isPublicEvent = !("0".equals(event.getItemValueString("keypublicresult"))); - // Payment.type muss immer eingetragne werden! - if (isPublicEvent && workitem.getTaskID() >= TASK_ERFASSUNG && workitem.getEventID() < 900) { - if (workitem.getItemValueString("payment.type").trim().isEmpty()) { - // throw a plugin exception! - String message = resourceBundleHandler.findMessage("ERROR_MISSING_PAYMENT"); - throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); - } - // doppelte Rechnungsnummer prüfen - if (workitem.getTaskID() == TASK_ERFASSUNG) { - validateInvoiceNumber(workitem); - checkIBANNumber(workitem); - } + // Payment.type muss immer eingetragne werden! + if (isPublicEvent && workitem.getTaskID() >= TASK_ERFASSUNG && workitem.getEventID() < 900) { + if (workitem.getItemValueString("payment.type").trim().isEmpty()) { + // throw a plugin exception! + String message = resourceBundleHandler.findMessage("ERROR_MISSING_PAYMENT"); + throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); + } + // doppelte Rechnungsnummer prüfen + if (workitem.getTaskID() == TASK_ERFASSUNG) { + validateInvoiceNumber(workitem); + checkIBANNumber(workitem); + } - if (workitem.getTaskID() == TASK_ERFASSUNG || ((workitem.getTaskID() == TASK_SACHPRUEFUNG - || workitem.getTaskID() == TASK_SACHPRUEFUNG_DELEGATION) - && workitem.getEventID() == EVENT_FREIGEBEN)) { - // Buchungsperiode auf plausi prüfen - if (isCargoRechnung(workitem) && "workitem".equals(workitem.getType())) { - validateBuchungsperiode(workitem.getItemValueString(InvoiceUtil.ITEM_INVOICE_PERIOD)); - } - // Cargosoft Kreditorennnummer prüfen - if (isCargoRechnung(workitem)) { - validateCargosoftCdtrNumber(workitem); - } - } - } + if (workitem.getTaskID() == TASK_ERFASSUNG || ((workitem.getTaskID() == TASK_SACHPRUEFUNG + || workitem.getTaskID() == TASK_SACHPRUEFUNG_DELEGATION) + && workitem.getEventID() == EVENT_FREIGEBEN)) { + // Buchungsperiode auf plausi prüfen + if (isCargoRechnung(workitem) && "workitem".equals(workitem.getType())) { + validateBuchungsperiode(workitem.getItemValueString(InvoiceUtil.ITEM_INVOICE_PERIOD)); + } + // Cargosoft Kreditorennnummer prüfen + if (isCargoRechnung(workitem)) { + validateCargosoftCdtrNumber(workitem); + } + } + } - // die prüfung der positionsnummern und Category erfolgt nur im Status - // Sachprüfung (5200) und nur beim Freigeben (20)! - if (isCargoRechnung(workitem) - && ((workitem.getTaskID() == TASK_SACHPRUEFUNG || workitem.getTaskID() == TASK_SACHPRUEFUNG_DELEGATION) - && workitem.getEventID() == EVENT_FREIGEBEN)) { - List childs = InvoiceUtil.explodeChildList(workitem); - for (ItemCollection posItem : childs) { - if (posItem.getItemValueString("name").trim().isEmpty()) { - // throw a plugin exception - because name is missing! - String message = resourceBundleHandler.findMessage("ERROR_MISSING_POSNO"); - message = message.replace("{1}", posItem.getItemValueString("numpos")); - throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); - } + // die prüfung der positionsnummern und Category erfolgt nur im Status + // Sachprüfung (5200) und nur beim Freigeben (20)! + if (isCargoRechnung(workitem) + && ((workitem.getTaskID() == TASK_SACHPRUEFUNG || workitem.getTaskID() == TASK_SACHPRUEFUNG_DELEGATION) + && workitem.getEventID() == EVENT_FREIGEBEN)) { + List childs = InvoiceUtil.explodeChildList(workitem); + for (ItemCollection posItem : childs) { + if (posItem.getItemValueString("name").trim().isEmpty()) { + // throw a plugin exception - because name is missing! + String message = resourceBundleHandler.findMessage("ERROR_MISSING_POSNO"); + message = message.replace("{1}", posItem.getItemValueString("numpos")); + throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); + } - // validate pos for regex pattern 'XX-YYY-####-###' - if (!posItem.getItemValueString("name").matches(invoicePositionsPattern)) { - // throw a plugin exception - because name is missing! - String message = resourceBundleHandler.findMessage("ERROR_FORMAT_POSNO"); - message = message.replace("{1}", posItem.getItemValueString("numpos")); - throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); - } + // validate pos for regex pattern 'XX-YYY-####-###' + if (!posItem.getItemValueString("name").matches(invoicePositionsPattern)) { + // throw a plugin exception - because name is missing! + String message = resourceBundleHandler.findMessage("ERROR_FORMAT_POSNO"); + message = message.replace("{1}", posItem.getItemValueString("numpos")); + throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); + } - if (posItem.getItemValueString("category").trim().isEmpty()) { - // throw a plugin exception - because name is missing! - String message = resourceBundleHandler.findMessage("ERROR_MISSING_CATEGORY"); - message = message.replace("{1}", posItem.getItemValueString("numpos")); - throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, - "Eingabefehler - Die Leistungsart in Zeile " + posItem.getItemValueString("numpos") - + " muss ausgefüllt sein!"); - } + if (posItem.getItemValueString("category").trim().isEmpty()) { + // throw a plugin exception - because name is missing! + String message = resourceBundleHandler.findMessage("ERROR_MISSING_CATEGORY"); + message = message.replace("{1}", posItem.getItemValueString("numpos")); + throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, + "Eingabefehler - Die Leistungsart in Zeile " + posItem.getItemValueString("numpos") + + " muss ausgefüllt sein!"); + } - if (posItem.getItemValueString("tax").trim().isEmpty()) { - // throw a plugin exception - because name is missing! - String message = resourceBundleHandler.findMessage("ERROR_MISSING_TAX"); - message = message.replace("{1}", posItem.getItemValueString("numpos")); - throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); - } + if (posItem.getItemValueString("tax").trim().isEmpty()) { + // throw a plugin exception - because name is missing! + String message = resourceBundleHandler.findMessage("ERROR_MISSING_TAX"); + message = message.replace("{1}", posItem.getItemValueString("numpos")); + throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); + } - if (posItem.getItemValueFloat("amount") == 0) { - // throw a plugin exception - because name is missing! - String message = resourceBundleHandler.findMessage("ERROR_MISSING_NET"); - message = message.replace("{1}", posItem.getItemValueString("numpos")); - throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); - } + if (posItem.getItemValueFloat("amount") == 0) { + // throw a plugin exception - because name is missing! + String message = resourceBundleHandler.findMessage("ERROR_MISSING_NET"); + message = message.replace("{1}", posItem.getItemValueString("numpos")); + throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); + } - // Buchunsperiode - if (!posItem.getItemValueString(InvoiceUtil.ITEM_INVOICE_PERIOD).trim().isEmpty()) { - validateBuchungsperiode(posItem.getItemValueString(InvoiceUtil.ITEM_INVOICE_PERIOD)); + // Buchunsperiode + if (!posItem.getItemValueString(InvoiceUtil.ITEM_INVOICE_PERIOD).trim().isEmpty()) { + validateBuchungsperiode(posItem.getItemValueString(InvoiceUtil.ITEM_INVOICE_PERIOD)); - } - } + } + } - // Ergaenzung 18.05.2021: - // es kann vorkommen, das die Anwender abweichende Buchungsperioden eingeben so - // das die Hauptbuchungsperiode gar ncht merh vorkommt. Das darf aber nicht der - // fall sein. - // Im folgenden überprüfen wir ob eine Buchungszeile vorkommt in der keine oder - // die Hauptbuchunsperiode ausgewählt wurde. Ist das nicht der Fal gibt es eine - // Fehlermeldung für den Anwendere - if (isCargoRechnung(workitem) && childs.size() > 0) { - String hauptBuchungsperiode = workitem.getItemValueString("invoice.period"); - boolean buchungsperiodenValid = false; - for (ItemCollection posItem : childs) { - String posBuchungsperiode = posItem.getItemValueString("invoice.period"); - if (posBuchungsperiode.isEmpty() || posBuchungsperiode.equals(hauptBuchungsperiode)) { - // alles fein! - buchungsperiodenValid = true; - } - } - if (buchungsperiodenValid == false) { - // fehlerhafte Buchungsperioden. - String message = resourceBundleHandler.findMessage("ERROR_MAIN_BOOKING_PERIOD"); - throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); - } - } + // Ergaenzung 18.05.2021: + // es kann vorkommen, das die Anwender abweichende Buchungsperioden eingeben so + // das die Hauptbuchungsperiode gar ncht merh vorkommt. Das darf aber nicht der + // fall sein. + // Im folgenden überprüfen wir ob eine Buchungszeile vorkommt in der keine oder + // die Hauptbuchunsperiode ausgewählt wurde. Ist das nicht der Fal gibt es eine + // Fehlermeldung für den Anwendere + if (isCargoRechnung(workitem) && childs.size() > 0) { + String hauptBuchungsperiode = workitem.getItemValueString("invoice.period"); + boolean buchungsperiodenValid = false; + for (ItemCollection posItem : childs) { + String posBuchungsperiode = posItem.getItemValueString("invoice.period"); + if (posBuchungsperiode.isEmpty() || posBuchungsperiode.equals(hauptBuchungsperiode)) { + // alles fein! + buchungsperiodenValid = true; + } + } + if (buchungsperiodenValid == false) { + // fehlerhafte Buchungsperioden. + String message = resourceBundleHandler.findMessage("ERROR_MAIN_BOOKING_PERIOD"); + throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); + } + } - } + } - return workitem; - } + return workitem; + } - /** - * Hilfsroutine die das _img item aktualisiert. - */ - private void updateImg(ItemCollection workitem) { + /** + * Hilfsroutine die das _img item aktualisiert. + */ + private void updateImg(ItemCollection workitem) { - // Update _img icon list - String img = workitem.getItemValueString("_img"); - // 031=ablehnen - // 100=sofortüberweichung - // 074=mahnen - // sofortüberweisung - if (workitem.getEventID() == 96 && !img.contains("100.png")) { - img = img + ""; - } - // mahnen - if (workitem.getEventID() == 95 && !img.contains("103.png")) { - img = img + ""; + // Update _img icon list + String img = workitem.getItemValueString("_img"); + // 031=ablehnen + // 100=sofortüberweichung + // 074=mahnen + // sofortüberweisung + if (workitem.getEventID() == 96 && !img.contains("100.png")) { + img = img + ""; + } + // mahnen + if (workitem.getEventID() == 95 && !img.contains("103.png")) { + img = img + ""; - } - // ablehnen - if (workitem.getTaskID() == 5100 && workitem.getEventID() == 90 && !img.contains("028.png")) { - img = img + ""; - } - if (workitem.getTaskID() == 5200 && workitem.getEventID() == 90 && !img.contains("028.png")) { - img = img + ""; - } - workitem.setItemValue("_img", img); + } + // ablehnen + if (workitem.getTaskID() == 5100 && workitem.getEventID() == 90 && !img.contains("028.png")) { + img = img + ""; + } + if (workitem.getTaskID() == 5200 && workitem.getEventID() == 90 && !img.contains("028.png")) { + img = img + ""; + } + workitem.setItemValue("_img", img); - } + } - /** - * Diese Method prüft die Buchungsperiode auf Plausibilität - * - * YYYY(+1)01-12 - * - * z.b. 202110 oder 202107 oder 202201 - * - * @param workitem - * @throws PluginException - */ - private void validateBuchungsperiode(String period) throws PluginException { + /** + * Diese Method prüft die Buchungsperiode auf Plausibilität + * + * YYYY(+1)01-12 + * + * z.b. 202110 oder 202107 oder 202201 + * + * @param workitem + * @throws PluginException + */ + private void validateBuchungsperiode(String period) throws PluginException { - // buchungsperionde nur prüfen wenn noch nicht archiviert - if (!period.isEmpty()) { - LocalDate localDate = LocalDate.now(); - int year = localDate.getYear(); - // build regex.... - String regex = "(" + year + "|" + (year + 1) + "|" + (year - 1) + ")(1[0-2]|0[1-9])"; + // buchungsperionde nur prüfen wenn noch nicht archiviert + if (!period.isEmpty()) { + LocalDate localDate = LocalDate.now(); + int year = localDate.getYear(); + // build regex.... + String regex = "(" + year + "|" + (year + 1) + "|" + (year - 1) + ")(1[0-2]|0[1-9])"; - if (!period.matches(regex)) { - // throw a plugin exception - because name is missing! - String message = resourceBundleHandler.findMessage("ERROR_BOOKING_PERIOD"); - throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); - } - } - } + if (!period.matches(regex)) { + // throw a plugin exception - because name is missing! + String message = resourceBundleHandler.findMessage("ERROR_BOOKING_PERIOD"); + throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); + } + } + } - /** - * Diese Method prüft ob die "invoice.number" bereits einmal im - * Rechnugnsworkflow vergeben wurde. Falls ja wird eine PluginExcpetion - * ausgelöst. - *

- * Query Example: - *

- * NOT $uniqueid:"9f72fa50-4845-41ea-b6b9-ccd518c353be" AND - txtcooperatespace:"9dba107e-f8ef-4150-a832-040d75a6eda7" AND invoice.number:"45" - * - *

- * in case a duplicate invoice was detected the item invoice.number.duplicate is - * filled. This item is used for a conditional event. The case is displayed as a - * warning in the form. - * - * @throws PluginException - * @throws QueryException - * - */ - private void validateInvoiceNumber(ItemCollection workitem) throws PluginException { + /** + * Diese Method prüft ob die "invoice.number" bereits einmal im + * Rechnugnsworkflow vergeben wurde. Falls ja wird eine PluginExcpetion + * ausgelöst. + *

+ * Query Example: + *

+ * NOT $uniqueid:"9f72fa50-4845-41ea-b6b9-ccd518c353be" AND + txtcooperatespace:"9dba107e-f8ef-4150-a832-040d75a6eda7" AND invoice.number:"45" + * + *

+ * in case a duplicate invoice was detected the item invoice.number.duplicate is + * filled. This item is used for a conditional event. The case is displayed as a + * warning in the form. + * + * @throws PluginException + * @throws QueryException + * + */ + private void validateInvoiceNumber(ItemCollection workitem) throws PluginException { - String invoiceNumber = workitem.getItemValueString("invoice.number"); - // strip - String invoiceNumberStripped = invoiceNumber.replace(" ", ""); - workitem.setItemValue("invoice.number.stripped", invoiceNumberStripped); - String invoiceNumberDuplicate = workitem.getItemValueString("invoice.number.duplicate"); + String invoiceNumber = workitem.getItemValueString("invoice.number"); + // strip + String invoiceNumberStripped = invoiceNumber.replace(" ", ""); + workitem.setItemValue("invoice.number.stripped", invoiceNumberStripped); + String invoiceNumberDuplicate = workitem.getItemValueString("invoice.number.duplicate"); - // wenn keine Rechnungsnummer eingegeben wurde gehts weiter! - if (invoiceNumber.isEmpty()) { - return; - } + // wenn keine Rechnungsnummer eingegeben wurde gehts weiter! + if (invoiceNumber.isEmpty()) { + return; + } - // rechnungseingang only workitems... - // Change 11.4.22 - we have now an additional field: invoice.number.stripped - String query = "(type:workitem OR type:workitemarchive) AND ($modelversion:rechnungseingang*) AND NOT ($uniqueid:\"" - + workitem.getUniqueID() + "\") AND ((invoice.number:\"" + invoiceNumberStripped - + "\") OR (invoice.number.stripped:\"" + invoiceNumberStripped + "\"))"; - try { - int result = this.getWorkflowService().getDocumentService().count(query, 1); - if (result > 0) { - // wenn _invoicenumber_duplicate bereits gesetzt ist - dann geht es ohne prüfung - // weiter - if (!invoiceNumberDuplicate.isEmpty()) { - logger.warning("...validateion skipped by user with duplicate invoice number: " + invoiceNumber); - } else { + // rechnungseingang only workitems... + // Change 11.4.22 - we have now an additional field: invoice.number.stripped + String query = "(type:workitem OR type:workitemarchive) AND ($modelversion:rechnungseingang*) AND NOT ($uniqueid:\"" + + workitem.getUniqueID() + "\") AND ((invoice.number:\"" + invoiceNumberStripped + + "\") OR (invoice.number.stripped:\"" + invoiceNumberStripped + "\"))"; + try { + int result = this.getWorkflowService().getDocumentService().count(query, 1); + if (result > 0) { + // wenn _invoicenumber_duplicate bereits gesetzt ist - dann geht es ohne prüfung + // weiter + if (!invoiceNumberDuplicate.isEmpty()) { + logger.warning("...validateion skipped by user with duplicate invoice number: " + invoiceNumber); + } else { - // set _invoicenumber_duplicate - dadurch wird die warnmeldung ausgegeben und - // der Vorgang nicht weitergeleitet - workitem.replaceItemValue("invoice.number.duplicate", invoiceNumber); - String message = resourceBundleHandler.findMessage("ERROR_INVOICENO"); - throw new PluginException(InvoicePlugin.class.getName(), ERROR_DUPPLICATE_INVOICE_NUMBER, message); - } - } else { - // clear ! - workitem.appendItemValue("invoice.number.duplicate.history", - workitem.getItemValueString("invoice.number.duplicate")); - workitem.replaceItemValue("invoice.number.duplicate", ""); - } - } catch (QueryException e) { - throw new PluginException(PluginException.class.getName(), "QUERY ERROR", e.getMessage(), e); - } - } + // set _invoicenumber_duplicate - dadurch wird die warnmeldung ausgegeben und + // der Vorgang nicht weitergeleitet + workitem.replaceItemValue("invoice.number.duplicate", invoiceNumber); + String message = resourceBundleHandler.findMessage("ERROR_INVOICENO"); + throw new PluginException(InvoicePlugin.class.getName(), ERROR_DUPPLICATE_INVOICE_NUMBER, message); + } + } else { + // clear ! + workitem.appendItemValue("invoice.number.duplicate.history", + workitem.getItemValueString("invoice.number.duplicate")); + workitem.replaceItemValue("invoice.number.duplicate", ""); + } + } catch (QueryException e) { + throw new PluginException(PluginException.class.getName(), "QUERY ERROR", e.getMessage(), e); + } + } - /** - * Diese Method prüft ob die eingegebene IBAN nummer bereits in dem ausgewälten - * Kreditor bekannt ist. Fallst nicht wird diese schnell mal in den Kreditoren - * Datensatz übertragen. Dadurch muss man nicht extra die Kreditoren Verwaltung - * aufrufen. Es ist ein art selbst-lernendes System. - *

- * Die Mehtode ruft eine Plugin Exception aus um den User zu fragen ob er das - * möchte. - * - * @throws PluginException - * @throws QueryException - * - */ - private void checkIBANNumber(ItemCollection workitem) throws PluginException { + /** + * Diese Method prüft ob die eingegebene IBAN nummer bereits in dem ausgewälten + * Kreditor bekannt ist. Fallst nicht wird diese schnell mal in den Kreditoren + * Datensatz übertragen. Dadurch muss man nicht extra die Kreditoren Verwaltung + * aufrufen. Es ist ein art selbst-lernendes System. + *

+ * Die Mehtode ruft eine Plugin Exception aus um den User zu fragen ob er das + * möchte. + * + * @throws PluginException + * @throws QueryException + * + */ + private void checkIBANNumber(ItemCollection workitem) throws PluginException { - String cdtrNumber = workitem.getItemValueString("cdtr.number"); - String iban = workitem.getItemValueString("cdtr.iban"); - String bic = workitem.getItemValueString("cdtr.bic"); - String overtakeIBAN = workitem.getItemValueString("ibanbic.overtake"); + String cdtrNumber = workitem.getItemValueString("cdtr.number"); + String iban = workitem.getItemValueString("cdtr.iban"); + String bic = workitem.getItemValueString("cdtr.bic"); + String overtakeIBAN = workitem.getItemValueString("ibanbic.overtake"); - if (cdtrNumber.isEmpty() || iban.isEmpty() || bic.isEmpty()) { - // no op - return; - } + if (cdtrNumber.isEmpty() || iban.isEmpty() || bic.isEmpty()) { + // no op + return; + } - ItemCollection businessPartner = businessPartnerService - .getBusinessPartnerByID(InvoiceUtil.buildBPID(cdtrNumber)); - if (businessPartner != null) { - // update country - workitem.setItemValue("invoice.country", businessPartner.getItemValueString("partner.country")); + ItemCollection businessPartner = businessPartnerService + .getBusinessPartnerByID(InvoiceUtil.buildBPID(cdtrNumber)); + if (businessPartner != null) { + // update country + workitem.setItemValue("invoice.country", businessPartner.getItemValueString("partner.country")); - // wenn bereits eine Übernahme angedroht wurde, dann übernehmen wir die neue - // IBAN/BIC! - if (!overtakeIBAN.isEmpty()) { - businessPartnerService.addNewIBANBIC(businessPartner, iban, bic); - workitem.setItemValue("ibanbic.overtake", ""); - return; - } + // wenn bereits eine Übernahme angedroht wurde, dann übernehmen wir die neue + // IBAN/BIC! + if (!overtakeIBAN.isEmpty()) { + businessPartnerService.addNewIBANBIC(businessPartner, iban, bic); + workitem.setItemValue("ibanbic.overtake", ""); + return; + } - // Prüfen ob wir die IBAN schon kennen - if (!businessPartnerService.isIBANBICKnown(businessPartner, iban, bic)) { - // OK - scheinbar ist diese IBAN/BIC nicht bekannt. Also fragen wir mal nach.... - workitem.setItemValue("ibanbic.overtake", iban + bic); - String message = resourceBundleHandler.findMessage("ERROR_IBAN_UNKNOWN"); - throw new PluginException(InvoicePlugin.class.getName(), ERROR_NEW_IBANBIC, message); - } + // Prüfen ob wir die IBAN schon kennen + if (!businessPartnerService.isIBANBICKnown(businessPartner, iban, bic)) { + // OK - scheinbar ist diese IBAN/BIC nicht bekannt. Also fragen wir mal nach.... + workitem.setItemValue("ibanbic.overtake", iban + bic); + String message = resourceBundleHandler.findMessage("ERROR_IBAN_UNKNOWN"); + throw new PluginException(InvoicePlugin.class.getName(), ERROR_NEW_IBANBIC, message); + } - } + } - } + } - /** - * Hilfsmethod prüft ob die cdtr.nummer mit einer nummer aus der Cargosoft - * Import Datei übereinstimmt. - * - * @param workitem - * @throws PluginException - */ - private void validateCargosoftCdtrNumber(ItemCollection workitem) throws PluginException { - String crdtrNumber = workitem.getItemValueString("cdtr.number"); + /** + * Hilfsmethod prüft ob die cdtr.nummer mit einer nummer aus der Cargosoft + * Import Datei übereinstimmt. + * + * @param workitem + * @throws PluginException + */ + private void validateCargosoftCdtrNumber(ItemCollection workitem) throws PluginException { + String crdtrNumber = workitem.getItemValueString("cdtr.number"); - // wenn keine Nummer eingegeben wurde gehts weiter! - if (crdtrNumber.isEmpty()) { - return; - } + // wenn keine Nummer eingegeben wurde gehts weiter! + if (crdtrNumber.isEmpty()) { + return; + } - // search creditor number in cargosoft... - // die cargosoft Kreditornummer beginnt seltsamerweise mit einem K..... - try { - String query = "(type:cargosoftkreditor) AND (name:K" + crdtrNumber + " OR name:" + crdtrNumber + ")"; - List result = this.getWorkflowService().getDocumentService().find(query, 1, 0); - if (result == null || result.size() == 0) { + // search creditor number in cargosoft... + // die cargosoft Kreditornummer beginnt seltsamerweise mit einem K..... + try { + String query = "(type:cargosoftkreditor) AND (name:K" + crdtrNumber + " OR name:" + crdtrNumber + ")"; + List result = this.getWorkflowService().getDocumentService().find(query, 1, 0); + if (result == null || result.size() == 0) { - String message = resourceBundleHandler.findMessage("ERROR_CDTR_INVALID"); - throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, - message); + String message = resourceBundleHandler.findMessage("ERROR_CDTR_INVALID"); + throw new PluginException(InvoicePlugin.class.getName(), ERROR_MISSING_DATA, message); - } else { - // update cargosoft crediotr name - ItemCollection cargo = result.get(0); - workitem.setItemValue("cdtr.name.cargosoft", cargo.getItemValueString("_VENDOR_Name")); - } - } catch (QueryException e) { - e.printStackTrace(); - } + } else { + // update cargosoft crediotr name + ItemCollection cargo = result.get(0); + workitem.setItemValue("cdtr.name.cargosoft", cargo.getItemValueString("_VENDOR_Name")); + } + } catch (QueryException e) { + e.printStackTrace(); + } - } + } - /** - * This method returns true if the current workitem is a Cargosoft Invoice. - * - * This can be based on different model versions e.g. rechnungseingang-de-1.2, - * rechnungseingang-pl-1.0, rechnungseingang-dwc-1.0, ... - * - * @param workitem - * @return - */ - public static boolean isCargoRechnung(ItemCollection workitem) { - String REGEX_PATTERN = "rechnungseingang-([a-z]{2}|[a-z]{3})-\\d.\\d"; - // Erstellen Sie ein Pattern-Objekt - Pattern pattern = Pattern.compile(REGEX_PATTERN); - Matcher matcher = pattern.matcher(workitem.getModelVersion()); - // Überprüfen, ob das Muster übereinstimmt - return matcher.matches(); - } + /** + * This method returns true if the current workitem is a Cargosoft Invoice. + * + * This can be based on different model versions e.g. rechnungseingang-de-1.2, + * rechnungseingang-pl-1.0, rechnungseingang-dwc-1.0, ... + * + * @param workitem + * @return + */ + public static boolean isCargoRechnung(ItemCollection workitem) { + String REGEX_PATTERN = "rechnungseingang-([a-z]{2}|[a-z]{3})-\\d.\\d"; + // Erstellen Sie ein Pattern-Objekt + Pattern pattern = Pattern.compile(REGEX_PATTERN); + Matcher matcher = pattern.matcher(workitem.getModelVersion()); + // Überprüfen, ob das Muster übereinstimmt + return matcher.matches(); + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceService.java index 873b42c..19770c2 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceService.java @@ -1,26 +1,26 @@ /******************************************************************************* - * Imixs Workflow - * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, + * Imixs Workflow + * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Project: + * + * Project: * http://www.imixs.org * http://java.net/projects/imixs-workflow - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika - Software Developer *******************************************************************************/ @@ -47,9 +47,9 @@ import jakarta.inject.Inject; /** * Der InvoiceService stellt einige Hilfsmethoden bereit. - * + * * @author rsoika - * + * */ @Singleton @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") @@ -79,7 +79,7 @@ public class InvoiceService { /** * Liefert die Liste der Ausgehenden Währungen - * + * * @return * @throws PluginException */ @@ -89,8 +89,7 @@ public class InvoiceService { ItemCollection configItemCollection = configService.loadConfiguration("AGL_CONFIGURATION"); List currencyList = configItemCollection.getItemValue("currency.out"); if (currencyList == null || currencyList.size() < 1) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "missing Currency configuration - please check parameters"); } return currencyList; @@ -98,7 +97,7 @@ public class InvoiceService { /** * Gibt die Hauptwährung zurück (1. in der liste) - * + * * @return * @throws PluginException */ @@ -108,8 +107,7 @@ public class InvoiceService { ItemCollection configItemCollection = configService.loadConfiguration("AGL_CONFIGURATION"); List currencyList = configItemCollection.getItemValue("currency.out"); if (currencyList == null || currencyList.size() < 1) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "missing Currency configuration - please check parameters"); } return currencyList.get(0); @@ -117,7 +115,7 @@ public class InvoiceService { /** * Liefert die Liste der Eingehenden Währungen - * + * * @return * @throws PluginException */ @@ -127,16 +125,14 @@ public class InvoiceService { ItemCollection configItemCollection = configService.loadConfiguration("AGL_CONFIGURATION"); List currencyList = configItemCollection.getItemValue("currency.in"); if (currencyList == null || currencyList.size() < 1) { - throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "missing Currency configuration - please check parameters"); } return currencyList; } /** - * Prüft ob der Vorgang überflällig ist. - * Default ist 21 tage. + * Prüft ob der Vorgang überflällig ist. Default ist 21 tage. */ public boolean isOverdue(Date date, int taskid) { Date now = new Date(); @@ -149,9 +145,9 @@ public class InvoiceService { /** * This method returns a text-block ItemCollection for a specified name. - * + * * @param name in attribute txtname - * + * * */ public FileData loadTextBlockFileData(String name, String fileName) { diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceUtil.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceUtil.java index cc7a582..c358d67 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceUtil.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceUtil.java @@ -12,14 +12,12 @@ import org.imixs.workflow.ItemCollection; /** * Hilfsmethoden für die Berechnung und Validierung von Ein und * Ausgangsrechnugnen - * - * - Rundungs Methode - * - Berechnung der Positionsnummern - * - Explode Childitems - * + * + * - Rundungs Methode - Berechnung der Positionsnummern - Explode Childitems + * * See: * https://stackoverflow.com/questions/2808535/round-a-double-to-2-decimal-places - * + * * @author rsoika * */ @@ -42,7 +40,7 @@ public class InvoiceUtil { /** * Rounds a Flat Value - * + * * @param value * @return */ @@ -54,7 +52,7 @@ public class InvoiceUtil { /** * Hilfsmethode zum validieren der Mailadresse - * + * * @param emailAddress * @return */ @@ -63,9 +61,7 @@ public class InvoiceUtil { String regexPattern = "^(?=.{1,64}@)[A-Za-z0-9_-]+(\\.[A-Za-z0-9_-]+)*@" + "[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$"; - return Pattern.compile(regexPattern) - .matcher(emailAddress) - .matches(); + return Pattern.compile(regexPattern).matcher(emailAddress).matches(); } // @Deprecated @@ -102,7 +98,7 @@ public class InvoiceUtil { /** * Convert the List of ItemCollections back into a List of Map elements - * + * * @param workitem */ public static void implodeChildList(ItemCollection workitem, List childItems) { @@ -111,7 +107,7 @@ public class InvoiceUtil { /** * Convert the List of ItemCollections back into a List of Map elements - * + * * @param workitem */ @SuppressWarnings({ "rawtypes" }) @@ -130,11 +126,10 @@ public class InvoiceUtil { /** * Diese Methode berechnet das feld 'invoice.positions' welches eine Valueliste - * mit allen Positionsnummern aus dem Childliste enthält. - * Es wird sowohl für Ausgangs wie Eingangsrechnungen genutzt. - * Das feld wird indiziert, so dass eine gezielte Suche nach positiosnummern - * möglich ist. - * + * mit allen Positionsnummern aus dem Childliste enthält. Es wird sowohl für + * Ausgangs wie Eingangsrechnungen genutzt. Das feld wird indiziert, so dass + * eine gezielte Suche nach positiosnummern möglich ist. + * * @param workitem */ public static void updateInvoicePositions(ItemCollection workitem) { @@ -162,9 +157,9 @@ public class InvoiceUtil { /** * Diese Hilfsmethode harmonisiert die Debitoren/Kreditoren ID von Cargosoft und * liefert eine Geschäftspartner ID zurück. - * + * * Aus Kreditor K70153 und Debitor D10153 wird die einheitliche ID: BP0153 - * + * * @param key * @return */ @@ -188,7 +183,7 @@ public class InvoiceUtil { /** * Diese Hilfmethode berechnet die BusinessPartner id aus einer Invoice - * + * * @param Invoice * @return */ @@ -204,7 +199,7 @@ public class InvoiceUtil { /** * Diese Hilfmethode berechnet die BusinessPartner Namen aus einer Invoice - * + * * @param Invoice * @return */ @@ -223,7 +218,7 @@ public class InvoiceUtil { /** * Gibt true zurück wenn es sich um eine Kreditoren Rechnung/Gutschrift handelt. - * + * * @param invoice * @return */ @@ -238,7 +233,7 @@ public class InvoiceUtil { /** * Gibt true zurück wenn es sich um eine Kreditoren Sachrechnung handelt. - * + * * @param invoice * @return */ @@ -252,7 +247,7 @@ public class InvoiceUtil { /** * Gibt true zurück wenn es sich um eine Debitoren Rechnung/Gutschrift handelt. - * + * * @param invoice * @return */ diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListController.java index 078e00f..36d15e6 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListController.java @@ -31,7 +31,7 @@ import jakarta.inject.Named; * errechnet der Controller die selectieren Invoices und verknpüft diese final * mit dem Zahlungseingang. * - * + * * @author rsoika * */ @@ -74,8 +74,7 @@ public class OPListController implements Serializable { * Diese Methode wird für Zahlungseingänge und Mahnläufe verwendet. *

* Die Methode liefert die Rechnungen zur gegebenen dbtrNumber. Die Methode - * nutzt - * einen lokalen cache um die Zugriffe zu beschleunigen. + * nutzt einen lokalen cache um die Zugriffe zu beschleunigen. *

* Befindet sich ein Zahlungseingang in Erstellung (Task=1000) werden alle * offenen Rechnungen geholt. @@ -90,8 +89,8 @@ public class OPListController implements Serializable { * will. *

* Beim Mahnlauf werden Inkasso Rechnungen ausgenommen. - * - * + * + * * @param _dbtrNumber * @return */ @@ -150,7 +149,7 @@ public class OPListController implements Serializable { /* * Zahlugnseingang? - * + * * Wir berechnen den saldo. */ if (workflowController.getWorkitem().getModelVersion().startsWith("zahlungseingang")) { @@ -172,10 +171,10 @@ public class OPListController implements Serializable { * Bei einem Mahnlauf selektieren wir alle Rechnungne immer vor. Das ist eine * Anforderung von AGL Der Benutzer kann dann einzelne Rechnungen wieder * abwählen. - * + * * Eine Ausnahme ist hier nur wenn jemand auf Speichern oder mahnen drückt * (Event = 10|20). Dann ändern wir die Liste nicht! - * + * * Hinweis: in der Liste befinden sich keine Inkasso Rechnungen, da man diese * nicht anwählen kann. */ @@ -185,8 +184,7 @@ public class OPListController implements Serializable { // einer Rechnung mit dem Event 100 (Mahnlauf erstellen) kommen, oder // wenn der Mahnlauf von außen gerate eben neu erzeugt wurde if (workflowController.getWorkitem().getItemValueInteger("$lastevent") == 100 - || - workflowController.getWorkitem().getItemValueInteger("$lastevent") == 0) { + || workflowController.getWorkitem().getItemValueInteger("$lastevent") == 0) { List intialSelection = new ArrayList(); for (ItemCollection invoice : invoiceList) { // ist die Rechnung fällig? @@ -247,7 +245,7 @@ public class OPListController implements Serializable { /** * On Before Process we store the selected invoices in $worktiemRef - * + * * @param workflowEvent */ public void onWorkflowEvent(@Observes WorkflowEvent workflowEvent) { @@ -299,7 +297,7 @@ public class OPListController implements Serializable { /** * Hilfsmethode delegates to ZahlungseingangServcie - * + * * @param payment * @return */ @@ -309,7 +307,7 @@ public class OPListController implements Serializable { /** * Berechnet den gesammten OP Saldo - * + * * @return */ public double calculateInvoiceTotal() { @@ -323,10 +321,10 @@ public class OPListController implements Serializable { } /** - * Berechnet die Summer aller Rechnungen zu einer Währung. - * Es werden nur Rechnungen berücksichtigt, deren Haupt Währung der übergebenen - * Währung entspricht. - * + * Berechnet die Summer aller Rechnungen zu einer Währung. Es werden nur + * Rechnungen berücksichtigt, deren Haupt Währung der übergebenen Währung + * entspricht. + * * @param currency * @return */ @@ -344,7 +342,7 @@ public class OPListController implements Serializable { /** * Berechnet den gesamten OP Saldo - * + * * @return */ public double calculateInvoiceSaldo() { @@ -361,7 +359,7 @@ public class OPListController implements Serializable { /** * Berechnet den noch offenen Differenz vom Zahlbetrag - * + * * @return */ public double calculatePaymentDifference() { @@ -379,7 +377,7 @@ public class OPListController implements Serializable { /** * Berechnet den Zahlbetrag in EUR bei Fremdwärungen - * + * * @return */ public double calculatePaymentSaldoEUR() { @@ -401,7 +399,7 @@ public class OPListController implements Serializable { /** * Returns true if the reminder date is today or in the past - * + * * @param uniqueid * @return */ diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapter.java index 1b4dff5..f123efb 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapter.java @@ -35,7 +35,7 @@ import jakarta.inject.Inject; * Der OPListExportAdapter exportiert eine Excel Datei mit allen offenen * Rechnungen gruppiert nach Debitor. Es werden alle Ausgehenden Währungen * berücksichtigt und ab Spalte H eingefügt. - * + * *

  * {@code
         textblock-ref
@@ -46,16 +46,16 @@ import jakarta.inject.Inject;
  * 

* Der Adapter erweitert den POIAdapter somit können Felder aktualisiert werden * (siehe POIFineReplaceAdapter). - * + * *

* Der Adapter kopiert die Rechnungsdaten in neue Zeilen, welche ab Zeilennummer * 12 eingefügt werden. *

- * + * * Abschliessend werden Summen Formeln in Zeile 14 für jede Währung eingefügt. - * + * * Der Fälligkeitszeitrum von 21 Tagen kann über den Parameter - * + * * @version 1.0 * @author rsoika */ @@ -78,7 +78,7 @@ public class OPListExportAdapter extends POIFindReplaceAdapter { /** * This method finds or create the Zahlungsavis and adds a reference * ($workitemref) to the current invoice. - * + * * @throws PluginException */ @SuppressWarnings("unchecked") @@ -95,9 +95,7 @@ public class OPListExportAdapter extends POIFindReplaceAdapter { } // read the template options - ItemCollection evalItemCollection = workflowService.evalWorkflowResult(event, "opliste", - document, - false); + ItemCollection evalItemCollection = workflowService.evalWorkflowResult(event, "opliste", document, false); if (evalItemCollection == null || !evalItemCollection.hasItem("excel-export")) { throw new PluginException(OPListExportAdapter.class.getSimpleName(), CONFIG_ERROR, "missing opliste configuration in model event - please check model configuration"); @@ -132,13 +130,11 @@ public class OPListExportAdapter extends POIFindReplaceAdapter { doc.write(byteArrayOutputStream); byte[] newContent = byteArrayOutputStream.toByteArray(); - FileData fileDataNew = new FileData(fileData.getName(), newContent, fileData.getContentType(), - null); + FileData fileDataNew = new FileData(fileData.getName(), newContent, fileData.getContentType(), null); document.addFileData(fileDataNew); logger.finest("......new document added"); } catch (IOException | QueryException e) { - throw new PluginException(OPListExportAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(OPListExportAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "failed to update opliste: " + e.getMessage()); } finally { if (doc != null) { @@ -157,8 +153,7 @@ public class OPListExportAdapter extends POIFindReplaceAdapter { } catch ( PluginException e) { - throw new PluginException(OPListExportAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(OPListExportAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "failed to update op-liste: " + e.getMessage()); } logger.fine("... completed!"); @@ -169,8 +164,8 @@ public class OPListExportAdapter extends POIFindReplaceAdapter { /** * EIne OP Liste kann für eine oder mehrere Währungen erstellt werden. Diese * Hilfsmethode fügt die Spalten anhand der Währungen ein. - * - * + * + * * @param sheet * @param currencyList */ @@ -206,13 +201,12 @@ public class OPListExportAdapter extends POIFindReplaceAdapter { *

* The named cell 'TOTAL' should contain the summary formula. It will be * evaluated at the end. - * + * * @throws PluginException * @throws QueryException */ private void insertInvoiceRows(XSSFSheet sheet, ItemCollection document, String fileName, boolean filter, - List currencyList) - throws PluginException, QueryException { + List currencyList) throws PluginException, QueryException { String spaceID = document.getItemValueString("space.ref"); logger.fine("... space.ref=" + spaceID + " ...grouping invoices by spaceid...."); @@ -344,17 +338,15 @@ public class OPListExportAdapter extends POIFindReplaceAdapter { /** * This method loads a text-block for a specified ref and appends the named * fileData object of this document. - * + * * @param document * @throws PluginException */ private FileData appendExcelTemplate(ItemCollection document, String textblockRef, String template, - String targetName) - throws PluginException { + String targetName) throws PluginException { if ((template == null || template.isEmpty()) || (textblockRef == null || textblockRef.isEmpty())) { - throw new PluginException(OPListExportAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(OPListExportAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "invalid POI configuration in model event - textblock/template reference not defined!"); } @@ -363,10 +355,8 @@ public class OPListExportAdapter extends POIFindReplaceAdapter { // do we found the document? if (fileData == null) { - throw new PluginException(OPListExportAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, - "invalid POI configuration in model event - template=" + template - + " not found!"); + throw new PluginException(OPListExportAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, + "invalid POI configuration in model event - template=" + template + " not found!"); } fileData.setName(targetName); // append document @@ -377,10 +367,10 @@ public class OPListExportAdapter extends POIFindReplaceAdapter { /** * Diese Methode gruppiert eine Rechnungsliste nach Debitorennummern - * + * * Falls 'filter==true' werden nur Rechnungen ab der 2. Mahnung oder mit einer * Fälligkeit >10 Tage ausgegeben. Dieses Flag wird im Workflowmodell gesetzt - * + * * @param spaceID * @return */ diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapterByWeek.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapterByWeek.java index 49de735..b110389 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapterByWeek.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportAdapterByWeek.java @@ -46,9 +46,9 @@ import jakarta.inject.Inject; * Der OPListExportAdapterByWeek ist ählnlich dem Der OPListExportAdapter. Er * erzeugt eine Excel tabelle mit allen offenen Rechnungen und gruppiert diese * nach Abteilungen und KW - * + * * Der OPListExportAdapterByWeek importiert eine Excel Datei aus einem Textblock - * + * *

  * {@code
         textblock-ref
@@ -59,14 +59,14 @@ import jakarta.inject.Inject;
  * 

* Der Adapter erweitert den POIAdapter somit können felder aktualisiert werden * (siehe POIFineReplaceAdapter). - * + * *

* Der Adapter kopiert die Rechnungsdaten in neue Zeilen, welche ab Zeilnenummer * 16 eingefügt werden. *

- * + * * Abschliessend wird die Zelle 'TOTAL' noch aktualisiert. - * + * * @version 1.0 * @author rsoika */ @@ -97,7 +97,7 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { /** * This method finds or create the Zahlungsavis and adds a reference * ($workitemref) to the current invoice. - * + * * @throws PluginException */ @SuppressWarnings("unchecked") @@ -119,9 +119,7 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { Collections.sort(spaces, new ItemCollectionComparator("space.name", true)); // read the template options - ItemCollection evalItemCollection = workflowService.evalWorkflowResult(event, "opliste", - document, - false); + ItemCollection evalItemCollection = workflowService.evalWorkflowResult(event, "opliste", document, false); if (evalItemCollection == null || !evalItemCollection.hasItem("excel-export")) { throw new PluginException(OPListExportAdapter.class.getSimpleName(), CONFIG_ERROR, "missing opliste configuration in model event - please check model configuration"); @@ -155,14 +153,12 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { doc.write(byteArrayOutputStream); byte[] newContent = byteArrayOutputStream.toByteArray(); - FileData fileDataNew = new FileData(fileData.getName(), newContent, fileData.getContentType(), - null); + FileData fileDataNew = new FileData(fileData.getName(), newContent, fileData.getContentType(), null); document.addFileData(fileDataNew); logger.finest("......new document added"); } catch (IOException | QueryException e) { - throw new PluginException(OPListExportAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(OPListExportAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "failed to update opliste: " + e.getMessage()); } finally { if (doc != null) { @@ -180,8 +176,7 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { } catch ( PluginException e) { - throw new PluginException(OPListExportAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(OPListExportAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "failed to update op-liste: " + e.getMessage()); } logger.fine("... completed!"); @@ -193,7 +188,7 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { /** * EIne OP Liste kann für eine oder mehrere Währungen erstellt werden. Diese * Hilfsmethode fügt die Spalten anhand der Währungen für jede Abteilung ein. - * + * * @param sheet * @param currencyList */ @@ -249,13 +244,12 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { *

* The named cell 'TOTAL' should contain the summary formula. It will be * evaluated at the end. - * + * * @throws PluginException * @throws QueryException */ private void insertInvoiceRows(XSSFWorkbook doc, XSSFSheet sheet, List currencyList, - List spaces) - throws PluginException, QueryException { + List spaces) throws PluginException, QueryException { List spaceNames = new ArrayList(); @@ -285,8 +279,8 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { } /* - * --- Phase 1 ------------------------------------------------------- - * Im folgenden gruppieren wir die Rechnungen nach Woche + * --- Phase 1 ------------------------------------------------------- Im + * folgenden gruppieren wir die Rechnungen nach Woche */ // XSSFCell cellKWTotalReference = referenceRowTotal.getCell(14); @@ -306,10 +300,10 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { totalDataMapUeberFaellig.put("TOTAL", new TotalInvoiceData("TOTAL")); /* - * --- Phase 3 ------------------------------------------------------- - * now build a new row for each week and print out the collected invoice data - * first iterate over all objects and collect all known weeks and sort them into - * a ordered list.... + * --- Phase 3 ------------------------------------------------------- now build + * a new row for each week and print out the collected invoice data first + * iterate over all objects and collect all known weeks and sort them into a + * ordered list.... */ List sortedWeekList = new ArrayList(); Iterator> iterator = weekDataMap.entrySet().iterator(); @@ -323,9 +317,9 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { Collections.sort(sortedWeekList); /* - * --- Phase 4 ------------------------------------------------------- - * Nun müssen wir so viele Zeilen nach unten schieben wie wir gleich einfügen. - * Dazu müssen wir kurz einen Probelauf machen um die geplante Anzahl zu + * --- Phase 4 ------------------------------------------------------- Nun + * müssen wir so viele Zeilen nach unten schieben wie wir gleich einfügen. Dazu + * müssen wir kurz einen Probelauf machen um die geplante Anzahl zu * errechnen.... */ int rowPos = 11; @@ -333,8 +327,8 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { sheet.shiftRows(rowPos + 1, 2999, insertCount, true, true); /* - * --- Phase 5 ------------------------------------------------------- - * Jetzt Zahlen Zeile für Zeile einfügen.... + * --- Phase 5 ------------------------------------------------------- Jetzt + * Zahlen Zeile für Zeile einfügen.... */ boolean ueberFaellig = true; for (String week : sortedWeekList) { @@ -399,9 +393,8 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { } /* - * --- Phase 6 ------------------------------------------------------- - * now lets update the totals... - * Überfällig + * --- Phase 6 ------------------------------------------------------- now lets + * update the totals... Überfällig */ int _rowNo = sortedWeekList.size() + 14; XSSFRow rowUeberFaellig = sheet.getRow(_rowNo); @@ -460,16 +453,14 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { /** * Diese Methode gruppiert eine Rechnungsliste nach Kalenderwoche - * + * * @param spaceID - Space Ref to select a list of invoices associated with - * a - * space + * a space * @param weekDataCache - a local cache storing all invoices by week - * - * + * + * */ - private void groupInvoicesByWeek(String spaceID, String spaceName, - Map weekDataCache) { + private void groupInvoicesByWeek(String spaceID, String spaceName, Map weekDataCache) { logger.info("...group invoices for " + spaceName + "/" + spaceID); try { @@ -482,9 +473,7 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { // compute Week Date dueDate = invoice.getItemValueDate("invoice.duedate"); - LocalDate localDate = dueDate.toInstant() - .atZone(ZoneId.systemDefault()) - .toLocalDate(); + LocalDate localDate = dueDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); int weekNumber = localDate.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR); int weekBasedYear = localDate.get(IsoFields.WEEK_BASED_YEAR); String weekCategory = weekBasedYear + "/" + String.format("%02d", weekNumber); @@ -521,17 +510,15 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { /** * This method loads a text-block for a specified ref and appends the named * fileData object of this document. - * + * * @param document * @throws PluginException */ private FileData appendExcelTemplate(ItemCollection document, String textblockRef, String template, - String targetName) - throws PluginException { + String targetName) throws PluginException { if ((template == null || template.isEmpty()) || (textblockRef == null || textblockRef.isEmpty())) { - throw new PluginException(OPListExportAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, + throw new PluginException(OPListExportAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, "invalid POI configuration in model event - textblock/template reference not defined!"); } @@ -540,10 +527,8 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { // do we found the document? if (fileData == null) { - throw new PluginException(OPListExportAdapter.class.getSimpleName(), - InvoiceService.ERROR_CONFIG, - "invalid POI configuration in model event - template=" + template - + " not found!"); + throw new PluginException(OPListExportAdapter.class.getSimpleName(), InvoiceService.ERROR_CONFIG, + "invalid POI configuration in model event - template=" + template + " not found!"); } fileData.setName(targetName); // append document @@ -555,8 +540,8 @@ public class OPListExportAdapterByWeek extends POIFindReplaceAdapter { } /** - * Data Element for invoice totals per week and space. - * The object holds multiple currencies. + * Data Element for invoice totals per week and space. The object holds multiple + * currencies. */ class WeekInvoiceData { @@ -601,8 +586,8 @@ class WeekInvoiceData { } /** - * Data Element for invoice totals per space. - * The object holds multiple currencies. + * Data Element for invoice totals per space. The object holds multiple + * currencies. */ class TotalInvoiceData { diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportController.java index 550b888..37fab8b 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportController.java @@ -2,26 +2,26 @@ package com.alexanderlogistics; /******************************************************************************* * Imixs Workflow Technology - * Copyright (C) 2003, 2008 Imixs Software Solutions GmbH, + * Copyright (C) 2003, 2008 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika - * + * *******************************************************************************/ import java.util.logging.Logger; @@ -34,44 +34,43 @@ import jakarta.inject.Named; /** * The OPListExportController is used to configure the OpListeExporterService. - * This service is - * used to export workitems. + * This service is used to export workitems. *

* The Controller creates a configuration entity "type=configuration; * txtname=OPLIST_EXPORT_CONFIGURATION". - * - * + * + * * @author rsoika - * + * */ @Named(value = "oplistExportController") @RequestScoped public class OPListExportController extends SchedulerController { - public static final String OPLIST_EXPORT_CONFIGURATION = "OPLIST_EXPORT_CONFIGURATION"; + public static final String OPLIST_EXPORT_CONFIGURATION = "OPLIST_EXPORT_CONFIGURATION"; - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(OPListExportController.class.getName()); + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(OPListExportController.class.getName()); - @Inject - OPListExportScheduler opListExportScheduler; + @Inject + OPListExportScheduler opListExportScheduler; - @Override - public String getName() { - return OPLIST_EXPORT_CONFIGURATION; - } + @Override + public String getName() { + return OPLIST_EXPORT_CONFIGURATION; + } - /** - * Returns the sepa scheduler class name. This name depends on the _export_type. - * - * There are two export interfaces available - csv and XML - * - */ - @Override - public String getSchedulerClass() { - String schedulerClass = OPListExportScheduler.class.getName(); - logger.finest("...... scheduler: " + schedulerClass); - return schedulerClass; - } + /** + * Returns the sepa scheduler class name. This name depends on the _export_type. + * + * There are two export interfaces available - csv and XML + * + */ + @Override + public String getSchedulerClass() { + String schedulerClass = OPListExportScheduler.class.getName(); + logger.finest("...... scheduler: " + schedulerClass); + return schedulerClass; + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportScheduler.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportScheduler.java index 154e8c1..542ba56 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportScheduler.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/OPListExportScheduler.java @@ -1,22 +1,22 @@ /******************************************************************************* * Imixs Workflow Technology - * Copyright (C) 2001, 2008 Imixs Software Solutions GmbH, + * Copyright (C) 2001, 2008 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika *******************************************************************************/ @@ -61,10 +61,10 @@ import jakarta.inject.Inject; * The class implements the interface * _org.imixs.workflow.engine.scheduler.Scheduler_ and can be used in * combination with the Imxis-Workflow Scheduler Service. - * + * * @see SchedulerService * @author rsoika - * + * */ public class OPListExportScheduler implements Scheduler { @@ -89,7 +89,7 @@ public class OPListExportScheduler implements Scheduler { /** * This is the method which processes the timeout event depending on the running * timer settings. - * + * * @param timer * @throws QueryException */ @@ -131,7 +131,7 @@ public class OPListExportScheduler implements Scheduler { /** * Prüfen ob es Rechnungen in diesem Space gibt die entweder die 2. Mahnstufe * erreicht haben oder deren Fälligkeit um mehr als 21 Tage überschritten ist. - * + * * @param space * @return */ @@ -139,8 +139,8 @@ public class OPListExportScheduler implements Scheduler { List invoices = new ArrayList(); try { invoices = documentService.find( - "$modelversion:rechnungsausgang-* AND type:workitem AND $uniqueidref:" + space.getUniqueID(), - 999, 0); + "$modelversion:rechnungsausgang-* AND type:workitem AND $uniqueidref:" + space.getUniqueID(), 999, + 0); } catch (QueryException e) { logger.warning("Failed to compute invoice list: " + e.getMessage()); e.printStackTrace(); @@ -168,7 +168,7 @@ public class OPListExportScheduler implements Scheduler { /** * Creates a new log entry stored in the item _scheduler_log. The log can be * writen optional to the configuraiton and the workitem - * + * * @param message * @param configuration */ diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/RenameFilenamesAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/RenameFilenamesAdapter.java index a4763dc..662d1c5 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/RenameFilenamesAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/RenameFilenamesAdapter.java @@ -17,123 +17,122 @@ import jakarta.inject.Inject; /** * Der RenameFilenamesAdapter kann optional eingesetzt werden. Dieser Adapter * verändert den Dateinamen von angehangenen Rechnungen - * + * * Der Adapter wir beispielweise in DWC eingesetzt bevor die Dateien in einen * FTP Server hochgeladen werden. - * + * * Die Dateinamen werden vom System berechnet. - * + * * sequencenumber + "_" +invoice.positions+".pdf" - * - * + * + * * Bei Sachrechnungen - * - * sequencenumber + "_" + invoice.number.stripped + ".pdf" - * wird das Rechnungsdatum verwendet um die Buchungsperiode zu berechnen. - * - * - * + * + * sequencenumber + "_" + invoice.number.stripped + ".pdf" wird das + * Rechnungsdatum verwendet um die Buchungsperiode zu berechnen. + * + * + * * @author rsoika * @version 1.0 - * + * */ public class RenameFilenamesAdapter implements SignalAdapter { - private static Logger logger = Logger.getLogger(RenameFilenamesAdapter.class.getName()); + private static Logger logger = Logger.getLogger(RenameFilenamesAdapter.class.getName()); - @Inject - SnapshotService snapshotService; + @Inject + SnapshotService snapshotService; - /** - * Rename Filenames - * - * @throws PluginException - * - **/ - @Override - public ItemCollection execute(ItemCollection workitem, ItemCollection event) - throws AdapterException, PluginException { + /** + * Rename Filenames + * + * @throws PluginException + * + **/ + @Override + public ItemCollection execute(ItemCollection workitem, ItemCollection event) + throws AdapterException, PluginException { - int fileNameCounter = 0; // for multiple files - List fileDataSet = workitem.getFileData(); - for (FileData fileData : fileDataSet) { - String fileName = fileData.getName(); - // only PDF files and X-Rechnung - if ((fileName.toLowerCase().endsWith(".pdf") - || fileName.toLowerCase().endsWith(".xml"))) { + int fileNameCounter = 0; // for multiple files + List fileDataSet = workitem.getFileData(); + for (FileData fileData : fileDataSet) { + String fileName = fileData.getName(); + // only PDF files and X-Rechnung + if ((fileName.toLowerCase().endsWith(".pdf") || fileName.toLowerCase().endsWith(".xml"))) { - // Build new Filename - String newFileName = buildFileName(fileName, workitem, fileNameCounter); - if (!newFileName.equals(fileName)) { - logger.info("Rename file name " + fileName + " -> " + newFileName); - // do we have content - if (fileData.getContent().length < 10) { - // fetch snaphot data - FileData snapShotFileData = snapshotService.getWorkItemFile(workitem.getUniqueID(), fileName); - // remove old data - workitem.removeFile(fileName); - snapShotFileData.setName(newFileName); - workitem.addFileData(snapShotFileData); - } else { - fileData.setName(newFileName); - } - fileNameCounter++; - } + // Build new Filename + String newFileName = buildFileName(fileName, workitem, fileNameCounter); + if (!newFileName.equals(fileName)) { + logger.info("Rename file name " + fileName + " -> " + newFileName); + // do we have content + if (fileData.getContent().length < 10) { + // fetch snaphot data + FileData snapShotFileData = snapshotService.getWorkItemFile(workitem.getUniqueID(), fileName); + // remove old data + workitem.removeFile(fileName); + snapShotFileData.setName(newFileName); + workitem.addFileData(snapShotFileData); + } else { + fileData.setName(newFileName); + } + fileNameCounter++; + } - // Update FTP Target path information - String targetPath = ""; - if (InvoiceUtil.isCreditorInvoice(workitem)) { - targetPath = "invoice-in/"; - String bookingPath = workitem.getItemValueString("invoice.period"); - // Sachrechnung - if (bookingPath.isEmpty()) { - // build period from invoice.date - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM"); - LocalDate invoiceDate = workitem.getItemValueLocalDate("invoice.date"); - bookingPath = invoiceDate.format(formatter); - } - if (bookingPath.length() > 4) { - targetPath = targetPath + bookingPath.substring(0, 4) + "/" + bookingPath.substring(4); - } - } else { - targetPath = "invoice-out/"; - String bookingPath = workitem.getItemValueString("invoice.bookingperiod"); - targetPath = targetPath + bookingPath.substring(0, 4) + "/" + bookingPath.substring(4); - } + // Update FTP Target path information + String targetPath = ""; + if (InvoiceUtil.isCreditorInvoice(workitem)) { + targetPath = "invoice-in/"; + String bookingPath = workitem.getItemValueString("invoice.period"); + // Sachrechnung + if (bookingPath.isEmpty()) { + // build period from invoice.date + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM"); + LocalDate invoiceDate = workitem.getItemValueLocalDate("invoice.date"); + bookingPath = invoiceDate.format(formatter); + } + if (bookingPath.length() > 4) { + targetPath = targetPath + bookingPath.substring(0, 4) + "/" + bookingPath.substring(4); + } + } else { + targetPath = "invoice-out/"; + String bookingPath = workitem.getItemValueString("invoice.bookingperiod"); + targetPath = targetPath + bookingPath.substring(0, 4) + "/" + bookingPath.substring(4); + } - workitem.setItemValue("ftp.target.path", targetPath + "/"); - } + workitem.setItemValue("ftp.target.path", targetPath + "/"); + } - } + } - return workitem; - } + return workitem; + } - /** - * Baut einen Filenamen aus verschiedenen Attributen zusammen. - * - * - * @param workitem - * @return - */ - private String buildFileName(String fileName, ItemCollection workitem, int counter) { - String result = workitem.getItemValueString("sequencenumber") + "_"; + /** + * Baut einen Filenamen aus verschiedenen Attributen zusammen. + * + * + * @param workitem + * @return + */ + private String buildFileName(String fileName, ItemCollection workitem, int counter) { + String result = workitem.getItemValueString("sequencenumber") + "_"; - if (InvoiceUtil.isSachrechnung(workitem)) { - result = result + workitem.getItemValueString("invoice.number.stripped"); - } else { - // für ausgangsrechnungen skippen wir die Cargosoft XML Datei - if (fileName.toLowerCase().endsWith(".xml")) { - return fileName; - } - // für die PDF Datei bauen wir einen neuen Namen - result = result + workitem.getItemValueString("invoice.positions"); - } - if (counter > 0) { - result = result + "_" + counter; - } - result = result + fileName.substring(fileName.lastIndexOf(".")); + if (InvoiceUtil.isSachrechnung(workitem)) { + result = result + workitem.getItemValueString("invoice.number.stripped"); + } else { + // für ausgangsrechnungen skippen wir die Cargosoft XML Datei + if (fileName.toLowerCase().endsWith(".xml")) { + return fileName; + } + // für die PDF Datei bauen wir einen neuen Namen + result = result + workitem.getItemValueString("invoice.positions"); + } + if (counter > 0) { + result = result + "_" + counter; + } + result = result + fileName.substring(fileName.lastIndexOf(".")); - return result; - } + return result; + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SEPAExportControllerAGL.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SEPAExportControllerAGL.java index 3de738c..d7d71e9 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SEPAExportControllerAGL.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SEPAExportControllerAGL.java @@ -14,10 +14,10 @@ import jakarta.inject.Inject; import jakarta.inject.Named; /** - * Der SEPAExportControllerAGL dient dazu die IBAN/BIC einstellungen in - * einem SEPA Export zu aktualiseren. Er wird von der Form - * sepa_export.xhtml aus per Ajax angesteuert. - * + * Der SEPAExportControllerAGL dient dazu die IBAN/BIC einstellungen in einem + * SEPA Export zu aktualiseren. Er wird von der Form sepa_export.xhtml aus per + * Ajax angesteuert. + * * @author rsoika * */ diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SteuerListController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SteuerListController.java index 5b1ce41..dd2334a 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SteuerListController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SteuerListController.java @@ -12,9 +12,9 @@ import jakarta.inject.Inject; import jakarta.inject.Named; /** - * Der SteuerListController dient dazu in einem Stuerbescheid - * die einzelnen Zeilen auszugeben. - * + * Der SteuerListController dient dazu in einem Stuerbescheid die einzelnen + * Zeilen auszugeben. + * * @author rsoika * */ @@ -32,7 +32,7 @@ public class SteuerListController implements Serializable { /** * Gibt die Zeilen aus einem importierten Steuerbeleg (eAkte) zurück - * + * * @return */ public List getRows() { diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SteuerbescheidPlugin.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SteuerbescheidPlugin.java index dc0aa00..7430eb9 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SteuerbescheidPlugin.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/SteuerbescheidPlugin.java @@ -15,165 +15,161 @@ import jakarta.inject.Inject; /** * Das SteuerbescheidPlugin prüft ob - * + * * - die ATC Nummer aus dem Steuerbescheid in einer Ausgangsrechnung gefunden - * wird ($taskID<5990) - * - invoice.atc.number - * - der Betrag "invoice.total.net" - * - die Währung??? - * - die Positionsnummer ??? - * - * + * wird ($taskID<5990) - invoice.atc.number - der Betrag "invoice.total.net" - + * die Währung??? - die Positionsnummer ??? + * + * * @author rsoika * @version 1.0 - * + * */ public class SteuerbescheidPlugin extends AbstractPlugin { - private static Logger logger = Logger.getLogger(SteuerbescheidPlugin.class.getName()); + private static Logger logger = Logger.getLogger(SteuerbescheidPlugin.class.getName()); - @Inject - protected DocumentService documentService; + @Inject + protected DocumentService documentService; - /** - * Prüft ob der Steuerbetrag abgerechnet ist. - * - * - **/ - @Override - public ItemCollection run(ItemCollection workitem, ItemCollection event) throws PluginException { + /** + * Prüft ob der Steuerbetrag abgerechnet ist. + * + * + **/ + @Override + public ItemCollection run(ItemCollection workitem, ItemCollection event) throws PluginException { - // Update Fälligkeit - updateFaelligkeit(workitem); + // Update Fälligkeit + updateFaelligkeit(workitem); - // Update Salden - updateTotalEustZoll(workitem); - updateInvoiceSaldo(workitem); + // Update Salden + updateTotalEustZoll(workitem); + updateInvoiceSaldo(workitem); - return workitem; - } + return workitem; + } - /** - * Aktualisiert die EUST/ZOLL Summen - * - */ - protected void updateTotalEustZoll(ItemCollection workitem) { + /** + * Aktualisiert die EUST/ZOLL Summen + * + */ + protected void updateTotalEustZoll(ItemCollection workitem) { - double saldoEust = 0; - double saldoZoll = 0; - List positionsTabelle = InvoiceUtil.explodeChildList(workitem); - for (ItemCollection pos : positionsTabelle) { - if ("EUST".equalsIgnoreCase(pos.getItemValueString("activity.type"))) { - saldoEust = saldoEust + pos.getItemValueDouble("amount"); - } - if ("ZOLL".equalsIgnoreCase(pos.getItemValueString("activity.type"))) { - saldoZoll = saldoZoll + pos.getItemValueDouble("amount"); - } - } + double saldoEust = 0; + double saldoZoll = 0; + List positionsTabelle = InvoiceUtil.explodeChildList(workitem); + for (ItemCollection pos : positionsTabelle) { + if ("EUST".equalsIgnoreCase(pos.getItemValueString("activity.type"))) { + saldoEust = saldoEust + pos.getItemValueDouble("amount"); + } + if ("ZOLL".equalsIgnoreCase(pos.getItemValueString("activity.type"))) { + saldoZoll = saldoZoll + pos.getItemValueDouble("amount"); + } + } - workitem.setItemValue("steuer.eust", saldoEust); - workitem.setItemValue("steuer.zoll", saldoZoll); + workitem.setItemValue("steuer.eust", saldoEust); + workitem.setItemValue("steuer.zoll", saldoZoll); - } + } - /** - * Aktualisiert die EUST/ZOLL Fälligkeiten in den Detailtabellen - * Die Daten kommen von der KI und werden auf die Positionsnummern gemappt. - * - */ - protected void updateFaelligkeit(ItemCollection workitem) { + /** + * Aktualisiert die EUST/ZOLL Fälligkeiten in den Detailtabellen Die Daten + * kommen von der KI und werden auf die Positionsnummern gemappt. + * + */ + protected void updateFaelligkeit(ItemCollection workitem) { - boolean foundZoll = false; - boolean foundEust = false; - Date datEust = null; - Date datZoll = null; - // resolve datEust und datZoll from steuerbescheid.positionen ChildItems.... - List steuerPositionen = InvoiceUtil.explodeChildList(workitem, "steuerbescheid.position"); - for (ItemCollection steuerPos : steuerPositionen) { - if (steuerPos.getItemValueString("key").startsWith("A")) { - datZoll = steuerPos.getItemValueDate("duedate"); - } - if (steuerPos.getItemValueString("key").startsWith("B")) { - datEust = steuerPos.getItemValueDate("duedate"); - } - } + boolean foundZoll = false; + boolean foundEust = false; + Date datEust = null; + Date datZoll = null; + // resolve datEust und datZoll from steuerbescheid.positionen ChildItems.... + List steuerPositionen = InvoiceUtil.explodeChildList(workitem, "steuerbescheid.position"); + for (ItemCollection steuerPos : steuerPositionen) { + if (steuerPos.getItemValueString("key").startsWith("A")) { + datZoll = steuerPos.getItemValueDate("duedate"); + } + if (steuerPos.getItemValueString("key").startsWith("B")) { + datEust = steuerPos.getItemValueDate("duedate"); + } + } - // Map fälligkeit auf die Cargosoft Positionen - List rows = InvoiceUtil.explodeChildList(workitem); - for (ItemCollection pos : rows) { - if (datZoll != null && "ZOLL".equalsIgnoreCase(pos.getItemValueString("activity.type"))) { - pos.setItemValue("duedate", datZoll); - foundZoll = true; - } - if (datEust != null && "EUST".equalsIgnoreCase(pos.getItemValueString("activity.type"))) { - pos.setItemValue("duedate", datEust); - foundEust = true; - } - } + // Map fälligkeit auf die Cargosoft Positionen + List rows = InvoiceUtil.explodeChildList(workitem); + for (ItemCollection pos : rows) { + if (datZoll != null && "ZOLL".equalsIgnoreCase(pos.getItemValueString("activity.type"))) { + pos.setItemValue("duedate", datZoll); + foundZoll = true; + } + if (datEust != null && "EUST".equalsIgnoreCase(pos.getItemValueString("activity.type"))) { + pos.setItemValue("duedate", datEust); + foundEust = true; + } + } - if (foundEust) { - workitem.setItemValue("steuer.eust.due", datEust); - } else { - workitem.removeItem("steuer.eust.due"); - } - if (foundZoll) { - workitem.setItemValue("steuer.zoll.due", datZoll); - } else { - workitem.removeItem("steuer.zoll.due"); - } - InvoiceUtil.implodeChildList(workitem, rows); - } + if (foundEust) { + workitem.setItemValue("steuer.eust.due", datEust); + } else { + workitem.removeItem("steuer.eust.due"); + } + if (foundZoll) { + workitem.setItemValue("steuer.zoll.due", datZoll); + } else { + workitem.removeItem("steuer.zoll.due"); + } + InvoiceUtil.implodeChildList(workitem, rows); + } - /** - * Aktualisiert den invoice.saldo anhand der ausgehenden Rechnungen zu einer ATC - * Nummer - * - */ - protected void updateInvoiceSaldo(ItemCollection workitem) { + /** + * Aktualisiert den invoice.saldo anhand der ausgehenden Rechnungen zu einer ATC + * Nummer + * + */ + protected void updateInvoiceSaldo(ItemCollection workitem) { - String atcNummer = workitem.getItemValueString("invoice.atc.number"); - logger.fine("Verify ATC-Nr.: " + atcNummer); - double amount = workitem.getItemValueDouble("invoice.total.net"); - List invoices = findInvoices(atcNummer); + String atcNummer = workitem.getItemValueString("invoice.atc.number"); + logger.fine("Verify ATC-Nr.: " + atcNummer); + double amount = workitem.getItemValueDouble("invoice.total.net"); + List invoices = findInvoices(atcNummer); - // Beträge subtrahieren.... - double saldo = amount; - logger.info("Found " + invoices.size() + " matching invoices..."); - for (ItemCollection invoice : invoices) { - workitem.setItemValueUnique("$workitemRef", invoice.getUniqueID()); + // Beträge subtrahieren.... + double saldo = amount; + logger.info("Found " + invoices.size() + " matching invoices..."); + for (ItemCollection invoice : invoices) { + workitem.setItemValueUnique("$workitemRef", invoice.getUniqueID()); - // suche die Zeile in der die ATC Nummer vorkommt. - List positionen = InvoiceUtil.explodeChildList(invoice); - for (ItemCollection pos : positionen) { - if (atcNummer.equals(pos.getItemValueString("atc.number"))) { - double umsatz = pos.getItemValueDouble("datev.umsatz"); - logger.info("...Umsatz=" + umsatz); - saldo = InvoiceUtil.round(saldo - umsatz); - } - } - } + // suche die Zeile in der die ATC Nummer vorkommt. + List positionen = InvoiceUtil.explodeChildList(invoice); + for (ItemCollection pos : positionen) { + if (atcNummer.equals(pos.getItemValueString("atc.number"))) { + double umsatz = pos.getItemValueDouble("datev.umsatz"); + logger.info("...Umsatz=" + umsatz); + saldo = InvoiceUtil.round(saldo - umsatz); + } + } + } - // Update Saldo - workitem.setItemValue("invoice.saldo", InvoiceUtil.round(saldo)); + // Update Saldo + workitem.setItemValue("invoice.saldo", InvoiceUtil.round(saldo)); - } + } - /* - * Sucht alle Ausgansrechnungen zu einer ATC Nummer - */ - protected List findInvoices(String atcNumber) { - List result = new ArrayList(); - String query = "(type:workitem OR type:workitemarchive) AND ($taskid:[5000 TO 5990])" - + " AND invoice.atc.number:\"" + atcNumber + "\" " - + " AND $workflowgroup:\"Rechnungsausgang\" "; - try { - result = documentService.find(query, 999, 0, "$created", false); - return result; - } catch (QueryException e) { - logger.severe("Failed to get invoices: " + e.getMessage()); - } + /* + * Sucht alle Ausgansrechnungen zu einer ATC Nummer + */ + protected List findInvoices(String atcNumber) { + List result = new ArrayList(); + String query = "(type:workitem OR type:workitemarchive) AND ($taskid:[5000 TO 5990])" + + " AND invoice.atc.number:\"" + atcNumber + "\" " + " AND $workflowgroup:\"Rechnungsausgang\" "; + try { + result = documentService.find(query, 999, 0, "$created", false); + return result; + } catch (QueryException e) { + logger.severe("Failed to get invoices: " + e.getMessage()); + } - return result; - } + return result; + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisAppendAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisAppendAdapter.java index 9dda062..e2eba98 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisAppendAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisAppendAdapter.java @@ -21,9 +21,9 @@ import jakarta.inject.Inject; * Kredito erzeugt der Adapter automatisch eine neue Prozessinstanz. *

* Wurde die Rechnugn bereits einem Zahlungsavis zugeordnet passiert nichts. - * - * - * + * + * + * * @version 1.0 * @author rsoika */ @@ -40,7 +40,7 @@ public class ZahlungsavisAppendAdapter implements SignalAdapter { /** * This method finds or create the Zahlungsavis and adds a reference * ($workitemref) to the current invoice. - * + * * @throws PluginException */ @Override @@ -54,7 +54,7 @@ public class ZahlungsavisAppendAdapter implements SignalAdapter { /** * Diese method hängt eine referenz der aktuellen Rechnung an den Zahlungsavis - * + * * @param document * @throws PluginException */ @@ -100,7 +100,7 @@ public class ZahlungsavisAppendAdapter implements SignalAdapter { /** * Prüft alle offenen Zahlugnsaviss und gibt den neuesten zur angegebenen * cdrNumber zurück, oder null falls es keinen Offenen Zahlungsavis gibt. - * + * * @param cdrNumber * @return * @throws QueryException diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisController.java index 0e98a34..5ee511d 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisController.java @@ -18,7 +18,7 @@ import jakarta.inject.Named; *

* Zusätzlich bietet er die Summenberechnung an, bei der die Gutschriften * abgezogen werden. - * + * * @author rsoika * */ @@ -41,7 +41,7 @@ public class ZahlungsavisController implements Serializable { * result is rounded to 2 digits. *

* Gutschriften werden abgezogen - * + * * @param refids - list of workitem uniqueIds * @param item - name of the item to summarize * @return sum rounded to 2 digits diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisExportAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisExportAdapter.java index 7904a7b..2eaeebb 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisExportAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungsavisExportAdapter.java @@ -27,7 +27,7 @@ import jakarta.inject.Inject; /** * Der ZahlungsavisAdapter importiert eine Excel Datei aus einem Textblock - * + * *

  * {@code
         textblock-ref
@@ -38,7 +38,7 @@ import jakarta.inject.Inject;
  * 

* Der Adapter erweitert den POIAdapter somit können felder aktualisiert werden * (siehe POIFineReplaceAdapter). - * + * *

* Der Adapter kopiert die Rechnungsdaten in neue Zeilen, welche ab Zeilnenummer * 16 eingefügt werden. @@ -46,7 +46,7 @@ import jakarta.inject.Inject; * Dabei werden Gutschriften in spalte E und Rechnungen in Spalte F eingetrgen. *

* Abschliessend wird die Zelle 'TOTAL' noch aktualisiert. - * + * * @version 1.0 * @author rsoika */ @@ -69,7 +69,7 @@ public class ZahlungsavisExportAdapter extends POIFindReplaceAdapter { /** * This method finds or create the Zahlungsavis and adds a reference * ($workitemref) to the current invoice. - * + * * @throws PluginException */ @SuppressWarnings("unchecked") @@ -121,7 +121,7 @@ public class ZahlungsavisExportAdapter extends POIFindReplaceAdapter { *

* The named cell 'TOTAL' should contain the summary formula. It will be * evaluated at the end. - * + * * @throws PluginException */ @SuppressWarnings("unchecked") @@ -193,7 +193,7 @@ public class ZahlungsavisExportAdapter extends POIFindReplaceAdapter { /** * This method loads a text-block for a specified ref and appends the named * fileData object of this document. - * + * * @param document * @throws PluginException */ @@ -223,9 +223,9 @@ public class ZahlungsavisExportAdapter extends POIFindReplaceAdapter { /** * This method returns a text-block ItemCollection for a specified name. - * + * * @param name in attribute txtname - * + * * */ public FileData loadTextBlockFileData(String name, String fileName) { diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungseingangSaldoAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungseingangSaldoAdapter.java index cf049d3..27c9809 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungseingangSaldoAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungseingangSaldoAdapter.java @@ -28,7 +28,7 @@ import jakarta.inject.Inject; *

  • payment.amount *

    * com.alexanderlogistics.ZahlungseingangSaldoAdapter - * + * * @version 1.0 * @author rsoika */ @@ -51,7 +51,7 @@ public class ZahlungseingangSaldoAdapter implements SignalAdapter { /** * This method finds the outgoing invoices and updates the saldo * (payment.total). - * + * * @throws PluginException */ @Override @@ -72,7 +72,7 @@ public class ZahlungseingangSaldoAdapter implements SignalAdapter { /** * Diese methode aktuallisiert alle referenzen der aktuellen Rechnungen - * + * * @param workitem * @throws PluginException */ @@ -127,13 +127,11 @@ public class ZahlungseingangSaldoAdapter implements SignalAdapter { String mainCurrency = invoiceService.getCurrenciesOutMain(); if ((!invoiceCurrency.equals(paymentCurrency)) - && (!invoiceCurrency.equals(mainCurrency) && - !paymentCurrency.equals(mainCurrency))) { + && (!invoiceCurrency.equals(mainCurrency) && !paymentCurrency.equals(mainCurrency))) { String message = resourceBundleHandler.findMessage("ERROR_PAYMENT4"); message = message.replace("{1}", mainCurrency); throw new PluginException(PluginException.class.getName(), - ZahlungseingangService.ERROR_PAYMENT_BOOKING_FAILED, - message); + ZahlungseingangService.ERROR_PAYMENT_BOOKING_FAILED, message); } if (!invoiceCurrency.equals(paymentCurrency)) { @@ -142,8 +140,7 @@ public class ZahlungseingangSaldoAdapter implements SignalAdapter { message = message.replace("{1}", paymentCurrency); message = message.replace("{2}", invoiceNumber); throw new PluginException(PluginException.class.getName(), - ZahlungseingangService.ERROR_PAYMENT_BOOKING_FAILED, - message); + ZahlungseingangService.ERROR_PAYMENT_BOOKING_FAILED, message); } } @@ -151,8 +148,7 @@ public class ZahlungseingangSaldoAdapter implements SignalAdapter { * Falls die Währung der Rechnung von der Währung der Zahlung abweicht, dann * rechnen wir den PaymentAmmout mit der Rate aus. */ - if ("EUR".equals(paymentCurrency) && - !invoiceCurrency.equals(paymentCurrency) && invoiceRate != 0) { + if ("EUR".equals(paymentCurrency) && !invoiceCurrency.equals(paymentCurrency) && invoiceRate != 0) { invoicePaymentAmount = invoicePaymentAmount * invoiceRate; // runde auf 2 Stellen invoicePaymentAmount = (float) InvoiceUtil.round(invoicePaymentAmount); @@ -213,7 +209,7 @@ public class ZahlungseingangSaldoAdapter implements SignalAdapter { /** * Returns true wenn der Zahlungseingang bereits gebucht ist - * + * * @param workitem * @return */ diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungseingangService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungseingangService.java index 542a180..365c85c 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungseingangService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ZahlungseingangService.java @@ -1,26 +1,26 @@ /******************************************************************************* - * Imixs Workflow - * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, + * Imixs Workflow + * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Project: + * + * Project: * http://www.imixs.org * http://java.net/projects/imixs-workflow - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika - Software Developer *******************************************************************************/ @@ -49,9 +49,9 @@ import jakarta.inject.Inject; /** * Der ZahlungseingangService wird vom ZahlungseingangSaldoAdapter verwendet um * Ausgangsrechnungen zu akutalisieren. - * + * * @author rsoika - * + * */ @DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", @@ -79,7 +79,7 @@ public class ZahlungseingangService { /** * Diese Method sucht eine Invoice mit Manager Rechnten - * + * * @throws PluginException */ public ItemCollection findInvoice(String id) throws PluginException { @@ -89,7 +89,7 @@ public class ZahlungseingangService { /** * * Diese Method processed eine Invoice mit Manager Rechnten - * + * * @throws PluginException * @throws ModelException * @throws ProcessingErrorException @@ -106,10 +106,10 @@ public class ZahlungseingangService { /** * The method returns a List of ItemCollection holding the payment details - * - * + * + * * Convert the List of ItemCollections back into a List of Map elements - * + * * @param invoice */ @SuppressWarnings({ "rawtypes", "unchecked" }) diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ai/PromptExampleAdapterXML.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ai/PromptExampleAdapterXML.java index 243bd2e..ff0f15f 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ai/PromptExampleAdapterXML.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/ai/PromptExampleAdapterXML.java @@ -15,23 +15,23 @@ import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; /** - * The PromptExampleAdapterXML is a CDI bean that creates XML examples based - * on the last 3 invoices. - * + * The PromptExampleAdapterXML is a CDI bean that creates XML examples based on + * the last 3 invoices. + * * The adapter only runs if the prompt template contains - * + * * <> - * + * * Prompt example generated by this Adapter: - * + * * Example: - * + * *

      * {@code
      * 1. Company Data:
      * - Name and address of the supplier
      * - Supplier contact information (phone number, email, etc.)
    - * 
    + *
      * 2. Invoice Data:
      * - Invoice number
      * - Invoice date
    @@ -41,7 +41,7 @@ import jakarta.inject.Inject;
      *
      * 
      *   ...
    - *   ... 
    + *   ...
      *   2024-12-31
      *   2024-12-31
      *   1234.00
    @@ -50,7 +50,7 @@ import jakarta.inject.Inject;
      * 
     * }
     * 
    - * + * */ public class PromptExampleAdapterXML { private static Logger logger = Logger.getLogger(PromptExampleAdapterXML.class.getName()); @@ -79,8 +79,7 @@ public class PromptExampleAdapterXML { if (invoices != null && invoices.size() > 0) { // Build an example for each invoice for (ItemCollection invoice : invoices) { - promptExamples = promptExamples + buildExampleDE(invoice, exampleHeaderData, - exampleHeaderXML); + promptExamples = promptExamples + buildExampleDE(invoice, exampleHeaderData, exampleHeaderXML); } } prompt = prompt.replace("<>", "" + promptExamples); @@ -91,13 +90,13 @@ public class PromptExampleAdapterXML { /** * Helper Method that builds a prompt example out of an existing invoice: - * + * *
          * {@code
          *
          * 
          *   ...
    -     *   ... 
    +     *   ...
          *   2024-12-31
          *   2024-12-31
          *   1234.00
    @@ -106,7 +105,7 @@ public class PromptExampleAdapterXML {
          * 
         * }
         * 
    - * + * * @param invoice * @return */ @@ -126,14 +125,12 @@ public class PromptExampleAdapterXML { example.append( " " + invoice.getItemValueString("invoice.number") + "\n"); if (invoice.getItemValueDate("invoice.date") != null) { - example.append( - " " + dateFormat.format(invoice.getItemValueDate("invoice.date")) - + "\n"); + example.append(" " + dateFormat.format(invoice.getItemValueDate("invoice.date")) + + "\n"); } if (invoice.getItemValueDate("invoice.duedate") != null) { - example.append( - " " + dateFormat.format(invoice.getItemValueDate("invoice.duedate")) - + "\n"); + example.append(" " + dateFormat.format(invoice.getItemValueDate("invoice.duedate")) + + "\n"); } example.append(" " + invoice.getItemValueDouble("invoice.total") + "\n"); @@ -146,7 +143,7 @@ public class PromptExampleAdapterXML { /** * Helper method to find the last 3 invoices by cdtr.name - * + * * @param cdtrName * @return */ @@ -154,8 +151,7 @@ public class PromptExampleAdapterXML { List result = new ArrayList<>(); String searchTerm = "(type:workitemarchive) AND ($modelversion:rechnungseingang*) AND ($taskid:5900) AND NOT ($uniqueid:" - + currentID + ") AND (document.company:\"" - + companyName + "\")"; + + currentID + ") AND (document.company:\"" + companyName + "\")"; try { result = documentService.find(searchTerm, 3, 0, "$modified", true); } catch (QueryException e) { diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/CargosoftMigrationRestService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/CargosoftMigrationRestService.java index 38c757c..333750d 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/CargosoftMigrationRestService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/CargosoftMigrationRestService.java @@ -1,26 +1,26 @@ /******************************************************************************* - * Imixs Workflow - * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, + * Imixs Workflow + * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Project: + * + * Project: * http://www.imixs.org * http://java.net/projects/imixs-workflow - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika - Software Developer *******************************************************************************/ @@ -60,9 +60,9 @@ import jakarta.ws.rs.core.MediaType; /** ** Dieser Service korrigiert falsch importierte Cargosoft daten. * Ausgangsrechnungen die ein falsches Jahr haben. - * + * * Oder DAten die über die XML Schnittstelle falsch reinkamen... - * + * * @author rsoika * @version 1.1 */ @@ -109,12 +109,12 @@ public class CargosoftMigrationRestService implements Serializable { * Dieser agent speichert einfach alle Cargosoft Krediotren/Debitoren Daten , * was dann zur automatischen Neuanlage bzw. aktualisierung des businesspartner * workflows führt. - * - * + * + * * curl -H "Cookie: * JSESSIONID=ShAegfNiXmqLFLSFwcUZ7hrLLQDKZGKiu4oj_xeX.imixs-office-workflow-8586d7d58d-h6k4q" * https://alexander-logistics-dwc.office-workflow.de/api/cargosoft/bp-sync?maxcount=1000 - * + * * @return * @throws QueryException * @throws AccessDeniedException @@ -145,8 +145,7 @@ public class CargosoftMigrationRestService implements Serializable { isRunning = true; int totalCount = documentService.count(query); - log("│   ├── found " + totalCount + " cargosoft entries", - messageBuffer); + log("│   ├── found " + totalCount + " cargosoft entries", messageBuffer); // Berechne Anzahl der benötigten Pages int totalPages = (int) Math.ceil((double) totalCount / batchSize); @@ -169,8 +168,8 @@ public class CargosoftMigrationRestService implements Serializable { } // Fortschritt loggen - log("│ ├── Processed page " + (pageIndex + 1) + " of " + totalPages + - " (" + totalObjects + " objects read, " + syncs + " total syncs)", messageBuffer); + log("│ ├── Processed page " + (pageIndex + 1) + " of " + totalPages + " (" + totalObjects + + " objects read, " + syncs + " total syncs)", messageBuffer); // Optional: Kurze Pause nach jedem 5. Batch try { Thread.sleep(100); @@ -185,8 +184,8 @@ public class CargosoftMigrationRestService implements Serializable { } long duration = System.currentTimeMillis() - l; double objectsPerSecond = totalObjects / (duration / 1000.0); - log("├── Successfully " + syncs + " business partner objects synced in " + - duration + "ms (" + String.format("%.1f", objectsPerSecond) + " objects/sec)", messageBuffer); + log("├── Successfully " + syncs + " business partner objects synced in " + duration + "ms (" + + String.format("%.1f", objectsPerSecond) + " objects/sec)", messageBuffer); isRunning = false; return messageBuffer.toString(); @@ -194,7 +193,7 @@ public class CargosoftMigrationRestService implements Serializable { /** * Hilfsmethode speichert eine cargoosft kreditor object... - * + * * @param bpID * @param messageBuffer * @return @@ -213,11 +212,11 @@ public class CargosoftMigrationRestService implements Serializable { /** * Dieser agent löscht einfach alle Businesspartner objekte. Wird eigentlich * nicht mehr benötigt. - * + * * curl -H "Cookie: * JSESSIONID=ShAegfNiXmqLFLSFwcUZ7hrLLQDKZGKiu4oj_xeX.imixs-office-workflow-8586d7d58d-h6k4q" * https://alexander-logistics-dwc.office-workflow.de/api/cargosoft/bp-delete?maxcount=10000 - * + * * @return * @throws QueryException * @throws AccessDeniedException @@ -273,8 +272,8 @@ public class CargosoftMigrationRestService implements Serializable { // Fortschritt loggen iterations++; - log("│ ├── Processed page " + (iterations) + " of " + totalPages + - " (" + deletions + " total deletions)", messageBuffer); + log("│ ├── Processed page " + (iterations) + " of " + totalPages + " (" + deletions + " total deletions)", + messageBuffer); // Optional: Kurze Pause nach jedem 5. Batch try { @@ -290,8 +289,8 @@ public class CargosoftMigrationRestService implements Serializable { } long duration = System.currentTimeMillis() - l; double objectsPerSecond = totalObjects / (duration / 1000.0); - log("├── Successfully " + deletions + " business partner objects deleted in " + - duration + "ms (" + String.format("%.1f", objectsPerSecond) + " objects/sec)", messageBuffer); + log("├── Successfully " + deletions + " business partner objects deleted in " + duration + "ms (" + + String.format("%.1f", objectsPerSecond) + " objects/sec)", messageBuffer); isRunning = false; return messageBuffer.toString(); @@ -299,7 +298,7 @@ public class CargosoftMigrationRestService implements Serializable { /** * Entfernt doppelte Eintrage - sind irgnedwie entstandnen :((( - * + * * @return * @throws QueryException * @throws AccessDeniedException @@ -373,8 +372,8 @@ public class CargosoftMigrationRestService implements Serializable { // Fortschritt loggen // Fortschritt loggen - log("│ ├── Processed page " + (pageIndex + 1) + " of " + totalPages + - " (" + deletions + " total deletions)", messageBuffer); + log("│ ├── Processed page " + (pageIndex + 1) + " of " + totalPages + " (" + deletions + + " total deletions)", messageBuffer); // Optional: Kurze Pause nach jedem 5. Batch try { Thread.sleep(100); @@ -389,8 +388,8 @@ public class CargosoftMigrationRestService implements Serializable { } long duration = System.currentTimeMillis() - l; double objectsPerSecond = totalObjects / (duration / 1000.0); - log("├── Successfully " + deletions + " duplicated cargosoft objects deleted in " + - duration + "ms (" + String.format("%.1f", objectsPerSecond) + " objects/sec)", messageBuffer); + log("├── Successfully " + deletions + " duplicated cargosoft objects deleted in " + duration + "ms (" + + String.format("%.1f", objectsPerSecond) + " objects/sec)", messageBuffer); isRunning = false; return messageBuffer.toString(); @@ -398,7 +397,7 @@ public class CargosoftMigrationRestService implements Serializable { /** * Hilfsmethode - löscht ein cargoosoft object... - * + * * @param bpID * @param messageBuffer * @return diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/DataRestService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/DataRestService.java index 5194d52..450b3f3 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/DataRestService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/DataRestService.java @@ -1,27 +1,27 @@ -/* - * Imixs-Workflow - * - * Copyright (C) 2001-2020 Imixs Software Solutions GmbH, +/* + * Imixs-Workflow + * + * Copyright (C) 2001-2020 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Project: + * + * Project: * https://www.imixs.org * https://github.com/imixs/imixs-workflow - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - Project Management * Ralph Soika - Software Developer */ @@ -58,7 +58,7 @@ import jakarta.ws.rs.core.Response; /** * The api endpoint '/data' provides methods to read and post data via a access * token - * + * * @version 1.0 * @author rsoika * @@ -87,7 +87,7 @@ public class DataRestService { /** * Test method - * + * * @param workflowgroup * @param task * @return @@ -132,16 +132,14 @@ public class DataRestService { * this token to the service. *

    * The method tests the access. If it is not possible the method returns null. - * + * * @see DefaultAuthenicator * @return */ protected Client createClient(String serviceAPI, String sessionID) { // Create a Cookie object with a name and a value - Cookie cookie = new Cookie.Builder("JSESSIONID") - .value(sessionID) - .path("/") + Cookie cookie = new Cookie.Builder("JSESSIONID").value(sessionID).path("/") // .domain("domain.com") .build(); @@ -173,7 +171,7 @@ public class DataRestService { /** * Searches a report by name - * + * * @param name * @return * @throws UnsupportedEncodingException diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/datev/CargosoftExportController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/datev/CargosoftExportController.java index b2efbf2..910abb5 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/datev/CargosoftExportController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/datev/CargosoftExportController.java @@ -2,26 +2,26 @@ package com.alexanderlogistics.datev; /******************************************************************************* * Imixs Workflow Technology - * Copyright (C) 2003, 2008 Imixs Software Solutions GmbH, + * Copyright (C) 2003, 2008 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika - * + * *******************************************************************************/ import java.util.logging.Logger; @@ -39,43 +39,43 @@ import jakarta.inject.Named; * txtname=datev". *

    * The following config items are defined: - * + * * The following config items are defined: - * + * *

      * _model_version = model version for the SEPA export
      * _initial_task = inital task ID
      * 
    - * - * + * + * * @author rsoika - * + * */ @Named @RequestScoped public class CargosoftExportController extends SchedulerController { - public static final String CARGOSOFT_EXPORT_CONFIGURATION = "CARGOSOFT_EXPORT_CONFIGURATION"; + public static final String CARGOSOFT_EXPORT_CONFIGURATION = "CARGOSOFT_EXPORT_CONFIGURATION"; - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(CargosoftExportController.class.getName()); + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(CargosoftExportController.class.getName()); - @Override - public String getName() { - return CARGOSOFT_EXPORT_CONFIGURATION; - } + @Override + public String getName() { + return CARGOSOFT_EXPORT_CONFIGURATION; + } - /** - * Returns the sepa scheduler class name. This name depends on the _export_type. - * - * There are two export interfaces available - csv and XML - * - */ - @Override - public String getSchedulerClass() { - String schedulerClass = CargosoftExportScheduler.class.getName(); - logger.finest("...... scheduler: " + schedulerClass); - return schedulerClass; - } + /** + * Returns the sepa scheduler class name. This name depends on the _export_type. + * + * There are two export interfaces available - csv and XML + * + */ + @Override + public String getSchedulerClass() { + String schedulerClass = CargosoftExportScheduler.class.getName(); + logger.finest("...... scheduler: " + schedulerClass); + return schedulerClass; + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/datev/CargosoftExportScheduler.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/datev/CargosoftExportScheduler.java index 7857bb3..b42b8da 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/datev/CargosoftExportScheduler.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/datev/CargosoftExportScheduler.java @@ -1,22 +1,22 @@ /******************************************************************************* * Imixs Workflow Technology - * Copyright (C) 2001, 2008 Imixs Software Solutions GmbH, + * Copyright (C) 2001, 2008 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika *******************************************************************************/ @@ -61,10 +61,10 @@ import jakarta.ejb.EJB; * The class implements the interface * _org.imixs.workflow.engine.scheduler.Scheduler_ and can be used in * combination with the Imxis-Workflow Scheduler Service. - * + * * @see SchedulerService * @author rsoika - * + * */ public class CargosoftExportScheduler implements Scheduler { @@ -94,9 +94,9 @@ public class CargosoftExportScheduler implements Scheduler { /** * This is the method which processes the timeout event depending on the running * timer settings. - * - * - * + * + * + * * @param timer * @throws QueryException */ @@ -198,7 +198,7 @@ public class CargosoftExportScheduler implements Scheduler { /** * Diese Hilfsmehtode überträgt eine Datei an einen FTP Server. Die * Verbindugnsdaten stehen im Configuraiton Workitem. - * + * * @param fileData object * @throws PluginException */ diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/ExcelExportController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/ExcelExportController.java index d046104..7d7c2d5 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/ExcelExportController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/ExcelExportController.java @@ -44,9 +44,9 @@ import jakarta.inject.Named; /** * Der ExcelExportController exportiert rechnungen anhand von Filterangaben * (excel_export_rechnungsausgang.xhmlt) nach Excel. - * + * * Die Ausgabe ist auf maximal 3000 Zeilen eingeschränkt - * + * * @author rsoika * */ @@ -54,291 +54,290 @@ import jakarta.inject.Named; @ConversationScoped public class ExcelExportController implements Serializable { - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(ExcelExportController.class.getName()); + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(ExcelExportController.class.getName()); - public static final String ERROR_CONFIG = "CONFIG_ERROR"; - final String TYPE_TEXTBLOCK = "textblock"; + public static final String ERROR_CONFIG = "CONFIG_ERROR"; + final String TYPE_TEXTBLOCK = "textblock"; - public static final int MAX_ROWS = 3000; - @Inject - protected DocumentService documentService; + public static final int MAX_ROWS = 3000; + @Inject + protected DocumentService documentService; - @Inject - WorkflowController workflowController; + @Inject + WorkflowController workflowController; - @Inject - DocumentController documentController; + @Inject + DocumentController documentController; - @Inject - SnapshotService snapshotService; + @Inject + SnapshotService snapshotService; - private ItemCollection filter; - private CustomFormItem dbtrItem; + private ItemCollection filter; + private CustomFormItem dbtrItem; - public ExcelExportController() { - } + public ExcelExportController() { + } - @PostConstruct - public void init() { - reset(); - } + @PostConstruct + public void init() { + reset(); + } - public ItemCollection getFilter() { - return filter; - } + public ItemCollection getFilter() { + return filter; + } - public void setFilter(ItemCollection filter) { - this.filter = filter; - } + public void setFilter(ItemCollection filter) { + this.filter = filter; + } - /** - * Fuert verschiedene Queries aus um eine Analyse der Sachprufung durchzuführen. - * - * @throws PluginException - */ - public String export() throws PluginException { + /** + * Fuert verschiedene Queries aus um eine Analyse der Sachprufung durchzuführen. + * + * @throws PluginException + */ + public String export() throws PluginException { - logger.info("start export...."); - Date start = filter.getItemValueDate("start"); - Date stop = filter.getItemValueDate("stop"); - logger.info("...daterange=" + start + " - " + stop); + logger.info("start export...."); + Date start = filter.getItemValueDate("start"); + Date stop = filter.getItemValueDate("stop"); + logger.info("...daterange=" + start + " - " + stop); - // serach date range? - String sDateFrom = "191401070000"; // because * did not work here - String sDateTo = "211401070000"; - SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMddHHmm"); + // serach date range? + String sDateFrom = "191401070000"; // because * did not work here + String sDateTo = "211401070000"; + SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMddHHmm"); - if (start != null) { - Calendar cal = Calendar.getInstance(); - cal.setTime(start); - sDateFrom = dateformat.format(cal.getTime()); - } - if (stop != null) { - Calendar cal = Calendar.getInstance(); - cal.setTime(stop); - cal.add(Calendar.DATE, 1); - sDateTo = dateformat.format(cal.getTime()); - } + if (start != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(start); + sDateFrom = dateformat.format(cal.getTime()); + } + if (stop != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(stop); + cal.add(Calendar.DATE, 1); + sDateTo = dateformat.format(cal.getTime()); + } - // query zeit - String query = "(type:workitem OR type:workitemarchive) AND ($workflowgroup:Rechnungsausgang) "; - if (start != null && stop != null) { - query += "AND (invoice.date:[" + sDateFrom + " TO " + sDateTo + "]) "; - } else { - throw new PluginException("ERROR", "Bitte geben Sie einen Zeitraum an", null); - } - logger.info(query); + // query zeit + String query = "(type:workitem OR type:workitemarchive) AND ($workflowgroup:Rechnungsausgang) "; + if (start != null && stop != null) { + query += "AND (invoice.date:[" + sDateFrom + " TO " + sDateTo + "]) "; + } else { + throw new PluginException("ERROR", "Bitte geben Sie einen Zeitraum an", null); + } + logger.info(query); - // query Sachprüfung + // query Sachprüfung - if (!filter.getItemValueString("space.ref").isEmpty() - && !"-".equals(filter.getItemValueString("space.ref"))) { - query += " AND ($uniqueidref:" + filter.getItemValueString("space.ref") + ")"; - } - if (workflowController.getWorkitem() != null - && !workflowController.getWorkitem().getItemValueString("dbtr.number").isEmpty()) { - query += " AND (dbtr.number:" + workflowController.getWorkitem().getItemValueString("dbtr.number") + ")"; - } - if (!filter.getItemValueString("invoice.text").isEmpty()) { - query += " AND (invoice.positions:" + filter.getItemValueString("invoice.text") + ")"; - } + if (!filter.getItemValueString("space.ref").isEmpty() && !"-".equals(filter.getItemValueString("space.ref"))) { + query += " AND ($uniqueidref:" + filter.getItemValueString("space.ref") + ")"; + } + if (workflowController.getWorkitem() != null + && !workflowController.getWorkitem().getItemValueString("dbtr.number").isEmpty()) { + query += " AND (dbtr.number:" + workflowController.getWorkitem().getItemValueString("dbtr.number") + ")"; + } + if (!filter.getItemValueString("invoice.text").isEmpty()) { + query += " AND (invoice.positions:" + filter.getItemValueString("invoice.text") + ")"; + } - // load the text block - String template = "ausgangsrechnungen-export_template.xlsx"; - FileData fileData = loadTextBlockFileData("Excel-Export Template", template); + // load the text block + String template = "ausgangsrechnungen-export_template.xlsx"; + FileData fileData = loadTextBlockFileData("Excel-Export Template", template); - // do we found the document? - if (fileData == null) { - throw new PluginException(InkassoExportAdapter.class.getSimpleName(), ERROR_CONFIG, - "Missing Excel Export Template - template=" + template + " not found!"); - } - String targetName = "ausgangsrechnungen-export_" + dateformat.format(new Date()) + ".xlsx"; - try { - List invoices = documentService.find(query, MAX_ROWS, 0, "invoice.date", true); - if (invoices.size() > 0) { - insertInvoiceRows(invoices, fileData); - } - fileData.setName(targetName); + // do we found the document? + if (fileData == null) { + throw new PluginException(InkassoExportAdapter.class.getSimpleName(), ERROR_CONFIG, + "Missing Excel Export Template - template=" + template + " not found!"); + } + String targetName = "ausgangsrechnungen-export_" + dateformat.format(new Date()) + ".xlsx"; + try { + List invoices = documentService.find(query, MAX_ROWS, 0, "invoice.date", true); + if (invoices.size() > 0) { + insertInvoiceRows(invoices, fileData); + } + fileData.setName(targetName); - // See: - // https://stackoverflow.com/questions/9391838/how-to-provide-a-file-download-from-a-jsf-backing-bean - download(fileData); - } catch (IOException | QueryException e) { - throw new PluginException(InkassoExportAdapter.class.getSimpleName(), ERROR_CONFIG, - "Failed to generate Excel Export: " + e.getMessage()); - } + // See: + // https://stackoverflow.com/questions/9391838/how-to-provide-a-file-download-from-a-jsf-backing-bean + download(fileData); + } catch (IOException | QueryException e) { + throw new PluginException(InkassoExportAdapter.class.getSimpleName(), ERROR_CONFIG, + "Failed to generate Excel Export: " + e.getMessage()); + } - logger.info("Query=" + query); - return "/pages/admin/excel_export_rechnungsausgang.jsf?faces-redirect=true"; - } + logger.info("Query=" + query); + return "/pages/admin/excel_export_rechnungsausgang.jsf?faces-redirect=true"; + } - /** - * Helper method to initialize a download - * - * @throws IOException - */ - public void download(FileData fileData) throws IOException { + /** + * Helper method to initialize a download + * + * @throws IOException + */ + public void download(FileData fileData) throws IOException { - FacesContext facesContext = FacesContext.getCurrentInstance(); - ExternalContext externalContext = facesContext.getExternalContext(); + FacesContext facesContext = FacesContext.getCurrentInstance(); + ExternalContext externalContext = facesContext.getExternalContext(); - externalContext.responseReset(); - externalContext.setResponseContentType("application/vnd.ms-excel"); - externalContext.setResponseContentLength(fileData.getContent().length); - externalContext.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileData.getName() + "\""); + externalContext.responseReset(); + externalContext.setResponseContentType("application/vnd.ms-excel"); + externalContext.setResponseContentLength(fileData.getContent().length); + externalContext.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileData.getName() + "\""); - OutputStream output = externalContext.getResponseOutputStream(); + OutputStream output = externalContext.getResponseOutputStream(); - // Now you can write the InputStream of the file to the above OutputStream the - // usual way. - output.write(fileData.getContent()); + // Now you can write the InputStream of the file to the above OutputStream the + // usual way. + output.write(fileData.getContent()); - facesContext.responseComplete(); // Important! Otherwise Faces will attempt to render the response which - // obviously will fail since it's already written with a file and closed. - } + facesContext.responseComplete(); // Important! Otherwise Faces will attempt to render the response which + // obviously will fail since it's already written with a file and closed. + } - /** - * This method returns a text-block ItemCollection for a specified name. - * - * @param name in attribute txtname - * - * - */ - private FileData loadTextBlockFileData(String name, String fileName) { - ItemCollection textBlockItemCollection = null; + /** + * This method returns a text-block ItemCollection for a specified name. + * + * @param name in attribute txtname + * + * + */ + private FileData loadTextBlockFileData(String name, String fileName) { + ItemCollection textBlockItemCollection = null; - // load text-block by name.... - String sQuery = "(type:\"" + TYPE_TEXTBLOCK + "\" AND txtname:\"" + name + "\")"; - Collection col; - try { - // find the textblock... - col = documentService.find(sQuery, 1, 0); - if (col.size() > 0) { - textBlockItemCollection = col.iterator().next(); - // fetch the fileData... - return snapshotService.getWorkItemFile(textBlockItemCollection.getUniqueID(), fileName); + // load text-block by name.... + String sQuery = "(type:\"" + TYPE_TEXTBLOCK + "\" AND txtname:\"" + name + "\")"; + Collection col; + try { + // find the textblock... + col = documentService.find(sQuery, 1, 0); + if (col.size() > 0) { + textBlockItemCollection = col.iterator().next(); + // fetch the fileData... + return snapshotService.getWorkItemFile(textBlockItemCollection.getUniqueID(), fileName); - } else { - logger.warning("Missing text-block : '" + name + "'"); - } - } catch (QueryException e) { - logger.warning("getTextBlock - invalid query: " + e.getMessage()); - } + } else { + logger.warning("Missing text-block : '" + name + "'"); + } + } catch (QueryException e) { + logger.warning("getTextBlock - invalid query: " + e.getMessage()); + } - return null; - } + return null; + } - /** - * This method reset the search and input state. - */ - public void reset() { - filter = new ItemCollection(); + /** + * This method reset the search and input state. + */ + public void reset() { + filter = new ItemCollection(); - // compute start stop based on current month - YearMonth startYearMonth = YearMonth.now(); - java.time.LocalDate startOfMonthDate = startYearMonth.atDay(1); - java.time.LocalDate endOfMonthDate = startYearMonth.atEndOfMonth(); - filter.setItemValue("start", - java.util.Date.from(startOfMonthDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); - filter.setItemValue("stop", - java.util.Date.from(endOfMonthDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); - workflowController.setWorkitem(new ItemCollection()); + // compute start stop based on current month + YearMonth startYearMonth = YearMonth.now(); + java.time.LocalDate startOfMonthDate = startYearMonth.atDay(1); + java.time.LocalDate endOfMonthDate = startYearMonth.atEndOfMonth(); + filter.setItemValue("start", + java.util.Date.from(startOfMonthDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + filter.setItemValue("stop", + java.util.Date.from(endOfMonthDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())); + workflowController.setWorkitem(new ItemCollection()); - // debtr item - dbtrItem = new CustomFormItem("dbtr.number", "text", "", false, false, false, "", null, false, 0); + // debtr item + dbtrItem = new CustomFormItem("dbtr.number", "text", "", false, false, false, "", null, false, 0); - } + } - public CustomFormItem getItem() { - // dbtrItem.setName("dbtr.number"); - return dbtrItem; - } + public CustomFormItem getItem() { + // dbtrItem.setName("dbtr.number"); + return dbtrItem; + } - /** - * This helper method inserts a row for each invoice - * - * @throws PluginException - */ - private void insertInvoiceRows(List invoices, FileData fileData) throws PluginException { - // load XSSFWorkbook - try (InputStream imputStream = new ByteArrayInputStream(fileData.getContent())) { - XSSFWorkbook doc = new XSSFWorkbook(imputStream); - // NOTE: we only take the first sheet ! - XSSFSheet sheet = doc.getSheetAt(0); + /** + * This helper method inserts a row for each invoice + * + * @throws PluginException + */ + private void insertInvoiceRows(List invoices, FileData fileData) throws PluginException { + // load XSSFWorkbook + try (InputStream imputStream = new ByteArrayInputStream(fileData.getContent())) { + XSSFWorkbook doc = new XSSFWorkbook(imputStream); + // NOTE: we only take the first sheet ! + XSSFSheet sheet = doc.getSheetAt(0); - XSSFCell standCell = getCellByRef(doc, sheet, "F6"); - standCell.setCellValue(new Date()); + XSSFCell standCell = getCellByRef(doc, sheet, "F6"); + standCell.setCellValue(new Date()); - CellReference cr = new CellReference("A11"); - XSSFRow referenceRow = sheet.getRow(cr.getRow()); - int referenceRowPos = referenceRow.getRowNum() + 1; - int rowPos = referenceRowPos; - // int lastRow = sheet.getLastRowNum(); - int lastRow = 999; - logger.finest("Last rownum=" + lastRow); - sheet.shiftRows(rowPos, lastRow, invoices.size(), true, true); + CellReference cr = new CellReference("A11"); + XSSFRow referenceRow = sheet.getRow(cr.getRow()); + int referenceRowPos = referenceRow.getRowNum() + 1; + int rowPos = referenceRowPos; + // int lastRow = sheet.getLastRowNum(); + int lastRow = 999; + logger.finest("Last rownum=" + lastRow); + sheet.shiftRows(rowPos, lastRow, invoices.size(), true, true); - for (ItemCollection invoice : invoices) { - logger.finest("......copy row..."); + for (ItemCollection invoice : invoices) { + logger.finest("......copy row..."); - // now create a new line.. - XSSFRow row = sheet.createRow(rowPos); - row.copyRowFrom(referenceRow, new CellCopyPolicy()); - // insert values - row.getCell(0).setCellValue(invoice.getItemValueString("invoice.number")); - row.getCell(1).setCellValue(invoice.getItemValueString("dbtr.number")); - row.getCell(2).setCellValue(invoice.getItemValueString("space.name")); - row.getCell(3).setCellValue(invoice.getItemValueString("invoice.text")); - row.getCell(4).setCellValue(invoice.getItemValueDate("invoice.date")); - row.getCell(5).setCellValue(invoice.getItemValueDate("invoice.duedate")); - row.getCell(6).setCellValue(invoice.getItemValueDouble("invoice.total")); + // now create a new line.. + XSSFRow row = sheet.createRow(rowPos); + row.copyRowFrom(referenceRow, new CellCopyPolicy()); + // insert values + row.getCell(0).setCellValue(invoice.getItemValueString("invoice.number")); + row.getCell(1).setCellValue(invoice.getItemValueString("dbtr.number")); + row.getCell(2).setCellValue(invoice.getItemValueString("space.name")); + row.getCell(3).setCellValue(invoice.getItemValueString("invoice.text")); + row.getCell(4).setCellValue(invoice.getItemValueDate("invoice.date")); + row.getCell(5).setCellValue(invoice.getItemValueDate("invoice.duedate")); + row.getCell(6).setCellValue(invoice.getItemValueDouble("invoice.total")); - rowPos++; - } - // delete reference row - sheet.shiftRows(referenceRowPos, lastRow + invoices.size(), -1, true, true); + rowPos++; + } + // delete reference row + sheet.shiftRows(referenceRowPos, lastRow + invoices.size(), -1, true, true); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - // write back the file - doc.write(byteArrayOutputStream); - doc.close(); - byte[] newContent = byteArrayOutputStream.toByteArray(); - fileData.setContent(newContent); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + // write back the file + doc.write(byteArrayOutputStream); + doc.close(); + byte[] newContent = byteArrayOutputStream.toByteArray(); + fileData.setContent(newContent); - } catch (IOException e) { - throw new PluginException(ZahlungsavisExportAdapter.class.getSimpleName(), ERROR_CONFIG, - "failed to update excel export: " + e.getMessage()); - } - } + } catch (IOException e) { + throw new PluginException(ZahlungsavisExportAdapter.class.getSimpleName(), ERROR_CONFIG, + "failed to update excel export: " + e.getMessage()); + } + } - /** - * Returns a Cell by name or an optional absolute cell postion - *

    - * Examples for refs are 'A1', 'AB3', 'MyCell' where 'MyCell' is a named cell. - *

    - * - */ - public static XSSFCell getCellByRef(XSSFWorkbook doc, XSSFSheet sheet, String cellReference) { - XSSFCell cell = null; + /** + * Returns a Cell by name or an optional absolute cell postion + *

    + * Examples for refs are 'A1', 'AB3', 'MyCell' where 'MyCell' is a named cell. + *

    + * + */ + public static XSSFCell getCellByRef(XSSFWorkbook doc, XSSFSheet sheet, String cellReference) { + XSSFCell cell = null; - // first we test if the cellName is a named cell - Name aNamedCell = doc.getName(cellReference); - if (aNamedCell != null) { - // yes its a named cell so we need to get the referrer Formula - logger.finest("...resolving named cell = " + aNamedCell.getNameName()); - cellReference = aNamedCell.getRefersToFormula(); - // now we can find the cell by its ref - } + // first we test if the cellName is a named cell + Name aNamedCell = doc.getName(cellReference); + if (aNamedCell != null) { + // yes its a named cell so we need to get the referrer Formula + logger.finest("...resolving named cell = " + aNamedCell.getNameName()); + cellReference = aNamedCell.getRefersToFormula(); + // now we can find the cell by its ref + } - CellReference cr = new CellReference(cellReference); - XSSFRow row = sheet.getRow(cr.getRow()); - if (row == null) { - logger.severe("Unable to resolve cell ref '" + cellReference + "'!"); - return null; - } - cell = row.getCell(cr.getCol()); - return cell; - } + CellReference cr = new CellReference(cellReference); + XSSFRow row = sheet.getRow(cr.getRow()); + if (row == null) { + logger.severe("Unable to resolve cell ref '" + cellReference + "'!"); + return null; + } + cell = row.getCell(cr.getCol()); + return cell; + } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoAppendAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoAppendAdapter.java index 3031b06..d2e9300 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoAppendAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoAppendAdapter.java @@ -17,13 +17,13 @@ import jakarta.inject.Inject; /** * Der InkassoAppendAdapter verknüpft eine Rechnung mit einem Inkasso Workflow - * zum aktuellen Debitor. Existiert aktuell kein Inkasso für diesen - * Debitor erzeugt der Adapter automatisch eine neue Prozessinstanz. + * zum aktuellen Debitor. Existiert aktuell kein Inkasso für diesen Debitor + * erzeugt der Adapter automatisch eine neue Prozessinstanz. *

    * Wurde die Rechnung bereits einem Inkasso zugeordnet passiert nichts. - * - * - * + * + * + * * @version 1.0 * @author rsoika */ @@ -41,9 +41,9 @@ public class InkassoAppendAdapter implements SignalAdapter { WorkflowService workflowService; /** - * This method finds or create the Inkasso and adds a reference - * ($workitemref) to the current invoice. - * + * This method finds or create the Inkasso and adds a reference ($workitemref) + * to the current invoice. + * * @throws PluginException */ @Override @@ -59,9 +59,9 @@ public class InkassoAppendAdapter implements SignalAdapter { /** * Diese method hängt eine referenz der aktuellen Rechnung an den Inkasso - * Workflow an. Gibt es noch keinen, wird einer erzeugt. - * Die method liefert das Inkasso Workitem zurück. - * + * Workflow an. Gibt es noch keinen, wird einer erzeugt. Die method liefert das + * Inkasso Workitem zurück. + * * @param document * @throws PluginException */ @@ -103,7 +103,7 @@ public class InkassoAppendAdapter implements SignalAdapter { /** * Prüft alle offenen Inkasso Workflows und gibt den neuesten zur angegebenen * dbtrNumber zurück, oder null falls es keinen Offenen gibt. - * + * * @param dbtrNumber * @return * @throws QueryException diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoController.java index 20a7533..a98aa84 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoController.java @@ -15,12 +15,12 @@ import jakarta.inject.Inject; import jakarta.inject.Named; /** - * Der InkassoController dient dazu eine bereits zugewiesene Rechnung - * wieder aus dem Inkasso zu entfernen. + * Der InkassoController dient dazu eine bereits zugewiesene Rechnung wieder aus + * dem Inkasso zu entfernen. *

    * Zusätzlich bietet er die Summenberechnung an, bei der die Gutschriften * abgezogen werden. - * + * * @author rsoika * */ @@ -43,7 +43,7 @@ public class InkassoController implements Serializable { * result is rounded to 2 digits. *

    * Gutschriften werden abgezogen - * + * * @param refids - list of workitem uniqueIds * @param item - name of the item to summarize * @return sum rounded to 2 digits diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoExportAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoExportAdapter.java index cdc81a3..4016091 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoExportAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/InkassoExportAdapter.java @@ -27,7 +27,7 @@ import jakarta.inject.Inject; /** * Der ZahlungsavisAdapter importiert eine Excel Datei aus einem Textblock - * + * *

      * {@code
             textblock-ref
    @@ -38,7 +38,7 @@ import jakarta.inject.Inject;
      * 

    * Der Adapter erweitert den POIAdapter somit können felder aktualisiert werden * (siehe POIFineReplaceAdapter). - * + * *

    * Der Adapter kopiert die Rechnungsdaten in neue Zeilen, welche ab Zeilnenummer * 16 eingefügt werden. @@ -46,7 +46,7 @@ import jakarta.inject.Inject; * Dabei werden Gutschriften in spalte E und Rechnungen in Spalte F eingetrgen. *

    * Abschliessend wird die Zelle 'TOTAL' noch aktualisiert. - * + * * @version 1.0 * @author rsoika */ @@ -69,7 +69,7 @@ public class InkassoExportAdapter extends POIFindReplaceAdapter { /** * This method finds or create the Zahlungsavis and adds a reference * ($workitemref) to the current invoice. - * + * * @throws PluginException */ @SuppressWarnings("unchecked") @@ -121,7 +121,7 @@ public class InkassoExportAdapter extends POIFindReplaceAdapter { *

    * The named cell 'TOTAL' should contain the summary formula. It will be * evaluated at the end. - * + * * @throws PluginException */ @SuppressWarnings("unchecked") @@ -186,7 +186,7 @@ public class InkassoExportAdapter extends POIFindReplaceAdapter { /** * This method loads a text-block for a specified ref and appends the named * fileData object of this document. - * + * * @param document * @throws PluginException */ @@ -216,9 +216,9 @@ public class InkassoExportAdapter extends POIFindReplaceAdapter { /** * This method returns a text-block ItemCollection for a specified name. - * + * * @param name in attribute txtname - * + * * */ public FileData loadTextBlockFileData(String name, String fileName) { diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufExecuteAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufExecuteAdapter.java index e5898ca..ed5a2c2 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufExecuteAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufExecuteAdapter.java @@ -29,10 +29,10 @@ import jakarta.inject.Inject; * processed invoices. This item is used in the Workflow Model to construct a * HTML Table wen sending the mail. *

    - * The Adapter also splits the item 'dbtr.mail' into a multi value field if - * the value contains email addresses separated with new lines. - * - * + * The Adapter also splits the item 'dbtr.mail' into a multi value field if the + * value contains email addresses separated with new lines. + * + * * @version 1.0 * @author rsoika */ @@ -52,7 +52,7 @@ public class MahnlaufExecuteAdapter implements SignalAdapter { /** * This method finds all invices ($workitemref) and tirgges event 300 - * + * * @throws PluginException */ @SuppressWarnings("unchecked") @@ -125,24 +125,24 @@ public class MahnlaufExecuteAdapter implements SignalAdapter { } switch (invoice.getTaskID()) { - case 5200: - case 5201: - if (dunningLevel < 1) - dunningLevel = 1; - break; - case 5210: - case 5211: - if (dunningLevel < 2) - dunningLevel = 2; - break; - case 5220: - if (dunningLevel < 3) - dunningLevel = 3; - break; - default: - if (dunningLevel < 1) - dunningLevel = 1; - break; + case 5200: + case 5201: + if (dunningLevel < 1) + dunningLevel = 1; + break; + case 5210: + case 5211: + if (dunningLevel < 2) + dunningLevel = 2; + break; + case 5220: + if (dunningLevel < 3) + dunningLevel = 3; + break; + default: + if (dunningLevel < 1) + dunningLevel = 1; + break; } // create a workitem stub for the mail message @@ -179,18 +179,18 @@ public class MahnlaufExecuteAdapter implements SignalAdapter { // compute subject switch (dunningLevel) { - case 2: - mahnLaufWorkitem.setItemValue("mail.subject_de", "2. MAHNUNG"); - mahnLaufWorkitem.setItemValue("mail.subject_en", "2. REMINDER"); - break; - case 3: - mahnLaufWorkitem.setItemValue("mail.subject_de", "LETZTE AUSSERGERICHTLICHE MAHNUNG"); - mahnLaufWorkitem.setItemValue("mail.subject_en", "LAST REMINDER"); - break; - default: - mahnLaufWorkitem.setItemValue("mail.subject_de", "1. MAHNUNG"); - mahnLaufWorkitem.setItemValue("mail.subject_en", "1. REMINDER"); - break; + case 2: + mahnLaufWorkitem.setItemValue("mail.subject_de", "2. MAHNUNG"); + mahnLaufWorkitem.setItemValue("mail.subject_en", "2. REMINDER"); + break; + case 3: + mahnLaufWorkitem.setItemValue("mail.subject_de", "LETZTE AUSSERGERICHTLICHE MAHNUNG"); + mahnLaufWorkitem.setItemValue("mail.subject_en", "LAST REMINDER"); + break; + default: + mahnLaufWorkitem.setItemValue("mail.subject_de", "1. MAHNUNG"); + mahnLaufWorkitem.setItemValue("mail.subject_en", "1. REMINDER"); + break; } // Verify the dbtr.mail item (split into multi value) @@ -198,8 +198,7 @@ public class MahnlaufExecuteAdapter implements SignalAdapter { for (String mail : mailListe) { logger.fine(" test mail address:'" + mail + "'"); if (!InvoiceUtil.validateEmail(mail)) { - throw new PluginException(MahnlaufExecuteAdapter.class.getName(), - "INVALID_EMAIL", + throw new PluginException(MahnlaufExecuteAdapter.class.getName(), "INVALID_EMAIL", "Die Daten konnten nicht aktualisiert werden! Bitte geben Sie eine gültige E-Mail Adresse ein!"); } } @@ -208,7 +207,7 @@ public class MahnlaufExecuteAdapter implements SignalAdapter { /** * Returns true if the reminder date is today or in the past - * + * * @param uniqueid * @return */ diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufRefAddAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufRefAddAdapter.java index e26bba8..c95b3a4 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufRefAddAdapter.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufRefAddAdapter.java @@ -30,152 +30,151 @@ import jakarta.inject.Inject; *

    * If the invoice has already been linked to a Mahnlauf nothing happens. *

    - * + * * @version 1.0 * @author rsoika */ public class MahnlaufRefAddAdapter implements SignalAdapter { - private static Logger logger = Logger.getLogger(MahnlaufRefAddAdapter.class.getName()); + private static Logger logger = Logger.getLogger(MahnlaufRefAddAdapter.class.getName()); - @EJB - ModelService modelService; + @EJB + ModelService modelService; - @EJB - WorkflowService workflowService = null; + @EJB + WorkflowService workflowService = null; - @Inject - SepaWorkflowService sepaWorkflowService; + @Inject + SepaWorkflowService sepaWorkflowService; - @Inject - BusinessPartnerService businessPartnerService; + @Inject + BusinessPartnerService businessPartnerService; - /** - * This method finds or create the Mahnlauf and adds a reference ($workitemref) - * to the current invoice. - * - * @throws PluginException - */ - @SuppressWarnings("unchecked") - @Override - public ItemCollection execute(ItemCollection invoice, ItemCollection event) - throws AdapterException, PluginException { + /** + * This method finds or create the Mahnlauf and adds a reference ($workitemref) + * to the current invoice. + * + * @throws PluginException + */ + @SuppressWarnings("unchecked") + @Override + public ItemCollection execute(ItemCollection invoice, ItemCollection event) + throws AdapterException, PluginException { - if (invoice.getTaskID() < MahnlaufService.TASK_MAHNLAUF_START - || invoice.getTaskID() > MahnlaufService.TASK_MAHNLAUF_END) { - throw new PluginException(MahnlaufRefAddAdapter.class.getName(), - MahnlaufService.ERROR_CONFIG, - "Der Mahnlauf kann nur für Rechnungen erstellt werden die bereits die Fälligkeit erreicht haben."); - } + if (invoice.getTaskID() < MahnlaufService.TASK_MAHNLAUF_START + || invoice.getTaskID() > MahnlaufService.TASK_MAHNLAUF_END) { + throw new PluginException(MahnlaufRefAddAdapter.class.getName(), MahnlaufService.ERROR_CONFIG, + "Der Mahnlauf kann nur für Rechnungen erstellt werden die bereits die Fälligkeit erreicht haben."); + } - String dbtrNumber = invoice.getItemValueString(MahnlaufService.ITEM_DBTR_NUMBER); - ItemCollection mahnLaufWorkitem; - try { - mahnLaufWorkitem = findMahnlauf(dbtrNumber); + String dbtrNumber = invoice.getItemValueString(MahnlaufService.ITEM_DBTR_NUMBER); + ItemCollection mahnLaufWorkitem; + try { + mahnLaufWorkitem = findMahnlauf(dbtrNumber); - if (mahnLaufWorkitem == null) { - // create one... - mahnLaufWorkitem = createNewMahnlauf(dbtrNumber, invoice, event); - } + if (mahnLaufWorkitem == null) { + // create one... + mahnLaufWorkitem = createNewMahnlauf(dbtrNumber, invoice, event); + } - // add Invoice to Mahnlauf - List refList = mahnLaufWorkitem.getItemValue("$workitemref"); - if (!refList.contains(invoice.getUniqueID())) { - mahnLaufWorkitem.appendItemValueUnique("$workitemref", invoice.getUniqueID()); - } + // add Invoice to Mahnlauf + List refList = mahnLaufWorkitem.getItemValue("$workitemref"); + if (!refList.contains(invoice.getUniqueID())) { + mahnLaufWorkitem.appendItemValueUnique("$workitemref", invoice.getUniqueID()); + } - // set event 100 and process - ItemCollection mahnlaufConfig = workflowService.evalWorkflowResult(event, "mahnlauf", invoice, true); - int eventID = mahnlaufConfig.getItemValueInteger("event"); + // set event 100 and process + ItemCollection mahnlaufConfig = workflowService.evalWorkflowResult(event, "mahnlauf", invoice, true); + int eventID = mahnlaufConfig.getItemValueInteger("event"); - mahnLaufWorkitem.event(eventID); - workflowService.processWorkItem(mahnLaufWorkitem); + mahnLaufWorkitem.event(eventID); + workflowService.processWorkItem(mahnLaufWorkitem); - } catch (QueryException | ModelException e) { - throw new PluginException(MahnlaufRefAddAdapter.class.getName(), - MahnlaufService.ERROR_CONFIG, "Unable to create Mahnlauf", e); - } + } catch (QueryException | ModelException e) { + throw new PluginException(MahnlaufRefAddAdapter.class.getName(), MahnlaufService.ERROR_CONFIG, + "Unable to create Mahnlauf", e); + } - // set mahnlauf ref - invoice.setItemValue("dunning.ref", mahnLaufWorkitem.getUniqueID()); - return invoice; - } + // set mahnlauf ref + invoice.setItemValue("dunning.ref", mahnLaufWorkitem.getUniqueID()); + return invoice; + } - /** - * Helper method finds an open mahnlauf for a debtr.numbre (name) - * - * @param key - * @return - * @throws QueryException - */ - public ItemCollection findMahnlauf(String key) throws QueryException { - String query = "(type:workitem) AND ($modelversion:mahnlauf*) AND (name:\"" + key + "\")"; - List resultList = workflowService.getDocumentService().find(query, 1, 0, "$modified", true); + /** + * Helper method finds an open mahnlauf for a debtr.numbre (name) + * + * @param key + * @return + * @throws QueryException + */ + public ItemCollection findMahnlauf(String key) throws QueryException { + String query = "(type:workitem) AND ($modelversion:mahnlauf*) AND (name:\"" + key + "\")"; + List resultList = workflowService.getDocumentService().find(query, 1, 0, "$modified", true); - if (resultList.size() > 0) { - return resultList.get(0); - } - // no mahnaluffound - return null; - } + if (resultList.size() > 0) { + return resultList.get(0); + } + // no mahnaluffound + return null; + } - /** - * Helper method to create a new Mahnlauf workitem - * - * @param key - * @param invoice - * @return - * @throws ModelException - * @throws PluginException - */ - @SuppressWarnings("unused") - public ItemCollection createNewMahnlauf(String key, ItemCollection invoice, ItemCollection event) - throws ModelException, PluginException { - String modelVersion = null; - int taskID = -1; - int eventID = -1; - // test if the event provides a sepa export configuration - ItemCollection mahnlaufConfig = workflowService.evalWorkflowResult(event, "mahnlauf", invoice, true); - if (mahnlaufConfig != null && mahnlaufConfig.hasItem("modelversion") && mahnlaufConfig.hasItem("task")) { - logger.fine("read model information from event"); - modelVersion = mahnlaufConfig.getItemValueString("modelVersion"); - taskID = mahnlaufConfig.getItemValueInteger("task"); - eventID = mahnlaufConfig.getItemValueInteger("event"); - } + /** + * Helper method to create a new Mahnlauf workitem + * + * @param key + * @param invoice + * @return + * @throws ModelException + * @throws PluginException + */ + @SuppressWarnings("unused") + public ItemCollection createNewMahnlauf(String key, ItemCollection invoice, ItemCollection event) + throws ModelException, PluginException { + String modelVersion = null; + int taskID = -1; + int eventID = -1; + // test if the event provides a sepa export configuration + ItemCollection mahnlaufConfig = workflowService.evalWorkflowResult(event, "mahnlauf", invoice, true); + if (mahnlaufConfig != null && mahnlaufConfig.hasItem("modelversion") && mahnlaufConfig.hasItem("task")) { + logger.fine("read model information from event"); + modelVersion = mahnlaufConfig.getItemValueString("modelVersion"); + taskID = mahnlaufConfig.getItemValueInteger("task"); + eventID = mahnlaufConfig.getItemValueInteger("event"); + } - // build the sepa export workitem.... - ItemCollection mahnlaufWorkitem = new ItemCollection().model(modelVersion).task(taskID); - mahnlaufWorkitem.replaceItemValue("name", key); - mahnlaufWorkitem.replaceItemValue(WorkflowKernel.CREATED, new Date()); - mahnlaufWorkitem.replaceItemValue(WorkflowKernel.MODIFIED, new Date()); - // set unqiueid - mahnlaufWorkitem.setItemValue(WorkflowKernel.UNIQUEID, WorkflowKernel.generateUniqueID()); + // build the sepa export workitem.... + ItemCollection mahnlaufWorkitem = new ItemCollection().model(modelVersion).task(taskID); + mahnlaufWorkitem.replaceItemValue("name", key); + mahnlaufWorkitem.replaceItemValue(WorkflowKernel.CREATED, new Date()); + mahnlaufWorkitem.replaceItemValue(WorkflowKernel.MODIFIED, new Date()); + // set unqiueid + mahnlaufWorkitem.setItemValue(WorkflowKernel.UNIQUEID, WorkflowKernel.generateUniqueID()); - // copy dbtr_iban - mahnlaufWorkitem.setItemValue(MahnlaufService.ITEM_DBTR_NAME, - invoice.getItemValue(MahnlaufService.ITEM_DBTR_NAME)); + // copy dbtr_iban + mahnlaufWorkitem.setItemValue(MahnlaufService.ITEM_DBTR_NAME, + invoice.getItemValue(MahnlaufService.ITEM_DBTR_NAME)); - // lookup email.... - String dbtrNumber = invoice.getItemValueString("dbtr.number"); - ItemCollection businessPartner = businessPartnerService - .getBusinessPartnerByID(InvoiceUtil.buildBPID(dbtrNumber)); - if (businessPartner != null) { - mahnlaufWorkitem.setItemValue(MahnlaufService.ITEM_DBTR_EMAIL, businessPartner.getItemValue("dbtr.mail"));// dbtr.mail - } + // lookup email.... + String dbtrNumber = invoice.getItemValueString("dbtr.number"); + ItemCollection businessPartner = businessPartnerService + .getBusinessPartnerByID(InvoiceUtil.buildBPID(dbtrNumber)); + if (businessPartner != null) { + mahnlaufWorkitem.setItemValue(MahnlaufService.ITEM_DBTR_EMAIL, businessPartner.getItemValue("dbtr.mail"));// dbtr.mail + } - mahnlaufWorkitem.setItemValue(MahnlaufService.ITEM_DBTR_NUMBER, - invoice.getItemValue(MahnlaufService.ITEM_DBTR_NUMBER)); - mahnlaufWorkitem.setItemValue("invoice.language", invoice.getItemValue("invoice.language")); + mahnlaufWorkitem.setItemValue(MahnlaufService.ITEM_DBTR_NUMBER, + invoice.getItemValue(MahnlaufService.ITEM_DBTR_NUMBER)); + mahnlaufWorkitem.setItemValue("invoice.language", invoice.getItemValue("invoice.language")); - // set workflow group name from the Task Element to identify document in xslt - Model model = modelService.getModel(modelVersion); - ItemCollection task = model.getTask(taskID); - String modelTaskGroupName = task.getItemValueString("txtworkflowgroup"); // DO NOT CHANGE! - mahnlaufWorkitem.setItemValue(WorkflowKernel.WORKFLOWGROUP, modelTaskGroupName); + // set workflow group name from the Task Element to identify document in xslt + Model model = modelService.getModel(modelVersion); + ItemCollection task = model.getTask(taskID); + String modelTaskGroupName = task.getItemValueString("txtworkflowgroup"); // DO NOT CHANGE! + mahnlaufWorkitem.setItemValue(WorkflowKernel.WORKFLOWGROUP, modelTaskGroupName); - logger.info("...created new mahnlauf " + key + "..."); + logger.info("...created new mahnlauf " + key + "..."); - return mahnlaufWorkitem; - } + return mahnlaufWorkitem; + } } \ No newline at end of file diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufService.java index ea9828b..2516dda 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/mahnlauf/MahnlaufService.java @@ -1,26 +1,26 @@ /******************************************************************************* - * Imixs Workflow - * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, + * Imixs Workflow + * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Project: + * + * Project: * http://www.imixs.org * http://java.net/projects/imixs-workflow - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika - Software Developer *******************************************************************************/ @@ -49,11 +49,11 @@ import jakarta.ejb.Singleton; import jakarta.inject.Inject; /** - * Der MahnlaufService wird vom MahnlaufExecuteAdapter um - * Ausgangsrechnungen zu akutalisieren. - * + * Der MahnlaufService wird vom MahnlaufExecuteAdapter um Ausgangsrechnungen zu + * akutalisieren. + * * @author rsoika - * + * */ @DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", @@ -87,7 +87,7 @@ public class MahnlaufService { /** * * Diese Method processed eine Invoice mit Manager Rechnten - * + * * @throws PluginException * @throws ModelException * @throws ProcessingErrorException @@ -104,7 +104,7 @@ public class MahnlaufService { /** * Helper method that loads all expressions from the spaces - * + * * @return List of list of expressions */ public Map> loadSpacePosMappings() { @@ -125,7 +125,7 @@ public class MahnlaufService { /** * Hilfsmethode zum errechnen des richtigen Spaces zu einer invoice.text Positon - * + * * @param workitem * @param spacePosMappings */ diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/BusinessPartnerImportService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/BusinessPartnerImportService.java index 8dbcced..2da25e6 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/BusinessPartnerImportService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/BusinessPartnerImportService.java @@ -1,27 +1,27 @@ -/* - * Imixs-Workflow - * - * Copyright (C) 2001-2020 Imixs Software Solutions GmbH, +/* + * Imixs-Workflow + * + * Copyright (C) 2001-2020 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Project: + * + * Project: * https://www.imixs.org * https://github.com/imixs/imixs-workflow - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - Project Management * Ralph Soika - Software Developer */ @@ -59,11 +59,10 @@ import jakarta.enterprise.event.Observes; /** * Der BusinessPartnerImportService hängt sich an den standard CSVImport Service - * der die Cargosoft Stammdaten aktualisiert. - * Es ist ein simples Observer Pattern das auf das Document Event - * "ON_DOCUMENT_SAVE" reagiert. Hier hängt sich der BusinessPartnerImportService - * ein und prüft ob der Workflow schon existiert oder ggf. aktualisiert werden - * muss. + * der die Cargosoft Stammdaten aktualisiert. Es ist ein simples Observer + * Pattern das auf das Document Event "ON_DOCUMENT_SAVE" reagiert. Hier hängt + * sich der BusinessPartnerImportService ein und prüft ob der Workflow schon + * existiert oder ggf. aktualisiert werden muss. *

    * Der Service migriert auch die alten zusätzlichen IBAN/BIC felder wenn der * Business partner erstmals neu angelegt wird. @@ -73,7 +72,7 @@ import jakarta.enterprise.event.Observes; *

    * Im Cargosoft Entity wird zusätzlich das flag NOSNAPSHOT=true gesetzt. Dies * ist nur eine Performance Optimierung. - * + * * @author rsoika * */ @@ -107,8 +106,8 @@ public class BusinessPartnerImportService { /** * Die Methode reagiert auf das CDI DocumentEvent beim Import von cargosoft * cargosoftkreditor documenten. - * - * + * + * */ public void onEvent(@Observes DocumentEvent event) { @@ -138,9 +137,9 @@ public class BusinessPartnerImportService { /** * Syncnrhonisert ein drecks Cargosoft Objekt mit dem neuen Busienss partner * objekt - * + * * Gibt true urück wenn sich was verädnert hat - * + * * @param creditor */ public boolean syncCargosoftCreditorBusinessPartner(ItemCollection importDoc) { @@ -218,7 +217,7 @@ public class BusinessPartnerImportService { /** * Erstellt ein neues leeres Business Partner Object - * + * * @return */ private ItemCollection createBusinessPartner(String partnerID) { @@ -340,7 +339,7 @@ public class BusinessPartnerImportService { /** * Sucht nach einem Business partner - * + * * @param id * @return */ diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLEAkteImportService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLEAkteImportService.java index 47b8194..198069f 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLEAkteImportService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLEAkteImportService.java @@ -1,27 +1,27 @@ -/* - * Imixs-Workflow - * - * Copyright (C) 2001-2020 Imixs Software Solutions GmbH, +/* + * Imixs-Workflow + * + * Copyright (C) 2001-2020 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Project: + * + * Project: * https://www.imixs.org * https://github.com/imixs/imixs-workflow - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - Project Management * Ralph Soika - Software Developer */ @@ -89,20 +89,19 @@ import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; /** - * Der CargosoftXMLEAkteImportService erweitert den FTPImportService und - * kann XML EAkten (Steuerbescheide) von Cargosoft importieren. - * Der Service reagiert auf DocumentImportEvents und kann über das Options Feld - * konfiguriert werden. + * Der CargosoftXMLEAkteImportService erweitert den FTPImportService und kann + * XML EAkten (Steuerbescheide) von Cargosoft importieren. Der Service reagiert + * auf DocumentImportEvents und kann über das Options Feld konfiguriert werden. *

    * Im Gegensatz zum FTPImportService werden die XML Dateien nach einem * speziellen Verfahren geparsed und dann als Steuerbescheid importiert. *

    - * In den Optionen muss die option "mandant.id" hinterlegt sein. Stimmt - * diese beim Import nicht mit der Mandanten Nummer in der XML Datei überein, - * wird das XML Dokument gelöscht und nicht importiert. - * - * - * + * In den Optionen muss die option "mandant.id" hinterlegt sein. Stimmt diese + * beim Import nicht mit der Mandanten Nummer in der XML Datei überein, wird das + * XML Dokument gelöscht und nicht importiert. + * + * + * * @author rsoika * */ @@ -135,8 +134,8 @@ public class CargosoftXMLEAkteImportService { /** * This method reacts on a CDI ImportEvent and reads documents form a ftp * server. - * - * + * + * */ public void onEvent(@Observes DocumentImportEvent event) { @@ -162,8 +161,7 @@ public class CargosoftXMLEAkteImportService { return; } - documentImportService.logMessage("...CARGOSOFT_EAKTE_XML Import started: mandant.id=" + mandantID, - event); + documentImportService.logMessage("...CARGOSOFT_EAKTE_XML Import started: mandant.id=" + mandantID, event); String ftpServer = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_SERVER); String ftpPort = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_PORT); String ftpUser = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_USER); @@ -216,8 +214,8 @@ public class CargosoftXMLEAkteImportService { ftpClient.connect(ftpServer, ftpPort); if (ftpClient.login(ftpUser, ftpPassword) == false) { - throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), - "FTP_ERROR", "FTP file transfer failed: login failed!"); + throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), "FTP_ERROR", + "FTP file transfer failed: login failed!"); } ftpClient.enterLocalPassiveMode(); logger.finest("...... FileType=" + FTP.BINARY_FILE_TYPE); @@ -225,8 +223,7 @@ public class CargosoftXMLEAkteImportService { ftpClient.setControlEncoding("UTF-8"); } catch (IOException e) { - throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), - "FTP_ERROR", e.getMessage()); + throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), "FTP_ERROR", e.getMessage()); } return ftpClient; @@ -234,7 +231,7 @@ public class CargosoftXMLEAkteImportService { /** * Helper method reads the working FTP directory and starts importing the files. - * + * * @param ftpClient * @param ftpPath * @throws PluginException @@ -250,8 +247,8 @@ public class CargosoftXMLEAkteImportService { // try to enter the working directory and read all files... boolean bWorkingDir = ftpClient.changeWorkingDirectory(ftpPath); if (bWorkingDir == false) { - throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), - "FTP_ERROR", "...failed to change into working directory: "); + throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), "FTP_ERROR", + "...failed to change into working directory: "); } FTPFile[] allFiles = ftpClient.listFiles(ftpPath); @@ -299,10 +296,10 @@ public class CargosoftXMLEAkteImportService { // "processed/"); // } - } catch (AccessDeniedException | ProcessingErrorException | PluginException - | ModelException | XPathExpressionException | TransformerException e) { - throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), - "FTP_ERROR", e.getMessage()); + } catch (AccessDeniedException | ProcessingErrorException | PluginException | ModelException + | XPathExpressionException | TransformerException e) { + throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), "FTP_ERROR", + e.getMessage()); } } documentImportService.logMessage("..." + count + " new files imported.", event); @@ -313,8 +310,7 @@ public class CargosoftXMLEAkteImportService { int r = ftpClient.getReplyCode(); logger.severe("FTP ReplyCode=" + r); - throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), - "FTP_ERROR", e.getMessage()); + throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), "FTP_ERROR", e.getMessage()); } finally { // do logout.... @@ -324,8 +320,7 @@ public class CargosoftXMLEAkteImportService { ftpClient.disconnect(); } catch (IOException e) { documentImportService.logMessage("...FTP file transfer failed: " + e.getMessage(), event); - throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), - "FTP_ERROR", e.getMessage()); + throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), "FTP_ERROR", e.getMessage()); } } } @@ -333,8 +328,8 @@ public class CargosoftXMLEAkteImportService { /** * Creates and processes a new workitem with a given xml information from the * filedata - * - * + * + * * @return * @throws ModelException * @throws PluginException @@ -344,9 +339,8 @@ public class CargosoftXMLEAkteImportService { * @throws XPathExpressionException */ public ItemCollection createWorkitem(ItemCollection source, String fileName, byte[] rawData, - Map> spacePosMappings) - throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException, - XPathExpressionException, TransformerException { + Map> spacePosMappings) throws AccessDeniedException, ProcessingErrorException, + PluginException, ModelException, XPathExpressionException, TransformerException { ItemCollection workitem = new ItemCollection(); workitem.model(source.getItemValueString(DocumentImportService.SOURCE_ITEM_MODELVERSION)); workitem.task(source.getItemValueInteger(DocumentImportService.SOURCE_ITEM_TASK)); @@ -375,37 +369,37 @@ public class CargosoftXMLEAkteImportService { // invoice.rate // invoice.base.amount - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='client']", workitem, - "mandant.id", String.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='client']", workitem, "mandant.id", + String.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='cs_voucher_number']", - workitem, "invoice.number", String.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='cs_voucher_number']", workitem, + "invoice.number", String.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='cs_address_number']", - workitem, "cdtr.number", String.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='cs_address_number']", workitem, + "cdtr.number", String.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='voucherdate']", - workitem, "invoice.date", Date.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='voucherdate']", workitem, + "invoice.date", Date.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='currency']", - workitem, "invoice.currency", String.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='currency']", workitem, + "invoice.currency", String.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='currency_rate']", - workitem, "invoice.rate", Double.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='currency_rate']", workitem, + "invoice.rate", Double.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='total_net_amount']", - workitem, "invoice.total.net", Double.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='total_tax_amount']", - workitem, "invoice.total.tax", Double.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='reference']", - workitem, "invoice.atc.number", String.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='booking_period']", - workitem, "invoice.booking_period", String.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='total_net_amount']", workitem, + "invoice.total.net", Double.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='total_tax_amount']", workitem, + "invoice.total.tax", Double.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='reference']", workitem, + "invoice.atc.number", String.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='booking_period']", workitem, + "invoice.booking_period", String.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='booking_date']", - workitem, "invoice.booking_date", Date.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='booking_text']", - workitem, "invoice.booking_text", String.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='booking_date']", workitem, + "invoice.booking_date", Date.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/References/Reference[@type='booking_text']", workitem, + "invoice.booking_text", String.class); // verify if invoice is already imported. if (alreadyImported(workitem.getItemValueString("invoice.number"))) { @@ -440,8 +434,7 @@ public class CargosoftXMLEAkteImportService { attacheFiles(doc, workitem); } } catch (ParserConfigurationException | SAXException | IOException e) { - throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), - "XML_ERROR", e.getMessage()); + throw new PluginException(CargosoftXMLEAkteImportService.class.getName(), "XML_ERROR", e.getMessage()); } return workitem; } @@ -449,12 +442,11 @@ public class CargosoftXMLEAkteImportService { /** * Reads a tag value from the xml tree and set the value into the given * workitem. - * + * * /Invoices/Invoice/InvoiceHeader/Client/Code - * - * Beispiel Datum: - * 2024-05-13T00:00:00+02:00 - * + * + * Beispiel Datum: 2024-05-13T00:00:00+02:00 + * * @param doc - xml doc * @param expression - xpath expression * @param workitem @@ -513,10 +505,10 @@ public class CargosoftXMLEAkteImportService { } /** - * This method attache the pdf file and the XML file to the workitem. - * The method removes the file content first form the xml tree and attache the - * XML without the pdf data. - * + * This method attache the pdf file and the XML file to the workitem. The method + * removes the file content first form the xml tree and attache the XML without + * the pdf data. + * * @throws XPathExpressionException * @throws TransformerException */ @@ -526,24 +518,23 @@ public class CargosoftXMLEAkteImportService { XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); // read filename and content.... - readXMLValue(doc, "/CargoSoftEFile/EFile/Attachments/Attachment/Filename", - workitem, "import.filename", String.class); - readXMLValue(doc, "/CargoSoftEFile/EFile/Attachments/Attachment/Content", - workitem, "import.filedata", String.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/Attachments/Attachment/Filename", workitem, "import.filename", + String.class); + readXMLValue(doc, "/CargoSoftEFile/EFile/Attachments/Attachment/Content", workitem, "import.filedata", + String.class); // attache the PDF file content String fileDataString = workitem.getItemValueString("import.filedata"); byte[] decodedPDFData = java.util.Base64.getDecoder().decode(fileDataString); - FileData fileData = new FileData(workitem.getItemValueString("import.filename"), - decodedPDFData, "application/pdf", null); + FileData fileData = new FileData(workitem.getItemValueString("import.filename"), decodedPDFData, + "application/pdf", null); workitem.addFileData(fileData); // remove the temp items.. workitem.removeItem("import.filename"); workitem.removeItem("import.filedata"); // next remove the Attachments/AttachmentContent from the dom tree - XPathExpression attachmentsExpr = xpath - .compile("/CargoSoftEFile/EFile/Attachments/Attachment/Content"); + XPathExpression attachmentsExpr = xpath.compile("/CargoSoftEFile/EFile/Attachments/Attachment/Content"); NodeList contentNodes = (NodeList) attachmentsExpr.evaluate(doc, XPathConstants.NODESET); // Remove the matching nodes for (int i = 0; i < contentNodes.getLength(); i++) { @@ -569,7 +560,7 @@ public class CargosoftXMLEAkteImportService { /** * Prüft ob die Belegnummer schon existiert (importiert wurde) - * + * * @param belegNummer * @return */ @@ -592,24 +583,23 @@ public class CargosoftXMLEAkteImportService { /** * This helper method reads the references starting with 'Row_' and creates a * child Workitem for each row. - * + * * The method assumes that the rows are starting with the type 'row_n_' where - * 'n' is the row number. - * We start with row 1 and read until we found more rows. - * + * 'n' is the row number. We start with row 1 and read until we found more rows. + * * Example: - * + * *

    {@code
    -     *  ... 
    +     *  ...
             
             3591.340
             0
             IM-GCA-2408-116
          * }
    - * + * *

    * The last filenumber text will be transferred into the item 'invoice.text' - * + * * @param xml * @return * @throws Exception diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportService.java index 10ac3aa..3b02d3a 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/xml/CargosoftXMLInvoiceImportService.java @@ -1,27 +1,27 @@ -/* - * Imixs-Workflow - * - * Copyright (C) 2001-2020 Imixs Software Solutions GmbH, +/* + * Imixs-Workflow + * + * Copyright (C) 2001-2020 Imixs Software Solutions GmbH, * http://www.imixs.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html - * - * Project: + * + * Project: * https://www.imixs.org * https://github.com/imixs/imixs-workflow - * - * Contributors: + * + * Contributors: * Imixs Software Solutions GmbH - Project Management * Ralph Soika - Software Developer */ @@ -89,22 +89,21 @@ import jakarta.inject.Inject; /** * Der CargosoftXMLInvoiceImportImportService erweitert den FTPImportService und - * kann XML Invoice Dokumente von Cargosoft importieren. - * Der Service reagiert auf DocumentImportEvents und kann über das Options Feld - * konfiguriert werden. + * kann XML Invoice Dokumente von Cargosoft importieren. Der Service reagiert + * auf DocumentImportEvents und kann über das Options Feld konfiguriert werden. *

    * Im Gegensatz zum FTPImportService werden die XML Dateien nach einem - * speziellen Verfahren geparsed und dann als Ausgangsrechnungen importiert. - * Der Service ersetzt den alten DATEV Import. Falls eine Rechnung schon - * existiert (Rechnungsnummer) wird der Datensatz vom FTP Laufwerk gelöscht und - * nicht importiert. + * speziellen Verfahren geparsed und dann als Ausgangsrechnungen importiert. Der + * Service ersetzt den alten DATEV Import. Falls eine Rechnung schon existiert + * (Rechnungsnummer) wird der Datensatz vom FTP Laufwerk gelöscht und nicht + * importiert. *

    - * In den Optionen muss die option "mandant.id" hinterlegt sein. Stimmt - * diese beim Import nicht mit der Mandanten Nummer in der XML Datei überein, - * wird das XML Dokument gelöscht und nicht importiert. - * - * - * + * In den Optionen muss die option "mandant.id" hinterlegt sein. Stimmt diese + * beim Import nicht mit der Mandanten Nummer in der XML Datei überein, wird das + * XML Dokument gelöscht und nicht importiert. + * + * + * * @author rsoika * */ @@ -137,8 +136,8 @@ public class CargosoftXMLInvoiceImportService { /** * This method reacts on a CDI ImportEvent and reads documents form a ftp * server. - * - * + * + * */ public void onEvent(@Observes DocumentImportEvent event) { @@ -164,8 +163,7 @@ public class CargosoftXMLInvoiceImportService { return; } - documentImportService.logMessage("...CARGOSOFT_INVOICE_XML Import started: mandant.id=" + mandantID, - event); + documentImportService.logMessage("...CARGOSOFT_INVOICE_XML Import started: mandant.id=" + mandantID, event); String ftpServer = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_SERVER); String ftpPort = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_PORT); String ftpUser = event.getSource().getItemValueString(DocumentImportService.SOURCE_ITEM_USER); @@ -218,8 +216,8 @@ public class CargosoftXMLInvoiceImportService { ftpClient.connect(ftpServer, ftpPort); if (ftpClient.login(ftpUser, ftpPassword) == false) { - throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), - "FTP_ERROR", "FTP file transfer failed: login failed!"); + throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", + "FTP file transfer failed: login failed!"); } ftpClient.enterLocalPassiveMode(); logger.finest("...... FileType=" + FTP.BINARY_FILE_TYPE); @@ -227,8 +225,7 @@ public class CargosoftXMLInvoiceImportService { ftpClient.setControlEncoding("UTF-8"); } catch (IOException e) { - throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), - "FTP_ERROR", e.getMessage()); + throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", e.getMessage()); } return ftpClient; @@ -236,7 +233,7 @@ public class CargosoftXMLInvoiceImportService { /** * Helper method reads the working FTP directory and starts importing the files. - * + * * @param ftpClient * @param ftpPath * @throws PluginException @@ -252,8 +249,8 @@ public class CargosoftXMLInvoiceImportService { // try to enter the working directory and read all files... boolean bWorkingDir = ftpClient.changeWorkingDirectory(ftpPath); if (bWorkingDir == false) { - throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), - "FTP_ERROR", "...failed to change into working directory: "); + throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", + "...failed to change into working directory: "); } FTPFile[] allFiles = ftpClient.listFiles(ftpPath); @@ -301,10 +298,10 @@ public class CargosoftXMLInvoiceImportService { // "processed/"); // } - } catch (AccessDeniedException | ProcessingErrorException | PluginException - | ModelException | XPathExpressionException | TransformerException e) { - throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), - "FTP_ERROR", e.getMessage()); + } catch (AccessDeniedException | ProcessingErrorException | PluginException | ModelException + | XPathExpressionException | TransformerException e) { + throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", + e.getMessage()); } } documentImportService.logMessage("..." + count + " new files imported.", event); @@ -315,8 +312,7 @@ public class CargosoftXMLInvoiceImportService { int r = ftpClient.getReplyCode(); logger.severe("FTP ReplyCode=" + r); - throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), - "FTP_ERROR", e.getMessage()); + throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", e.getMessage()); } finally { // do logout.... @@ -326,8 +322,8 @@ public class CargosoftXMLInvoiceImportService { ftpClient.disconnect(); } catch (IOException e) { documentImportService.logMessage("...FTP file transfer failed: " + e.getMessage(), event); - throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), - "FTP_ERROR", e.getMessage()); + throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "FTP_ERROR", + e.getMessage()); } } } @@ -335,8 +331,8 @@ public class CargosoftXMLInvoiceImportService { /** * Creates and processes a new workitem with a given xml information from the * fileData - * - * + * + * * @return * @throws ModelException * @throws PluginException @@ -346,9 +342,8 @@ public class CargosoftXMLInvoiceImportService { * @throws XPathExpressionException */ public ItemCollection createWorkitem(ItemCollection source, String fileName, byte[] rawData, - Map> spacePosMappings) - throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException, - XPathExpressionException, TransformerException { + Map> spacePosMappings) throws AccessDeniedException, ProcessingErrorException, + PluginException, ModelException, XPathExpressionException, TransformerException { ItemCollection workitem = new ItemCollection(); workitem.model(source.getItemValueString(DocumentImportService.SOURCE_ITEM_MODELVERSION)); workitem.task(source.getItemValueInteger(DocumentImportService.SOURCE_ITEM_TASK)); @@ -377,11 +372,11 @@ public class CargosoftXMLInvoiceImportService { // invoice.rate // invoice.base.amount - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Client/Codes/Code", workitem, - "mandant.id", String.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Client/Codes/Code", workitem, "mandant.id", + String.class); - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceNumber", - workitem, "invoice.number", String.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceNumber", workitem, "invoice.number", + String.class); // verify if invoice is already imported. if (alreadyImported(workitem.getItemValueString("invoice.number"))) { @@ -389,14 +384,14 @@ public class CargosoftXMLInvoiceImportService { return null; } - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceType/Codes/Code", - workitem, "invoice.type", String.class); - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceType/Description", - workitem, "invoice.type.description", String.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceType/Codes/Code", workitem, "invoice.type", + String.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceType/Description", workitem, + "invoice.type.description", String.class); // Rechnungssumme errechnen..... - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value", - workitem, "invoice.total.net", Double.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/NetAmount/Amount/Value", workitem, + "invoice.total.net", Double.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/VATInformation/VATAmount/Amount/Value", workitem, "invoice.total.tax", Double.class); if (!workitem.hasItem("invoice.total.tax")) { @@ -416,34 +411,33 @@ public class CargosoftXMLInvoiceImportService { } // Steuer - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/VATInformation/VAT/VATRate", - workitem, "invoice.vatrate", Double.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAmount/VATInformation/VAT/VATRate", workitem, + "invoice.vatrate", Double.class); // Booking Period - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/BookingPeriod", - workitem, "invoice.bookingperiod", String.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/BookingPeriod", workitem, "invoice.bookingperiod", + String.class); - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceCurrency/Codes/Code", - workitem, "invoice.currency", String.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceCurrency/Codes/Code", workitem, + "invoice.currency", String.class); - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceDate", - workitem, "invoice.date", Date.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceDate", workitem, "invoice.date", Date.class); readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/PaymentConditions/PaymentCondition/Codes/Code[@Type='cs']", workitem, "payment.term", String.class); // kostenstelle - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/CostUnit/Description", - workitem, "invoice.CostUnit", String.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/CostUnit/Description", workitem, "invoice.CostUnit", + String.class); - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/PaymentConditions/DueDate", - workitem, "invoice.duedate", Date.class); - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/PaymentConditions/DueDate", - workitem, "invoice.reminder", Date.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/PaymentConditions/DueDate", workitem, "invoice.duedate", + Date.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/PaymentConditions/DueDate", workitem, "invoice.reminder", + Date.class); - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAddress/Codes/Code", - workitem, "dbtr.number", String.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/InvoiceAddress/Codes/Code", workitem, "dbtr.number", + String.class); String dbtrNumber = workitem.getItemValueString("dbtr.number"); if (dbtrNumber.startsWith("K") || dbtrNumber.startsWith("D")) { workitem.setItemValue("dbtr.number", dbtrNumber.substring(1)); @@ -487,17 +481,16 @@ public class CargosoftXMLInvoiceImportService { attacheFiles(doc, workitem); } catch (ParserConfigurationException | SAXException | IOException e) { - throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), - "XML_ERROR", e.getMessage()); + throw new PluginException(CargosoftXMLInvoiceImportService.class.getName(), "XML_ERROR", e.getMessage()); } return workitem; } /** * Prüft ob die Rechnung eine Rückstellung ist also mit R1 bis R8 beginnt - * + * * ^R[1-8] . - * + * * @param workitem * @return */ @@ -509,12 +502,11 @@ public class CargosoftXMLInvoiceImportService { /** * Reads a tag value from the xml tree and set the value into the given * workitem. - * + * * /Invoices/Invoice/InvoiceHeader/Client/Code - * - * Beispiel Datum: - * 2024-05-13T00:00:00+02:00 - * + * + * Beispiel Datum: 2024-05-13T00:00:00+02:00 + * * @param doc - xml doc * @param expression - xpath expression * @param workitem @@ -566,12 +558,10 @@ public class CargosoftXMLInvoiceImportService { /** * Hilfsmethode die die ATC Nummer sucht und in das workitem feld - * invoice.atc.number einträgt. - * Wir gehen davon aus, das wir über die ChildItems diese aus dem billingtexts - * schon ermittel haben. - * Es können mehrere ATC nummern existieren. Diese werden in einer Liste - * gespeichert - * + * invoice.atc.number einträgt. Wir gehen davon aus, das wir über die ChildItems + * diese aus dem billingtexts schon ermittel haben. Es können mehrere ATC + * nummern existieren. Diese werden in einer Liste gespeichert + * * @param doc * @param workitem */ @@ -592,19 +582,13 @@ public class CargosoftXMLInvoiceImportService { /** * Reads the positions rows - * + * * InvoiceRows/InvoiceRow * - * Each row has the following items: - * _childitems { - * datev.shzeichen=[H], - * datev.kurs=[0.0], - * datev.basisumsatz=[0.0], - * datev.text=[R LA-PAP-2405-051], - * datev.konto=[3851], - * datev.umsatz=[3307.81], - * datev.wkz=[EUR]}} - * + * Each row has the following items: _childitems { datev.shzeichen=[H], + * datev.kurs=[0.0], datev.basisumsatz=[0.0], datev.text=[R LA-PAP-2405-051], + * datev.konto=[3851], datev.umsatz=[3307.81], datev.wkz=[EUR]}} + * * @param doc - xml doc * @param workitem */ @@ -622,8 +606,7 @@ public class CargosoftXMLInvoiceImportService { XPathExpression netAmountExchangeRateExpr = xPath .compile("InvoiceAmount/NetAmount/Amount/ExchangeRate/text()"); - XPathExpression netActivityTypeExpr = xPath - .compile("ActivityType/Codes/Code[@Type='cs']/text()"); + XPathExpression netActivityTypeExpr = xPath.compile("ActivityType/Codes/Code[@Type='cs']/text()"); NodeList rowList = doc.getElementsByTagName("InvoiceRow"); double _kurs = 0.0; @@ -679,7 +662,7 @@ public class CargosoftXMLInvoiceImportService { /** * S/H Kennzeichen auflösen - * + * * Bei G = Gutschrift oder SR = Stornorechnung müssen wir das vorzeichen ändern * Invoice Type S/H */ @@ -693,7 +676,7 @@ public class CargosoftXMLInvoiceImportService { /** * Liste der BillingText-Elemente lesen.... - * + * * und ggf. die ATC Nummer finden.... **/ childItemCol.setItemValue("BillingCode", billingCode); @@ -732,14 +715,11 @@ public class CargosoftXMLInvoiceImportService { } /** - * G = Gutschrift - * SR = Stornorechnung - * GK = Sammelgutschrift - * SS = Sammelrechnung Storno - * - * müssen wir das vorzeichen ändern - * dies wird über den Invoice.Type geprüft - * + * G = Gutschrift SR = Stornorechnung GK = Sammelgutschrift SS = Sammelrechnung + * Storno + * + * müssen wir das vorzeichen ändern dies wird über den Invoice.Type geprüft + * * @return */ private boolean isGutschrift(ItemCollection workitem) { @@ -748,10 +728,10 @@ public class CargosoftXMLInvoiceImportService { } /** - * This method attache the pdf file and the XML file to the workitem. - * The method removes the file content first form the xml tree and attache the - * XML without the pdf data. - * + * This method attache the pdf file and the XML file to the workitem. The method + * removes the file content first form the xml tree and attache the XML without + * the pdf data. + * * @throws XPathExpressionException * @throws TransformerException */ @@ -761,16 +741,16 @@ public class CargosoftXMLInvoiceImportService { XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); // read filename and content.... - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Attachments/Attachment/Filename", - workitem, "import.filename", String.class); - readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Attachments/Attachment/Content", - workitem, "import.filedata", String.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Attachments/Attachment/Filename", workitem, + "import.filename", String.class); + readXMLValue(doc, "/Invoices/Invoice/InvoiceHeader/Attachments/Attachment/Content", workitem, "import.filedata", + String.class); // attache the PDF file content String fileDataString = workitem.getItemValueString("import.filedata"); byte[] decodedPDFData = java.util.Base64.getDecoder().decode(fileDataString); - FileData fileData = new FileData(workitem.getItemValueString("import.filename"), - decodedPDFData, "application/pdf", null); + FileData fileData = new FileData(workitem.getItemValueString("import.filename"), decodedPDFData, + "application/pdf", null); workitem.addFileData(fileData); // remove the temp items.. workitem.removeItem("import.filename"); @@ -804,7 +784,7 @@ public class CargosoftXMLInvoiceImportService { /** * Prüft ob die Belegnummer schon existiert (importiert wurde) - * + * * @param belegNummer * @return */ @@ -828,7 +808,7 @@ public class CargosoftXMLInvoiceImportService { * Diese Method sucht einen Debitor. *

    * Diese Methode erwartet das führende D - * + * * @throws PluginException */ public ItemCollection findDebitor(String dbtrNumber) throws PluginException { diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/TokenResponse.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/TokenResponse.java index 66cb23e..433a866 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/TokenResponse.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/TokenResponse.java @@ -2,9 +2,9 @@ package com.alexanderlogistics.zoho; /** * Helper class for JSON deserialization - * + * * If the request is successful, you would receive the following: - * + * *

         {
         "access_token": "{access_token}",
    diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoAPIService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoAPIService.java
    index 0486d5e..06ac9a1 100644
    --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoAPIService.java
    +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoAPIService.java
    @@ -42,336 +42,306 @@ import jakarta.json.bind.JsonbConfig;
      * Der ZohoAPIService stellt methoden für den Zugriff auf die Zoho API bereit.
      */
     @DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
    -                "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
    -                "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
    +        "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
    +        "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
     @RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
    -                "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
    -                "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
    +        "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
    +        "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
     @Singleton
     @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
     public class ZohoAPIService {
     
    -        private static Logger logger = Logger.getLogger(ZohoAPIService.class.getName());
    -        private static final String ZOHO_API_BASE_URL = "https://www.zohoapis.eu/books/v3/";
    +    private static Logger logger = Logger.getLogger(ZohoAPIService.class.getName());
     
    -        @Inject
    -        ZohoOAuthManager zohoOAuthManager;
    +    @Inject
    +    ZohoOAuthManager zohoOAuthManager;
     
    -        private HttpClient httpClient;
    -        private Jsonb jsonb;
    +    private HttpClient httpClient;
    +    private Jsonb jsonb;
     
    -        @PostConstruct
    -        void init() {
    -                this.httpClient = HttpClient.newBuilder()
    -                                .connectTimeout(Duration.ofSeconds(10))
    -                                .build();
    +    @PostConstruct
    +    void init() {
    +        this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
     
    -                JsonbConfig config = new JsonbConfig()
    -                                .withDateFormat("yyyy-MM-dd", Locale.getDefault())
    -                                .setProperty("jsonb.fail-on-unknown-properties", false); // Ignoriert unbekannte Felder
    -                this.jsonb = JsonbBuilder.create(config);
    +        JsonbConfig config = new JsonbConfig().withDateFormat("yyyy-MM-dd", Locale.getDefault())
    +                .setProperty("jsonb.fail-on-unknown-properties", false); // Ignoriert unbekannte Felder
    +        this.jsonb = JsonbBuilder.create(config);
    +    }
    +
    +    public String findContactIDByCompanyName(String companyName, String accessToken) throws PluginException {
    +
    +        // Print JSON to console for verification
    +        logger.info("├── Zoho lookup contact '" + companyName + "'...");
    +        try {
    +            // https://www.zohoapis.com/books/v3/contacts?organization_id=10234695&company_name=xxx'
    +            String uri = zohoOAuthManager.getBaseURI() + "contacts?organization_id="
    +                    + zohoOAuthManager.getOrganization() + "&company_name="
    +                    + URLEncoder.encode(companyName, StandardCharsets.UTF_8);
    +            logger.fine("│   ├──  uri= " + uri);
    +            logger.fine("│   ├──  accessToken=" + accessToken);
    +            HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri))
    +                    .header("Authorization", "Zoho-oauthtoken " + accessToken)
    +                    .header("Content-Type", "application/json").GET().build();
    +
    +            HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    +
    +            // Print response details to console
    +            logger.fine("├── Zoho Response:");
    +            logger.fine("│   ├──  Status Code: " + response.statusCode());
    +            logger.fine("│   ├──  Response Body: " + response.body());
    +            if (response.statusCode() >= 300) {
    +                throw new PluginException(this.getClass().getName(), "Zoho API error",
    +                        "Failed to find Contacts - Status code: " + response.statusCode() + ", Response: "
    +                                + response.body());
    +            }
    +
    +            ZohoContactFindResponse contactsResponse = jsonb.fromJson(response.body(), ZohoContactFindResponse.class);
    +
    +            if (contactsResponse != null && contactsResponse.getContacts() != null) {
    +                // Durchsuche die Ergebnisse nach exakter Übereinstimmung
    +                for (ZohoContact contact : contactsResponse.getContacts()) {
    +                    if (companyName.equalsIgnoreCase(contact.getCompanyName())) {
    +                        logger.info("│   ├──  contactID= " + contact.getContactId());
    +                        return contact.getContactId();
    +                    }
    +                }
    +            }
    +            return null;
    +        } catch (Exception e) {
    +            throw new PluginException(this.getClass().getName(), "Zoho API error", "Failed to find contacts", e);
             }
     
    -        public String findContactIDByCompanyName(String companyName, String accessToken) throws PluginException {
    +    }
     
    -                // Print JSON to console for verification
    -                logger.info("├── Zoho lookup contact '" + companyName + "'...");
    -                try {
    -                        // https://www.zohoapis.com/books/v3/contacts?organization_id=10234695&company_name=xxx'
    -                        String uri = ZOHO_API_BASE_URL + "contacts?organization_id="
    -                                        + zohoOAuthManager.getOrganization() + "&company_name="
    -                                        + URLEncoder.encode(companyName, StandardCharsets.UTF_8);
    -                        logger.fine("│   ├──  uri= " + uri);
    -                        logger.fine("│   ├──  accessToken=" + accessToken);
    -                        HttpRequest request = HttpRequest.newBuilder()
    -                                        .uri(URI.create(uri))
    -                                        .header("Authorization", "Zoho-oauthtoken " + accessToken)
    -                                        .header("Content-Type", "application/json")
    -                                        .GET()
    -                                        .build();
    -
    -                        HttpResponse response = httpClient.send(
    -                                        request, HttpResponse.BodyHandlers.ofString());
    -
    -                        // Print response details to console
    -                        logger.fine("├── Zoho Response:");
    -                        logger.fine("│   ├──  Status Code: " + response.statusCode());
    -                        logger.fine("│   ├──  Response Body: " + response.body());
    -                        if (response.statusCode() >= 300) {
    -                                throw new PluginException(this.getClass().getName(), "Zoho API error",
    -                                                "Failed to find Contacts - Status code: " + response.statusCode() +
    -                                                                ", Response: " + response.body());
    -                        }
    -
    -                        ZohoContactFindResponse contactsResponse = jsonb.fromJson(response.body(),
    -                                        ZohoContactFindResponse.class);
    -
    -                        if (contactsResponse != null && contactsResponse.getContacts() != null) {
    -                                // Durchsuche die Ergebnisse nach exakter Übereinstimmung
    -                                for (ZohoContact contact : contactsResponse.getContacts()) {
    -                                        if (companyName.equalsIgnoreCase(contact.getCompanyName())) {
    -                                                logger.info("│   ├──  contactID= " + contact.getContactId());
    -                                                return contact.getContactId();
    -                                        }
    -                                }
    -                        }
    -                        return null;
    -                } catch (Exception e) {
    -                        throw new PluginException(this.getClass().getName(), "Zoho API error",
    -                                        "Failed to find contacts", e);
    -                }
    +    /**
    +     * Exports an invocie document
    +     *
    +     * @see https://www.zoho.com/books/api/v3/invoices/#create-an-invoice
    +     *
    +     * @param invoice
    +     * @throws PluginException
    +     */
    +    public ZohoInvoice exportInvoice(ItemCollection invoice, List files) throws PluginException {
    +        String accessToken = zohoOAuthManager.getValidAccessToken();
    +        logger.info("├── Zoho Export Invoice " + invoice.getItemValueString("invoice.number") + "...");
     
    +        // first lookup contact!
    +        String contactID = "";
    +        contactID = findContactIDByCompanyName(invoice.getItemValueString("dbtr.name"), accessToken);
    +        if (contactID == null) {
    +            logger.info("│   ├──  contact not found");
    +            ZohoContact contact = createContact(invoice.getItemValueString("dbtr.name"), accessToken);
    +            contactID = contact.getContactId();
             }
     
    -        /**
    -         * Exports an invocie document
    -         * 
    -         * @see https://www.zoho.com/books/api/v3/invoices/#create-an-invoice
    -         * 
    -         * @param invoice
    -         * @throws PluginException
    -         */
    -        public ZohoInvoice exportInvoice(ItemCollection invoice, List files) throws PluginException {
    -                String accessToken = zohoOAuthManager.getValidAccessToken();
    -                logger.info("├── Zoho Export Invoice " + invoice.getItemValueString("invoice.number") + "...");
    +        // Konvertiere ItemCollection zu ZohoInvoiceDTO
    +        ZohoInvoice zohoInvoice = convertToZohoInvoice(invoice, contactID, accessToken);
     
    -                // first lookup contact!
    -                String contactID = "";
    -                contactID = findContactIDByCompanyName(invoice.getItemValueString("dbtr.name"), accessToken);
    -                if (contactID == null) {
    -                        logger.info("│   ├──  contact not found");
    -                        ZohoContact contact = createContact(invoice.getItemValueString("dbtr.name"), accessToken);
    -                        contactID = contact.getContactId();
    +        try {
    +            String requestBody = jsonb.toJson(zohoInvoice);
    +
    +            // Print JSON to console for verification
    +            logger.fine("├── Zoho Invoice JSON Request...");
    +            logger.fine("│   ├──  " + requestBody);
    +
    +            // https://www.zohoapis.com/books/v3/invoices?organization_id=10234695'
    +            String uri = zohoOAuthManager.getBaseURI() + "invoices?organization_id="
    +                    + zohoOAuthManager.getOrganization();
    +            logger.fine("│   ├──  uri= " + uri);
    +            logger.fine("│   ├──  accessToken=" + accessToken);
    +            HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri))
    +                    .header("Authorization", "Zoho-oauthtoken " + accessToken)
    +                    .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(requestBody))
    +                    .build();
    +
    +            HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    +
    +            // Print response details to console
    +            logger.fine("├── Zoho Response:");
    +            logger.fine("│   ├──  Status Code: " + response.statusCode());
    +
    +            if (response.statusCode() >= 300) {
    +                throw new PluginException(this.getClass().getName(), "Zoho API error",
    +                        "Failed to create invoice. Status code: " + response.statusCode() + ", Response: "
    +                                + response.body());
    +            }
    +
    +            // Erfolgreiche Erstellung
    +            String bodyContent = response.body();
    +            logger.fine("│   ├──  Response Body: " + bodyContent);
    +
    +            System.out.print(bodyContent);
    +            ZohoInvoiceCreateResponse createResponse = jsonb.fromJson(bodyContent, ZohoInvoiceCreateResponse.class);
    +
    +            logger.fine("│   ├──  OK - customerID=" + createResponse.getInvoice().getCustomerId());
    +            logger.info("│   ├──  OK - invoiceID=" + createResponse.getInvoice().getInvoiceId());
    +
    +            if (createResponse != null) {
    +                zohoInvoice = createResponse.getInvoice();
    +
    +                // now add attachments (optional)
    +                if (files != null) {
    +                    for (FileData file : files) {
    +                        attacheFileData(zohoInvoice.getInvoiceId(), file, "invoices", accessToken);
    +                    }
                     }
     
    -                // Konvertiere ItemCollection zu ZohoInvoiceDTO
    -                ZohoInvoice zohoInvoice = convertToZohoInvoice(invoice, contactID, accessToken);
    -
    -                try {
    -                        String requestBody = jsonb.toJson(zohoInvoice);
    -
    -                        // Print JSON to console for verification
    -                        logger.fine("├── Zoho Invoice JSON Request...");
    -                        logger.fine("│   ├──  " + requestBody);
    -
    -                        // https://www.zohoapis.com/books/v3/invoices?organization_id=10234695'
    -                        String uri = ZOHO_API_BASE_URL + "invoices?organization_id="
    -                                        + zohoOAuthManager.getOrganization();
    -                        logger.fine("│   ├──  uri= " + uri);
    -                        logger.fine("│   ├──  accessToken=" + accessToken);
    -                        HttpRequest request = HttpRequest.newBuilder()
    -                                        .uri(URI.create(uri))
    -                                        .header("Authorization", "Zoho-oauthtoken " + accessToken)
    -                                        .header("Content-Type", "application/json")
    -                                        .POST(HttpRequest.BodyPublishers.ofString(requestBody))
    -                                        .build();
    -
    -                        HttpResponse response = httpClient.send(
    -                                        request, HttpResponse.BodyHandlers.ofString());
    -
    -                        // Print response details to console
    -                        logger.fine("├── Zoho Response:");
    -                        logger.fine("│   ├──  Status Code: " + response.statusCode());
    -
    -                        if (response.statusCode() >= 300) {
    -                                throw new PluginException(this.getClass().getName(), "Zoho API error",
    -                                                "Failed to create invoice. Status code: " + response.statusCode() +
    -                                                                ", Response: " + response.body());
    -                        }
    -
    -                        // Erfolgreiche Erstellung
    -                        String bodyContent = response.body();
    -                        logger.fine("│   ├──  Response Body: " + bodyContent);
    -
    -                        System.out.print(bodyContent);
    -                        ZohoInvoiceCreateResponse createResponse = jsonb.fromJson(bodyContent,
    -                                        ZohoInvoiceCreateResponse.class);
    -
    -                        logger.fine("│   ├──  OK - customerID=" + createResponse.getInvoice().getCustomerId());
    -                        logger.info("│   ├──  OK - invoiceID=" + createResponse.getInvoice().getInvoiceId());
    -
    -                        if (createResponse != null) {
    -                                zohoInvoice = createResponse.getInvoice();
    -
    -                                // now add attachments (optional)
    -                                if (files != null) {
    -                                        for (FileData file : files) {
    -                                                attacheFileData(zohoInvoice.getInvoiceId(), file, "invoices",
    -                                                                accessToken);
    -                                        }
    -                                }
    -
    -                                logger.info("│   ├──  Invoice Export successful.");
    -                                return zohoInvoice;
    -                        } else {
    -                                throw new PluginException(this.getClass().getName(), "Zoho API error",
    -                                                "Failed to export invoice to Zoho: unable to resolve response");
    -                        }
    -
    -                } catch (IOException | InterruptedException e) {
    -                        throw new PluginException(this.getClass().getName(), "Zoho API error",
    -                                        "Failed to export invoice to Zoho: " + e.getMessage(), e);
    -                }
    -
    -        }
    -
    -        /**
    -         * Erstellt einen neuen Contact in Zoho Books
    -         * 
    -         * @param companyName Der Firmenname für den neuen Contact
    -         * @return Der erstellte ZohoContact mit contact_id
    -         * @throws PluginException Wenn der API-Aufruf fehlschlägt
    -         */
    -        public ZohoContact createContact(String companyName, String accessToken) throws PluginException {
    -                logger.info("├── Zoho create contact '" + companyName + "'...");
    -                try {
    -                        // Contact-DTO vorbereiten
    -                        ZohoContact newContact = new ZohoContact();
    -                        newContact.setContactName(companyName); // Wir verwenden den Firmennamen auch als Contact Name
    -                        newContact.setCompanyName(companyName);
    -
    -                        // JSON serialisieren
    -                        String requestBody = jsonb.toJson(newContact);
    -                        logger.fine("│   ├──  " + requestBody);
    -                        String uri = ZOHO_API_BASE_URL + "contacts?organization_id="
    -                                        + zohoOAuthManager.getOrganization();
    -
    -                        HttpRequest request = HttpRequest.newBuilder()
    -                                        .uri(URI.create(uri))
    -                                        .header("Authorization", "Zoho-oauthtoken " + accessToken)
    -                                        .header("Content-Type", "application/json")
    -                                        .POST(HttpRequest.BodyPublishers.ofString(requestBody))
    -                                        .build();
    -
    -                        HttpResponse response = httpClient.send(
    -                                        request, HttpResponse.BodyHandlers.ofString());
    -                        logger.fine("├── Zoho Response:");
    -                        logger.fine("│   ├──  Status Code: " + response.statusCode());
    -                        logger.fine("│   ├──  Response Body: " + response.body());
    -                        if (response.statusCode() >= 300) {
    -                                throw new PluginException(this.getClass().getName(), "Zoho API error",
    -                                                "Failed to create invoice. Status code: " + response.statusCode() +
    -                                                                ", Response: " + response.body());
    -                        }
    -
    -                        // Erfolgreiche Erstellung
    -                        ZohoContactCreateResponse createResponse = jsonb.fromJson(response.body(),
    -                                        ZohoContactCreateResponse.class);
    -                        if (createResponse != null) {
    -                                logger.info("│   ├──  OK - contactID=" + createResponse.getContact().getContactId());
    -                                return createResponse.getContact();
    -                        } else {
    -                                throw new PluginException(this.getClass().getName(), "Zoho API error",
    -                                                "Response could not be resolved!");
    -                        }
    -
    -                } catch (IOException | InterruptedException e) {
    -                        throw new PluginException(this.getClass().getName(), "Zoho API error",
    -                                        "Failed to create contact: " + e.getMessage(), e);
    -                }
    -
    -        }
    -
    -        private ZohoInvoice convertToZohoInvoice(ItemCollection invoice, String contactID, String accessToken)
    -                        throws PluginException {
    -
    -                ZohoInvoice zohoInvoice = new ZohoInvoice();
    -                // Basisinformationen
    -                logger.fine("invoice.date=" + invoice.getItemValueDate("invoice.date"));
    -                logger.fine("invoice.duedate=" + invoice.getItemValueDate("invoice.duedate"));
    -
    -                // LocalDate.ofInstant(date.toInstant(), ZoneId.systemDefault())
    -                zohoInvoice.setDate(
    -                                LocalDate.ofInstant(invoice.getItemValueDate("invoice.date").toInstant(),
    -                                                ZoneId.systemDefault()));
    -
    -                zohoInvoice.setDueDate(
    -                                LocalDate.ofInstant(invoice.getItemValueDate("invoice.duedate").toInstant(),
    -                                                ZoneId.systemDefault()));
    -
    -                zohoInvoice.setTotal(invoice.getItemValueDouble("invoice.total"));
    -                // Kunden-ID (muss angepasst werden je nachdem wie du sie speicherst)
    -                zohoInvoice.setCustomerId(contactID);
    -                List positionen = InvoiceUtil.explodeChildList(invoice);
    -
    -                List lineItems = positionen.stream()
    -                                .map(item -> {
    -                                        ItemCollection itemCol = (ItemCollection) item;
    -                                        ZohoInvoiceItem lineItem = new ZohoInvoiceItem();
    -                                        lineItem.setName(itemCol.getItemValueString("datev.text"));
    -                                        lineItem.setDescription(itemCol.getItemValueString("billingtext"));
    -                                        lineItem.setRate(itemCol.getItemValueDouble("datev.umsatz"));
    -                                        lineItem.setQuantity(1);
    -                                        return lineItem;
    -                                })
    -                                .collect(Collectors.toList());
    -
    -                zohoInvoice.setLineItems(lineItems);
    +                logger.info("│   ├──  Invoice Export successful.");
                     return zohoInvoice;
    +            } else {
    +                throw new PluginException(this.getClass().getName(), "Zoho API error",
    +                        "Failed to export invoice to Zoho: unable to resolve response");
    +            }
    +
    +        } catch (IOException | InterruptedException e) {
    +            throw new PluginException(this.getClass().getName(), "Zoho API error",
    +                    "Failed to export invoice to Zoho: " + e.getMessage(), e);
             }
     
    -        /**
    -         * This method adds an attachment to an invoice
    -         * 
    -         * @param invoiceId   The Zoho invoice ID
    -         * @param file        The FileData object containing the attachment
    -         * @param uriPattern  The API endpoint pattern (e.g., "invoices")
    -         * @param accessToken The OAuth access token
    -         * @throws PluginException If the API call fails
    -         */
    -        public void attacheFileData(String invoiceId, FileData file, String uriPattern, String accessToken)
    -                        throws PluginException {
    -                String uri = ZOHO_API_BASE_URL + uriPattern + "/" + invoiceId + "/attachment" +
    -                                "?organization_id=" + zohoOAuthManager.getOrganization();
    +    }
     
    -                logger.info("│   ├──  add attachment '" + file.getName() + "'...");
    -                logger.fine("│   ├──  ...uri: " + uri + "...");
    +    /**
    +     * Erstellt einen neuen Contact in Zoho Books
    +     *
    +     * @param companyName Der Firmenname für den neuen Contact
    +     * @return Der erstellte ZohoContact mit contact_id
    +     * @throws PluginException Wenn der API-Aufruf fehlschlägt
    +     */
    +    public ZohoContact createContact(String companyName, String accessToken) throws PluginException {
    +        logger.info("├── Zoho create contact '" + companyName + "'...");
    +        try {
    +            // Contact-DTO vorbereiten
    +            ZohoContact newContact = new ZohoContact();
    +            newContact.setContactName(companyName); // Wir verwenden den Firmennamen auch als Contact Name
    +            newContact.setCompanyName(companyName);
     
    -                try {
    -                        // Generate a unique boundary
    -                        String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
    +            // JSON serialisieren
    +            String requestBody = jsonb.toJson(newContact);
    +            logger.fine("│   ├──  " + requestBody);
    +            String uri = zohoOAuthManager.getBaseURI() + "contacts?organization_id="
    +                    + zohoOAuthManager.getOrganization();
     
    -                        // Build multipart request body
    -                        byte[] header = ("--" + boundary + "\r\n" +
    -                                        "Content-Disposition: form-data; name=\"attachment\"; filename=\""
    -                                        + file.getName() + "\"\r\n" +
    -                                        "Content-Type: " + file.getContentType() + "\r\n\r\n")
    -                                        .getBytes(StandardCharsets.UTF_8);
    +            HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri))
    +                    .header("Authorization", "Zoho-oauthtoken " + accessToken)
    +                    .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(requestBody))
    +                    .build();
     
    -                        byte[] footer = ("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8);
    +            HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    +            logger.fine("├── Zoho Response:");
    +            logger.fine("│   ├──  Status Code: " + response.statusCode());
    +            logger.fine("│   ├──  Response Body: " + response.body());
    +            if (response.statusCode() >= 300) {
    +                throw new PluginException(this.getClass().getName(), "Zoho API error",
    +                        "Failed to create invoice. Status code: " + response.statusCode() + ", Response: "
    +                                + response.body());
    +            }
     
    -                        logger.fine("│   ├──  content size=" + file.getContent().length);
    -                        HttpRequest.BodyPublisher bodyPublisher = HttpRequest.BodyPublishers.ofByteArrays(
    -                                        Arrays.asList(header, file.getContent(), footer));
    +            // Erfolgreiche Erstellung
    +            ZohoContactCreateResponse createResponse = jsonb.fromJson(response.body(), ZohoContactCreateResponse.class);
    +            if (createResponse != null) {
    +                logger.info("│   ├──  OK - contactID=" + createResponse.getContact().getContactId());
    +                return createResponse.getContact();
    +            } else {
    +                throw new PluginException(this.getClass().getName(), "Zoho API error",
    +                        "Response could not be resolved!");
    +            }
     
    -                        // Create and send the request
    -                        HttpRequest request = HttpRequest.newBuilder()
    -                                        .uri(URI.create(uri))
    -                                        .header("Authorization", "Zoho-oauthtoken " + accessToken)
    -                                        .header("Content-Type", "multipart/form-data; boundary=" + boundary)
    -                                        .POST(bodyPublisher)
    -                                        .build();
    -
    -                        HttpResponse response = httpClient.send(
    -                                        request,
    -                                        HttpResponse.BodyHandlers.ofString());
    -
    -                        logger.fine("├── Zoho Response:");
    -                        logger.fine("│   ├──  Status Code: " + response.statusCode());
    -                        logger.fine("│   ├──  Response Body: " + response.body());
    -
    -                        if (response.statusCode() >= 300) {
    -                                throw new PluginException(this.getClass().getName(), "Zoho API error",
    -                                                "Failed to add attachment. Status code: " + response.statusCode() +
    -                                                                ", Response: " + response.body());
    -                        }
    -
    -                        logger.info("│   ├──  OK - Attachment added successfully");
    -
    -                } catch (IOException | InterruptedException e) {
    -                        throw new PluginException(this.getClass().getName(), "Zoho API error",
    -                                        "Failed to add attachment: " + e.getMessage(), e);
    -                }
    +        } catch (IOException | InterruptedException e) {
    +            throw new PluginException(this.getClass().getName(), "Zoho API error",
    +                    "Failed to create contact: " + e.getMessage(), e);
             }
     
    +    }
    +
    +    private ZohoInvoice convertToZohoInvoice(ItemCollection invoice, String contactID, String accessToken)
    +            throws PluginException {
    +
    +        ZohoInvoice zohoInvoice = new ZohoInvoice();
    +        // Basisinformationen
    +
    +        zohoInvoice.setInvoiceNumber(invoice.getItemValueString("invoice.number"));
    +        zohoInvoice.setDate(
    +                LocalDate.ofInstant(invoice.getItemValueDate("invoice.date").toInstant(), ZoneId.systemDefault()));
    +
    +        zohoInvoice.setDueDate(
    +                LocalDate.ofInstant(invoice.getItemValueDate("invoice.duedate").toInstant(), ZoneId.systemDefault()));
    +
    +        zohoInvoice.setTotal(invoice.getItemValueDouble("invoice.total"));
    +        // Kunden-ID (muss angepasst werden je nachdem wie du sie speicherst)
    +        zohoInvoice.setCustomerId(contactID);
    +        List positionen = InvoiceUtil.explodeChildList(invoice);
    +
    +        List lineItems = positionen.stream().map(item -> {
    +            ItemCollection itemCol = (ItemCollection) item;
    +            ZohoInvoiceItem lineItem = new ZohoInvoiceItem();
    +            lineItem.setName(itemCol.getItemValueString("datev.text"));
    +            lineItem.setDescription(itemCol.getItemValueString("billingtext"));
    +            lineItem.setRate(itemCol.getItemValueDouble("datev.umsatz"));
    +            lineItem.setQuantity(1);
    +            return lineItem;
    +        }).collect(Collectors.toList());
    +
    +        zohoInvoice.setLineItems(lineItems);
    +        return zohoInvoice;
    +    }
    +
    +    /**
    +     * This method adds an attachment to an invoice
    +     *
    +     * @param invoiceId   The Zoho invoice ID
    +     * @param file        The FileData object containing the attachment
    +     * @param uriPattern  The API endpoint pattern (e.g., "invoices")
    +     * @param accessToken The OAuth access token
    +     * @throws PluginException If the API call fails
    +     */
    +    public void attacheFileData(String invoiceId, FileData file, String uriPattern, String accessToken)
    +            throws PluginException {
    +        String uri = zohoOAuthManager.getBaseURI() + uriPattern + "/" + //
    +                invoiceId + //
    +                "/attachment" + //
    +                "?organization_id=" + zohoOAuthManager.getOrganization();
    +
    +        logger.info("│   ├──  add attachment '" + file.getName() + "'...");
    +        logger.fine("│   ├──  ...uri: " + uri + "...");
    +
    +        try {
    +            // Generate a unique boundary
    +            String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
    +
    +            // Build multipart request body
    +            byte[] header = ("--" + boundary + "\r\n"
    +                    + "Content-Disposition: form-data; name=\"attachment\"; filename=\"" + file.getName() + "\"\r\n"
    +                    + "Content-Type: " + file.getContentType() + "\r\n\r\n").getBytes(StandardCharsets.UTF_8);
    +
    +            byte[] footer = ("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8);
    +
    +            logger.fine("│   ├──  content size=" + file.getContent().length);
    +            HttpRequest.BodyPublisher bodyPublisher = HttpRequest.BodyPublishers
    +                    .ofByteArrays(Arrays.asList(header, file.getContent(), footer));
    +
    +            // Create and send the request
    +            HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri))
    +                    .header("Authorization", "Zoho-oauthtoken " + accessToken)
    +                    .header("Content-Type", "multipart/form-data; boundary=" + boundary).POST(bodyPublisher).build();
    +
    +            HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    +
    +            logger.fine("├── Zoho Response:");
    +            logger.fine("│   ├──  Status Code: " + response.statusCode());
    +            logger.fine("│   ├──  Response Body: " + response.body());
    +
    +            if (response.statusCode() >= 300) {
    +                throw new PluginException(this.getClass().getName(), "Zoho API error",
    +                        "Failed to add attachment. Status code: " + response.statusCode() + ", Response: "
    +                                + response.body());
    +            }
    +
    +            logger.info("│   ├──  OK - Attachment added successfully");
    +
    +        } catch (IOException | InterruptedException e) {
    +            throw new PluginException(this.getClass().getName(), "Zoho API error",
    +                    "Failed to add attachment: " + e.getMessage(), e);
    +        }
    +    }
    +
     }
    diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoController.java
    index 6f7675d..292012a 100644
    --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoController.java
    +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoController.java
    @@ -2,26 +2,26 @@ package com.alexanderlogistics.zoho;
     
     /*******************************************************************************
      *  Imixs Workflow Technology
    - *  Copyright (C) 2003, 2008 Imixs Software Solutions GmbH,  
    + *  Copyright (C) 2003, 2008 Imixs Software Solutions GmbH,
      *  http://www.imixs.com
    - *  
    - *  This program is free software; you can redistribute it and/or 
    - *  modify it under the terms of the GNU General Public License 
    - *  as published by the Free Software Foundation; either version 2 
    + *
    + *  This program is free software; you can redistribute it and/or
    + *  modify it under the terms of the GNU General Public License
    + *  as published by the Free Software Foundation; either version 2
      *  of the License, or (at your option) any later version.
    - *  
    - *  This program is distributed in the hope that it will be useful, 
    - *  but WITHOUT ANY WARRANTY; without even the implied warranty of 
    - *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
    + *
    + *  This program is distributed in the hope that it will be useful,
    + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
    + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
      *  General Public License for more details.
    - *  
    + *
      *  You can receive a copy of the GNU General Public
      *  License at http://www.gnu.org/licenses/gpl.html
    - *  
    - *  Contributors:  
    + *
    + *  Contributors:
      *  	Imixs Software Solutions GmbH - initial API and implementation
      *  	Ralph Soika
    - *  
    + *
      *******************************************************************************/
     
     import java.util.logging.Logger;
    @@ -35,54 +35,54 @@ import jakarta.inject.Named;
     
     /**
      * Der ZohoController ist für das Admin Interface um Access Tokens zu erzeugen.
    - * 
    + *
      * @see admin/zoho.xhml
    - * 
    + *
      * @author rsoika
    - * 
    + *
      */
     @Named("zohoController")
     @ApplicationScoped
     public class ZohoController extends ConfigController {
     
    -	public static final String ZOHO_CONFIGURATION = "ZOHO_CONFIGURATION";
    +    public static final String ZOHO_CONFIGURATION = "ZOHO_CONFIGURATION";
     
    -	private static final long serialVersionUID = 1L;
    -	private static Logger logger = Logger.getLogger(ZohoController.class.getName());
    +    private static final long serialVersionUID = 1L;
    +    private static Logger logger = Logger.getLogger(ZohoController.class.getName());
     
    -	@Inject
    -	ZohoOAuthManager zohoOAuthManager;
    +    @Inject
    +    ZohoOAuthManager zohoOAuthManager;
     
    -	public ZohoController() {
    -		super();
    -		setName(ZOHO_CONFIGURATION);
    -	}
    +    public ZohoController() {
    +        super();
    +        setName(ZOHO_CONFIGURATION);
    +    }
     
    -	/**
    -	 * Ruft zohoOAuhtManager.updateToken auf
    -	 */
    -	public void updateAccessToken() {
    -		//
    -		try {
    -			zohoOAuthManager.updateTokens(this.getWorkitem().getItemValueString("zoho.code"));
    -		} catch (PluginException e) {
    -			String message = "Failed to generate token: " + e.getMessage();
    -			logger.warning(message);
    -			this.getWorkitem().setItemValue("message", message);
    -		}
    -	}
    +    /**
    +     * Ruft zohoOAuhtManager.updateToken auf
    +     */
    +    public void updateAccessToken() {
    +        //
    +        try {
    +            zohoOAuthManager.updateTokens(this.getWorkitem().getItemValueString("zoho.code"));
    +        } catch (PluginException e) {
    +            String message = "Failed to generate token: " + e.getMessage();
    +            logger.warning(message);
    +            this.getWorkitem().setItemValue("message", message);
    +        }
    +    }
     
    -	/**
    -	 * Ruft zohoOAuhtManager.refreshToken auf
    -	 */
    -	public void refreshAccessToken() {
    -		//
    -		try {
    -			zohoOAuthManager.refreshAccessToken();
    -		} catch (PluginException e) {
    -			String message = "Failed to refresh token: " + e.getMessage();
    -			logger.warning(message);
    -			this.getWorkitem().setItemValue("message", message);
    -		}
    -	}
    +    /**
    +     * Ruft zohoOAuhtManager.refreshToken auf
    +     */
    +    public void refreshAccessToken() {
    +        //
    +        try {
    +            zohoOAuthManager.refreshAccessToken();
    +        } catch (PluginException e) {
    +            String message = "Failed to refresh token: " + e.getMessage();
    +            logger.warning(message);
    +            this.getWorkitem().setItemValue("message", message);
    +        }
    +    }
     }
    diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoExportAdapter.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoExportAdapter.java
    index 0909a53..9394df1 100644
    --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoExportAdapter.java
    +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoExportAdapter.java
    @@ -18,7 +18,7 @@ import jakarta.inject.Inject;
     
     /**
      * This adapter exports the invoice data to zoho
    - * 
    + *
      * @version 1.0
      * @author rsoika
      */
    @@ -44,7 +44,7 @@ public class ZohoExportAdapter implements SignalAdapter {
     
         /**
          * This method calls the zoho api
    -     * 
    +     *
          * @throws PluginException
          */
         @Override
    diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoOAuthCallbackService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoOAuthCallbackService.java
    index 6c2ffaa..8afb329 100644
    --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoOAuthCallbackService.java
    +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoOAuthCallbackService.java
    @@ -1,27 +1,27 @@
    -/*  
    - *  Imixs-Workflow 
    - *  
    - *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
    +/*
    + *  Imixs-Workflow
    + *
    + *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,
      *  http://www.imixs.com
    - *  
    - *  This program is free software; you can redistribute it and/or 
    - *  modify it under the terms of the GNU General Public License 
    - *  as published by the Free Software Foundation; either version 2 
    + *
    + *  This program is free software; you can redistribute it and/or
    + *  modify it under the terms of the GNU General Public License
    + *  as published by the Free Software Foundation; either version 2
      *  of the License, or (at your option) any later version.
    - *  
    - *  This program is distributed in the hope that it will be useful, 
    - *  but WITHOUT ANY WARRANTY; without even the implied warranty of 
    - *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
    + *
    + *  This program is distributed in the hope that it will be useful,
    + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
    + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
      *  General Public License for more details.
    - *  
    + *
      *  You can receive a copy of the GNU General Public
      *  License at http://www.gnu.org/licenses/gpl.html
    - *  
    - *  Project: 
    + *
    + *  Project:
      *      https://www.imixs.org
      *      https://github.com/imixs/imixs-workflow
    - *  
    - *  Contributors:  
    + *
    + *  Contributors:
      *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
      */
    @@ -45,20 +45,20 @@ import jakarta.ws.rs.core.MediaType;
     /**
      * Dieser API Endpoint dient dazu über zoho einen sogenannten Grant Token zu
      * erhalten.
    - * 
    + *
      * https://www.zoho.com/books/api/v3/oauth/
    - * 
    + *
      * Der Service wird aktuell nicht benötigt, das wir sogenannte Zoho Self-Clients
      * verwenden!
    - * 
    + *
      * Der Service stellt im Grunde nur eine Callback URL für einen normalen Zoho
    - * Client ein. Also wenn jemand einen solchen Token über den Browser
    - * anfordert. Der URL muss dann odrt als CallBack URL angegeben sein!
    - * 
    + * Client ein. Also wenn jemand einen solchen Token über den Browser anfordert.
    + * Der URL muss dann odrt als CallBack URL angegeben sein!
    + *
      * Beispiel:
    - * 
    + *
      * https://accounts.zoho.com/oauth/v2/auth?scope=ZohoBooks.invoices.CREATE,ZohoBooks.invoices.READ,ZohoBooks.invoices.UPDATE,ZohoBooks.invoices.DELETE&client_id=1000.NLTM2KUEHI696MSLGEIANATVGGX7IN&state=testing&response_type=code&redirect_uri=https://alexander-logistics-dwc.office-workflow.de/api/zoho/grant
    - * 
    + *
      * @version 1.0
      * @author rsoika
      *
    @@ -85,15 +85,14 @@ public class ZohoOAuthCallbackService {
     
         /**
          * Receive Grant Code.
    -     * 
    +     *
          * The expected query params look like this:
    -     * 
    +     *
          * state=testing
          * code=1000.c27802b99c007ad8db93a5a7291cb15a.8cb60b6a51df13ea627ef3ac7c2e4791
    -     * location=eu
    -     * accounts-server=https%3A%2F%2Faccounts.zoho.eu
    -     * 
    -     * 
    +     * location=eu accounts-server=https%3A%2F%2Faccounts.zoho.eu
    +     *
    +     *
          * @param workflowgroup
          * @param task
          * @return
    @@ -102,11 +101,8 @@ public class ZohoOAuthCallbackService {
         @GET
         @Path("/grant")
         @Produces({ MediaType.TEXT_HTML })
    -    public String getGrantToken(
    -            @QueryParam("state") String state,
    -            @QueryParam("code") String code,
    -            @QueryParam("location") String location,
    -            @QueryParam("accounts-server") String accountsServer)
    +    public String getGrantToken(@QueryParam("state") String state, @QueryParam("code") String code,
    +            @QueryParam("location") String location, @QueryParam("accounts-server") String accountsServer)
                 throws QueryException {
     
             logger.info("├── Receive Grant Token from Zoho");
    diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoOAuthManager.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoOAuthManager.java
    index f888173..666c5fe 100644
    --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoOAuthManager.java
    +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/ZohoOAuthManager.java
    @@ -27,8 +27,8 @@ import jakarta.json.bind.JsonbBuilder;
     /**
      * Der ZohoOAuthManager stellt Methoden bereit um einen Access Token zu
      * erstellen.
    - * 
    - * 
    + *
    + *
      */
     @Singleton
     @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
    @@ -47,9 +47,7 @@ public class ZohoOAuthManager {
     
         @PostConstruct
         void init() {
    -        this.httpClient = HttpClient.newBuilder()
    -                .connectTimeout(Duration.ofSeconds(10))
    -                .build();
    +        this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
             this.jsonb = JsonbBuilder.create();
         }
     
    @@ -57,7 +55,7 @@ public class ZohoOAuthManager {
          * Gibt den aktuellen Access Token zurück. Falls dieser expired ist wird
          * automatisch ein refresh durchgeführt. Somit liefert die Methode immer einen
          * gültigen token.
    -     * 
    +     *
          * @return
          * @throws PluginException
          */
    @@ -87,14 +85,26 @@ public class ZohoOAuthManager {
             return null;
         }
     
    +    public String getBaseURI() throws PluginException {
    +        String uri = "";
    +        ItemCollection zohoConfig = configService.loadConfiguration(ZohoController.ZOHO_CONFIGURATION, false);
    +        if (zohoConfig != null) {
    +            uri = zohoConfig.getItemValueString("zoho.uri");
    +            if (!uri.endsWith("/")) {
    +                uri = uri + "/";
    +            }
    +        }
    +        return uri;
    +    }
    +
         /**
          * Step 3: Generate Access and Refresh Token
    -     * 
    +     *
          * After getting code from the GrantService we make a POST request
    -     * 
    +     *
          * See description: https://www.zoho.com/books/api/v3/oauth/
    -     * 
    -     * 
    +     *
    +     *
          * @param code
          * @throws PluginException
          */
    @@ -107,24 +117,21 @@ public class ZohoOAuthManager {
     
                 String clientID = zohoConfig.getItemValueString("zoho.clientid");
                 String clientSecret = zohoConfig.getItemValueString("zoho.clientsecret");
    -            String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" +
    -                    "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8) +
    -                    "&client_id=" + URLEncoder.encode(clientID, StandardCharsets.UTF_8) +
    -                    "&client_secret=" + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8) +
    -                    "&redirect_uri=" + URLEncoder.encode(redirectURI, StandardCharsets.UTF_8) +
    -                    "&grant_type=authorization_code";
    +            String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" + "?code="
    +                    + URLEncoder.encode(code, StandardCharsets.UTF_8) + "&client_id="
    +                    + URLEncoder.encode(clientID, StandardCharsets.UTF_8) + "&client_secret="
    +                    + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8) + "&redirect_uri="
    +                    + URLEncoder.encode(redirectURI, StandardCharsets.UTF_8) + "&grant_type=authorization_code";
     
                 logger.fine("│   ├──  Request URI=" + requestUrl);
     
    -            HttpRequest request = HttpRequest.newBuilder()
    -                    .uri(URI.create(requestUrl))
    +            HttpRequest request = HttpRequest.newBuilder().uri(URI.create(requestUrl))
                         .header("Content-Type", "application/x-www-form-urlencoded")
                         .POST(HttpRequest.BodyPublishers.noBody()) // Wichtig: Kein Body!
                         .build();
     
                 HttpResponse response;
    -            response = httpClient.send(request,
    -                    HttpResponse.BodyHandlers.ofString());
    +            response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
     
                 logger.fine("│   ├──  Response statusCode=" + response.statusCode());
                 if (response.statusCode() == 200) {
    @@ -132,29 +139,26 @@ public class ZohoOAuthManager {
                     logger.fine("│   ├──  Response Body=" + response.body());
     
                     if (response.body().contains("error")) {
    -                    throw new PluginException(ZohoOAuthManager.class.getSimpleName(),
    -                            ZohoOAuthManager.ERROR_CONFIG,
    +                    throw new PluginException(ZohoOAuthManager.class.getSimpleName(), ZohoOAuthManager.ERROR_CONFIG,
                                 "Ungültiger Access Code!");
                     }
     
    -                TokenResponse tokenResponse = jsonb.fromJson(response.body(),
    -                        TokenResponse.class);
    +                TokenResponse tokenResponse = jsonb.fromJson(response.body(), TokenResponse.class);
                     saveTokenData(zohoConfig, tokenResponse);
     
                 } else {
    -                throw new RuntimeException("Failed to get tokens: " +
    -                        response.statusCode() + " - " + response.body());
    +                throw new RuntimeException("Failed to get tokens: " + response.statusCode() + " - " + response.body());
                 }
     
             } catch (IOException | InterruptedException e) {
    -            throw new PluginException(ZohoOAuthManager.class.getSimpleName(),
    -                    ZohoOAuthManager.ERROR_CONFIG, "Unable to resolve access tokens: " + e.getMessage());
    +            throw new PluginException(ZohoOAuthManager.class.getSimpleName(), ZohoOAuthManager.ERROR_CONFIG,
    +                    "Unable to resolve access tokens: " + e.getMessage());
             }
         }
     
         /**
          * This method updates the Access Token based on the current Refresh Token
    -     * 
    +     *
          * @throws IOException
          * @throws PluginException
          */
    @@ -168,26 +172,21 @@ public class ZohoOAuthManager {
     
             String refreshToken = zohoConfig.getItemValueString("zoho.refreshToken");
             try {
    -            String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" +
    -                    "?refresh_token=" + URLEncoder.encode(refreshToken, StandardCharsets.UTF_8) +
    -                    "&client_id=" + URLEncoder.encode(clientID, StandardCharsets.UTF_8) +
    -                    "&client_secret=" + URLEncoder.encode(clientSecret,
    -                            StandardCharsets.UTF_8)
    -                    +
    -                    "&grant_type=refresh_token";
    +            String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" + "?refresh_token="
    +                    + URLEncoder.encode(refreshToken, StandardCharsets.UTF_8) + "&client_id="
    +                    + URLEncoder.encode(clientID, StandardCharsets.UTF_8) + "&client_secret="
    +                    + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8) + "&grant_type=refresh_token";
     
                 logger.fine("│ ├── Request URI=" + requestUrl);
     
    -            HttpRequest request = HttpRequest.newBuilder()
    -                    .uri(URI.create(requestUrl))
    +            HttpRequest request = HttpRequest.newBuilder().uri(URI.create(requestUrl))
                         .header("Content-Type", "application/x-www-form-urlencoded")
                         .POST(HttpRequest.BodyPublishers.noBody()) // Wichtig: Kein Body!
                         .build();
     
                 HttpResponse response;
     
    -            response = httpClient.send(request,
    -                    HttpResponse.BodyHandlers.ofString());
    +            response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
     
                 logger.fine("│ ├── Response statusCode=" + response.statusCode());
                 if (response.statusCode() == 200) {
    @@ -195,25 +194,23 @@ public class ZohoOAuthManager {
                     // 1. Response-Logging für Debugging
                     logger.fine("│ ├── Response Body=" + response.body());
     
    -                TokenResponse tokenResponse = jsonb.fromJson(response.body(),
    -                        TokenResponse.class);
    +                TokenResponse tokenResponse = jsonb.fromJson(response.body(), TokenResponse.class);
                     saveTokenData(zohoConfig, tokenResponse);
                     return zohoConfig.getItemValueString("zoho.accessToken");
     
                 } else {
    -                throw new RuntimeException("Failed to get tokens: " +
    -                        response.statusCode() + " - " + response.body());
    +                throw new RuntimeException("Failed to get tokens: " + response.statusCode() + " - " + response.body());
                 }
     
             } catch (IOException | InterruptedException e) {
    -            throw new PluginException(ZohoOAuthManager.class.getSimpleName(),
    -                    ZohoOAuthManager.ERROR_CONFIG, "Unable to refresh access tokens: " + e.getMessage());
    +            throw new PluginException(ZohoOAuthManager.class.getSimpleName(), ZohoOAuthManager.ERROR_CONFIG,
    +                    "Unable to refresh access tokens: " + e.getMessage());
             }
         }
     
         /**
          * Helper method to update the config itemcolleciton based on a tokenResponse
    -     * 
    +     *
          * @param zohoConfig
          * @param tokenResponse
          */
    diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/dto/ZohoInvoiceItem.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/dto/ZohoInvoiceItem.java
    index 8971bd5..5fe608a 100644
    --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/dto/ZohoInvoiceItem.java
    +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/zoho/dto/ZohoInvoiceItem.java
    @@ -1,42 +1,42 @@
     package com.alexanderlogistics.zoho.dto;
     
     public class ZohoInvoiceItem {
    -        private String name;
    -        private String description;
    -        private double rate;
    -        private int quantity;
    +    private String name;
    +    private String description;
    +    private double rate;
    +    private int quantity;
     
    -        // Getter und Setter
    +    // Getter und Setter
     
    -        public String getName() {
    -                return name;
    -        }
    +    public String getName() {
    +        return name;
    +    }
     
    -        public void setName(String name) {
    -                this.name = name;
    -        }
    +    public void setName(String name) {
    +        this.name = name;
    +    }
     
    -        public String getDescription() {
    -                return description;
    -        }
    +    public String getDescription() {
    +        return description;
    +    }
     
    -        public void setDescription(String description) {
    -                this.description = description;
    -        }
    +    public void setDescription(String description) {
    +        this.description = description;
    +    }
     
    -        public double getRate() {
    -                return rate;
    -        }
    +    public double getRate() {
    +        return rate;
    +    }
     
    -        public void setRate(double rate) {
    -                this.rate = rate;
    -        }
    +    public void setRate(double rate) {
    +        this.rate = rate;
    +    }
     
    -        public int getQuantity() {
    -                return quantity;
    -        }
    +    public int getQuantity() {
    +        return quantity;
    +    }
     
    -        public void setQuantity(int quantity) {
    -                this.quantity = quantity;
    -        }
    +    public void setQuantity(int quantity) {
    +        this.quantity = quantity;
    +    }
     }
    diff --git a/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/forms/AnalyticController.java b/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/forms/AnalyticController.java
    index 923a98c..3952bd6 100644
    --- a/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/forms/AnalyticController.java
    +++ b/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/forms/AnalyticController.java
    @@ -24,8 +24,8 @@ import jakarta.inject.Named;
      * The controller implements a caching mechanism to avoid repeated calls for new
      * analytic values. If the analytic value is already stored in the current
      * workitem, no new value will be fired.
    - * 
    - * 
    + *
    + *
      * @author rsoika
      *
      */
    @@ -33,150 +33,149 @@ import jakarta.inject.Named;
     @ConversationScoped
     public class AnalyticController implements Serializable {
     
    -	private static final long serialVersionUID = 1L;
    -	private static Logger logger = Logger.getLogger(AnalyticController.class.getName());
    +    private static final long serialVersionUID = 1L;
    +    private static Logger logger = Logger.getLogger(AnalyticController.class.getName());
     
    -	@Inject
    -	protected Event analyticEvents;
    +    @Inject
    +    protected Event analyticEvents;
     
    -	@Inject
    -	protected WorkflowController workflowController;
    +    @Inject
    +    protected WorkflowController workflowController;
     
    -	/**
    -	 * Returns a analytic value as a String for a given key.
    -	 * 
    -	 * @param key
    -	 * @return
    -	 */
    -	public String getAsString(String key) {
    -		ItemCollection analyticData = computeValue(key);
    -		return analyticData.getItemValueString("value");
    -	}
    +    /**
    +     * Returns a analytic value as a String for a given key.
    +     *
    +     * @param key
    +     * @return
    +     */
    +    public String getAsString(String key) {
    +        ItemCollection analyticData = computeValue(key);
    +        return analyticData.getItemValueString("value");
    +    }
     
    -	/**
    -	 * Returns a analytic value as a Json String for a given key.
    -	 * 
    -	 * @param key
    -	 * @return
    -	 */
    -	public String getAsJson(String key) {
    -		ItemCollection analyticData = computeValue(key);
    -		String jsonval = analyticData.getItemValueString("value");
    -		if (jsonval == null || jsonval.isEmpty()) {
    -			return "null";
    -		} else {
    -			return jsonval;
    -		}
    -	}
    +    /**
    +     * Returns a analytic value as a Json String for a given key.
    +     *
    +     * @param key
    +     * @return
    +     */
    +    public String getAsJson(String key) {
    +        ItemCollection analyticData = computeValue(key);
    +        String jsonval = analyticData.getItemValueString("value");
    +        if (jsonval == null || jsonval.isEmpty()) {
    +            return "null";
    +        } else {
    +            return jsonval;
    +        }
    +    }
     
    -	/**
    -	 * Returns a analytic value as a Double for a given key.
    -	 * 
    -	 * @param key
    -	 * @return
    -	 */
    -	public double getAsDouble(String key) {
    -		ItemCollection analyticData = computeValue(key);
    -		return analyticData.getItemValueDouble("value");
    -	}
    +    /**
    +     * Returns a analytic value as a Double for a given key.
    +     *
    +     * @param key
    +     * @return
    +     */
    +    public double getAsDouble(String key) {
    +        ItemCollection analyticData = computeValue(key);
    +        return analyticData.getItemValueDouble("value");
    +    }
     
    -	/**
    -	 * Returns the analytic label for a given key
    -	 * 
    -	 * @param key
    -	 * @return
    -	 */
    -	public String getLabel(String key) {
    -		ItemCollection analyticData = computeValue(key);
    -		return analyticData.getItemValueString("label");
    -	}
    +    /**
    +     * Returns the analytic label for a given key
    +     *
    +     * @param key
    +     * @return
    +     */
    +    public String getLabel(String key) {
    +        ItemCollection analyticData = computeValue(key);
    +        return analyticData.getItemValueString("label");
    +    }
     
    -	/**
    -	 * Returns the analytic optional link for a given key
    -	 * 
    -	 * @param key
    -	 * @return
    -	 */
    -	public String getLink(String key) {
    -		ItemCollection analyticData = computeValue(key);
    -		return analyticData.getItemValueString("link");
    -	}
    +    /**
    +     * Returns the analytic optional link for a given key
    +     *
    +     * @param key
    +     * @return
    +     */
    +    public String getLink(String key) {
    +        ItemCollection analyticData = computeValue(key);
    +        return analyticData.getItemValueString("link");
    +    }
     
    -	/**
    -	 * Returns the analytic description for a given key
    -	 * 
    -	 * @param key
    -	 * @return
    -	 */
    -	public String getDescription(String key) {
    -		ItemCollection analyticData = computeValue(key);
    -		return analyticData.getItemValueString("description");
    -	}
    +    /**
    +     * Returns the analytic description for a given key
    +     *
    +     * @param key
    +     * @return
    +     */
    +    public String getDescription(String key) {
    +        ItemCollection analyticData = computeValue(key);
    +        return analyticData.getItemValueString("description");
    +    }
     
    -	/**
    -	 * Computes an analytic value. The method cache the value in the
    -	 * item key.
    -	 * 
    -	 * An observer controller is responsible to cache or reset the cached values if
    -	 * needed.
    -	 * 
    -	 * @param key
    -	 * @return
    -	 */
    -	protected ItemCollection computeValue(String key) {
    -		if (workflowController.getWorkitem() != null) {
    -			logger.fine("fire analytic event for key '" + key + "'");
    -			// Fire the Analytics Event for this key
    -			AnalyticEvent event = new AnalyticEvent(key, workflowController.getWorkitem());
    -			if (analyticEvents != null) {
    -				analyticEvents.fire(event);
    -				if (event.getValue() != null) {
    -					ItemCollection details = new ItemCollection();
    -					details.setItemValue("value", event.getValue());
    -					details.setItemValue("label", event.getLabel());
    -					details.setItemValue("description", event.getDescription());
    -					details.setItemValue("link", event.getLink());
    -					// cache result
    -					implodeDetails(key, details);
    -				}
    -			}
    -		}
    +    /**
    +     * Computes an analytic value. The method cache the value in the item key.
    +     *
    +     * An observer controller is responsible to cache or reset the cached values if
    +     * needed.
    +     *
    +     * @param key
    +     * @return
    +     */
    +    protected ItemCollection computeValue(String key) {
    +        if (workflowController.getWorkitem() != null) {
    +            logger.fine("fire analytic event for key '" + key + "'");
    +            // Fire the Analytics Event for this key
    +            AnalyticEvent event = new AnalyticEvent(key, workflowController.getWorkitem());
    +            if (analyticEvents != null) {
    +                analyticEvents.fire(event);
    +                if (event.getValue() != null) {
    +                    ItemCollection details = new ItemCollection();
    +                    details.setItemValue("value", event.getValue());
    +                    details.setItemValue("label", event.getLabel());
    +                    details.setItemValue("description", event.getDescription());
    +                    details.setItemValue("link", event.getLink());
    +                    // cache result
    +                    implodeDetails(key, details);
    +                }
    +            }
    +        }
     
    -		// analytic value is now already cached!
    -		return explodeDetails(key);
    -	}
    +        // analytic value is now already cached!
    +        return explodeDetails(key);
    +    }
     
    -	/**
    -	 * Convert the List of ItemCollections back into a List of Map elements
    -	 * 
    -	 * @param workitem
    -	 */
    -	@SuppressWarnings({ "rawtypes" })
    -	private void implodeDetails(String key, ItemCollection details) {
    -		// convert the child ItemCollection elements into a List of Map
    -		List detailsList = new ArrayList();
    -		detailsList.add(details.getAllItems());
    -		workflowController.getWorkitem().replaceItemValue(key, detailsList);
    -	}
    +    /**
    +     * Convert the List of ItemCollections back into a List of Map elements
    +     *
    +     * @param workitem
    +     */
    +    @SuppressWarnings({ "rawtypes" })
    +    private void implodeDetails(String key, ItemCollection details) {
    +        // convert the child ItemCollection elements into a List of Map
    +        List detailsList = new ArrayList();
    +        detailsList.add(details.getAllItems());
    +        workflowController.getWorkitem().replaceItemValue(key, detailsList);
    +    }
     
    -	/**
    -	 * converts the Map List of a workitem into a List of ItemCollectons
    -	 */
    -	@SuppressWarnings({ "rawtypes", "unchecked" })
    -	private ItemCollection explodeDetails(String key) {
    -		// convert current list of childItems into ItemCollection elements
    -		List mapOrderItems = workflowController.getWorkitem().getItemValue(key);
    -		if (mapOrderItems != null && mapOrderItems.size() > 0) {
    -			ItemCollection itemCol = new ItemCollection((Map) mapOrderItems.get(0));
    -			return itemCol;
    -		}
    -		// return empty collection
    -		ItemCollection dummy = new ItemCollection();
    -		dummy.setItemValue("value", "");
    -		dummy.setItemValue("label", "");
    -		dummy.setItemValue("description", "No data available");
    +    /**
    +     * converts the Map List of a workitem into a List of ItemCollectons
    +     */
    +    @SuppressWarnings({ "rawtypes", "unchecked" })
    +    private ItemCollection explodeDetails(String key) {
    +        // convert current list of childItems into ItemCollection elements
    +        List mapOrderItems = workflowController.getWorkitem().getItemValue(key);
    +        if (mapOrderItems != null && mapOrderItems.size() > 0) {
    +            ItemCollection itemCol = new ItemCollection((Map) mapOrderItems.get(0));
    +            return itemCol;
    +        }
    +        // return empty collection
    +        ItemCollection dummy = new ItemCollection();
    +        dummy.setItemValue("value", "");
    +        dummy.setItemValue("label", "");
    +        dummy.setItemValue("description", "No data available");
     
    -		return dummy;
    -	}
    +        return dummy;
    +    }
     
     }
    diff --git a/office-alexander-logistics-app/src/main/webapp/pages/admin/excel_export_rechnungsausgang.xhtml b/office-alexander-logistics-app/src/main/webapp/pages/admin/excel_export_rechnungsausgang.xhtml
    index 1508cae..c55d6ab 100644
    --- a/office-alexander-logistics-app/src/main/webapp/pages/admin/excel_export_rechnungsausgang.xhtml
    +++ b/office-alexander-logistics-app/src/main/webapp/pages/admin/excel_export_rechnungsausgang.xhtml
    @@ -10,12 +10,12 @@
     	
     		
     	
    @@ -32,7 +32,8 @@
     					
     						
    -

    Der folgende Report exportiert Ausgangsrechnungen, in eine Excel Datei. Der Export ist auf maximal 3000 Einträge pro Tabelle beschränkt +

    Der folgende Report exportiert Ausgangsrechnungen, in eine Excel Datei. Der + Export ist auf maximal 3000 Einträge pro Tabelle beschränkt

    @@ -64,13 +65,15 @@
    Abteilung:
    -
    - + - - + - + @@ -82,27 +85,26 @@ Debitor:
    - + - +
    - +
    Positionsnummer:
    - +
    - +
    diff --git a/office-alexander-logistics-app/src/main/webapp/pages/admin/zoho.xhtml b/office-alexander-logistics-app/src/main/webapp/pages/admin/zoho.xhtml index 63b5c9d..486afd4 100644 --- a/office-alexander-logistics-app/src/main/webapp/pages/admin/zoho.xhtml +++ b/office-alexander-logistics-app/src/main/webapp/pages/admin/zoho.xhtml @@ -19,13 +19,13 @@ /*]]>*/ - +

    ZOHO - API

    - +
    @@ -41,8 +41,27 @@
    +
    -
    Client ID
    +
    API Base URL *
    +
    + + +
    +
    + + +
    +
    Organization *
    +
    + + +
    +
    +
    +
    Client ID *
    @@ -51,27 +70,20 @@
    -
    Client Secret
    +
    Client Secret *
    -
    -
    Organization
    -
    - - -
    -
    +
    -
    Access Code
    +
    Access Code *
    diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestFTP.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestFTP.java index 8369d88..6e7a552 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestFTP.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestFTP.java @@ -13,46 +13,46 @@ import org.junit.jupiter.api.Test; /** * This test generates random test data - * + * * @author rsoika - * + * */ public class TestFTP { - final static String FILE = "/DTVF_SKBeschrift_21010.csv"; + final static String FILE = "/DTVF_SKBeschrift_21010.csv"; - private final static Logger logger = Logger.getLogger(TestFTP.class.getName()); + private final static Logger logger = Logger.getLogger(TestFTP.class.getName()); - @BeforeEach - public void setUp() throws Exception { + @BeforeEach + public void setUp() throws Exception { - } + } - /** - * Test Kreditoren import - */ - @Disabled - @Test - public void testPutFileToFTP() { + /** + * Test Kreditoren import + */ + @Disabled + @Test + public void testPutFileToFTP() { - FTPConnector ftpc = new FTPConnector(); + FTPConnector ftpc = new FTPConnector(); - ftpc.ftpServer = Optional.ofNullable("u248962.your-storagebox.de"); - ftpc.ftpPort = Optional.ofNullable(21); - ftpc.ftpUser = Optional.ofNullable("u248962-sub2"); - ftpc.ftpPassword = Optional.ofNullable("aXa61n9Un3jDNQlL"); - ftpc.ftpPath = Optional.ofNullable("/"); + ftpc.ftpServer = Optional.ofNullable("u248962.your-storagebox.de"); + ftpc.ftpPort = Optional.ofNullable(21); + ftpc.ftpUser = Optional.ofNullable("u248962-sub2"); + ftpc.ftpPassword = Optional.ofNullable("aXa61n9Un3jDNQlL"); + ftpc.ftpPath = Optional.ofNullable("/"); - FileData fileData = new FileData("test.txt", "some data...".getBytes(), "text", null); - try { - ftpc.put(fileData); - logger.info("test finished..."); - } catch (PluginException e) { + FileData fileData = new FileData("test.txt", "some data...".getBytes(), "text", null); + try { + ftpc.put(fileData); + logger.info("test finished..."); + } catch (PluginException e) { - e.printStackTrace(); - fail(); - } + e.printStackTrace(); + fail(); + } - } + } } diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestFormat.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestFormat.java index 3d2c3da..1634c48 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestFormat.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestFormat.java @@ -19,9 +19,9 @@ import org.junit.jupiter.api.Test; /** * This test generates random test data - * + * * @author rsoika - * + * */ public class TestFormat { @@ -54,7 +54,7 @@ public class TestFormat { * ;"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";""; * "";"";"";"SO";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";0;"01012023" * ;"" - * + * */ @Test public void testDatum() { diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPAuhtenticators.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPAuhtenticators.java index aa532e3..070f175 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPAuhtenticators.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPAuhtenticators.java @@ -23,9 +23,9 @@ import jakarta.mail.Store; /** * This test class test the Imixs IMAPAuthenticator classes - * + * * @author rsoika - * + * */ public class TestIMAPAuhtenticators { @@ -35,7 +35,7 @@ public class TestIMAPAuhtenticators { /** * Test IMAP connect via OAuth for email address imixs@alexander-logistics.com - * + * * https://techcommunity.microsoft.com/t5/exchange/javamail-connecting-to-office-365-xoauth2-for-imap/m-p/1505026 */ @Test @@ -76,7 +76,7 @@ public class TestIMAPAuhtenticators { /** * Test IMAP connect via OAuth for email address imixs@alexander-logistics.com - * + * * https://techcommunity.microsoft.com/t5/exchange/javamail-connecting-to-office-365-xoauth2-for-imap/m-p/1505026 */ @Test @@ -118,7 +118,7 @@ public class TestIMAPAuhtenticators { /** * Test IMAP connect via OAuth for email address imixs@alexander-logistics.com - * + * * https://techcommunity.microsoft.com/t5/exchange/javamail-connecting-to-office-365-xoauth2-for-imap/m-p/1505026 */ @Test @@ -163,7 +163,7 @@ public class TestIMAPAuhtenticators { *

    * NOTE: This test should fail in December 2022 as Basic Authentication is no * longer supported by Microsoft Outlook. - * + * */ @Test @Disabled diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPOutlookOAuth.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPOutlookOAuth.java index 0bc2ff7..87c16b9 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPOutlookOAuth.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPOutlookOAuth.java @@ -41,11 +41,11 @@ import jakarta.mail.internet.MimeUtility; /** * This test shows how to access outlook via oauth2 - * + * * See: https://www.java-forum.org/thema/javamail-mit-oauth2-an-o365.199133/ - * + * * @author rsoika - * + * */ public class TestIMAPOutlookOAuth { @@ -58,9 +58,9 @@ public class TestIMAPOutlookOAuth { /* * Neue Daten - * - * Wert: zDL8Q~iEy8AX~dEmIOHrVv3aBTCZLrHIFmShibZ_ - * Geheime ID: bcaa558a-3300-4498-94e1-2267f418afd5 + * + * Wert: zDL8Q~iEy8AX~dEmIOHrVv3aBTCZLrHIFmShibZ_ Geheime ID: + * bcaa558a-3300-4498-94e1-2267f418afd5 */ @Test @@ -403,21 +403,21 @@ public class TestIMAPOutlookOAuth { /** * Helper method to receive a Microsoft access Token - * + * * https://jwt.ms/ - * + * * "OAuth2": { - * + * * "Authority": * https://login.microsoftonline.com/51e2f038-0a96-41be-801d-bb4118aa018e/v2.0, - * + * * "ClientId": "39e9eec5-6939-47dc-9729-3438a9898e63", - * + * * "ClientSecret": "............." - * + * * } - * - * + * + * * @return * @throws IOException * @throws ClientProtocolException @@ -455,7 +455,7 @@ public class TestIMAPOutlookOAuth { /** * Helper method to read bytes from strem - * + * * @param inputStream * @return * @throws IOException diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPOutlookOAuthDebug.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPOutlookOAuthDebug.java index 26ba922..8b20918 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPOutlookOAuthDebug.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestIMAPOutlookOAuthDebug.java @@ -40,7 +40,7 @@ import com.sun.mail.imap.IMAPFolder; /** * This test shows how to access outlook via oauth2 - * + * * @author rsoika */ public class TestIMAPOutlookOAuthDebug { @@ -223,7 +223,7 @@ public class TestIMAPOutlookOAuthDebug { /** * Helper method to read bytes from stream - * + * * @param inputStream * @return * @throws IOException diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestModelMatcher.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestModelMatcher.java index 4de70ea..2f0d21c 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestModelMatcher.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestModelMatcher.java @@ -8,9 +8,9 @@ import org.junit.jupiter.api.Test; /** * This test tests the InvoicePlugin isCargosoft method - * + * * @author rsoika - * + * */ public class TestModelMatcher { diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestOPListen.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestOPListen.java index 7ee5c7c..8bc30f4 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestOPListen.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestOPListen.java @@ -17,9 +17,9 @@ import org.junit.jupiter.api.Test; /** * Testen der KW Berechnung - * + * * @author rsoika - * + * */ public class TestOPListen { diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestPostionsnummernRegex.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestPostionsnummernRegex.java index 3d0f3f9..b9a2db2 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestPostionsnummernRegex.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestPostionsnummernRegex.java @@ -7,24 +7,24 @@ import org.junit.jupiter.api.Test; /** * This test generates random test data - * + * * @author rsoika - * + * */ public class TestPostionsnummernRegex { - /** - * Test positionnummern 'XX-YYY-####-###' - */ - @Test - public void testPutFileToFTP() { + /** + * Test positionnummern 'XX-YYY-####-###' + */ + @Test + public void testPutFileToFTP() { - assertTrue("AA-BBC-1234-991".matches(InvoicePlugin.INVOICE_POSITIONS_PATTERN_DEFAULT)); - assertFalse("aa-BBC-1234-991".matches(InvoicePlugin.INVOICE_POSITIONS_PATTERN_DEFAULT)); - assertFalse("CC-BBC-1234-991 ".matches(InvoicePlugin.INVOICE_POSITIONS_PATTERN_DEFAULT)); + assertTrue("AA-BBC-1234-991".matches(InvoicePlugin.INVOICE_POSITIONS_PATTERN_DEFAULT)); + assertFalse("aa-BBC-1234-991".matches(InvoicePlugin.INVOICE_POSITIONS_PATTERN_DEFAULT)); + assertFalse("CC-BBC-1234-991 ".matches(InvoicePlugin.INVOICE_POSITIONS_PATTERN_DEFAULT)); - assertTrue("IM-ZEL-2101-005".matches(InvoicePlugin.INVOICE_POSITIONS_PATTERN_DEFAULT)); + assertTrue("IM-ZEL-2101-005".matches(InvoicePlugin.INVOICE_POSITIONS_PATTERN_DEFAULT)); - } + } } diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestRunden.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestRunden.java index 27f308d..30fda80 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestRunden.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/TestRunden.java @@ -12,9 +12,9 @@ import org.junit.jupiter.api.Test; /** * This test generates random test data - * + * * @author rsoika - * + * */ public class TestRunden { diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/DueDateBugfix.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/DueDateBugfix.java index f730339..b70aa31 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/DueDateBugfix.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/DueDateBugfix.java @@ -52,7 +52,7 @@ public class DueDateBugfix { * Diese Test Method wandelt die Datei OPD_migration_230126.csv in eine EXTV * Datev Datei um, welche wir dann für einen einmaligen Import der * Rechnugnsdaten verwenden können. - * + * */ @Test @Disabled @@ -64,38 +64,24 @@ public class DueDateBugfix { /* * Erledigte - * - * - * EXTF_202301_202302030602_1295 - * EXTF_202301_202302060601_1296 - * EXTF_202301_202302070603_1297 - * EXTF_202301_202302080603_1298 - * EXTF_202301_202302090604_1299 - * EXTF_202301_202302100603_1300 - * EXTF_202301_202302130601_1301 - * EXTF_202301_202302140604_1302 - * EXTF_202301_202302141840_1303 - * EXTF_202301_202302160944_1304 - * EXTF_202302_202302020601_1294 - * EXTF_202302_202302030602_1295 - * EXTF_202302_202302060601_1296 - * EXTF_202302_202302070603_1297 - * EXTF_202302_202302080603_1298 - * EXTF_202302_202302090604_1299 - * EXTF_202302_202302100604_1300 - * EXTF_202302_202302130601_1301 - * EXTF_202302_202302140604_1302 - * EXTF_202302_202302141840_1303 - * EXTF_202302_202302160944_1304 - * EXTF_202303_202302070603_1297 - * EXTF_202303_202302080603_1298 - * EXTF_202303_202302090604_1299 - * EXTF_202303_202302100603_1300 - * EXTF_202303_202302130601_1301 - * EXTF_202303_202302140604_1302 - * EXTF_202303_202302141840_1303 + * + * + * EXTF_202301_202302030602_1295 EXTF_202301_202302060601_1296 + * EXTF_202301_202302070603_1297 EXTF_202301_202302080603_1298 + * EXTF_202301_202302090604_1299 EXTF_202301_202302100603_1300 + * EXTF_202301_202302130601_1301 EXTF_202301_202302140604_1302 + * EXTF_202301_202302141840_1303 EXTF_202301_202302160944_1304 + * EXTF_202302_202302020601_1294 EXTF_202302_202302030602_1295 + * EXTF_202302_202302060601_1296 EXTF_202302_202302070603_1297 + * EXTF_202302_202302080603_1298 EXTF_202302_202302090604_1299 + * EXTF_202302_202302100604_1300 EXTF_202302_202302130601_1301 + * EXTF_202302_202302140604_1302 EXTF_202302_202302141840_1303 + * EXTF_202302_202302160944_1304 EXTF_202303_202302070603_1297 + * EXTF_202303_202302080603_1298 EXTF_202303_202302090604_1299 + * EXTF_202303_202302100603_1300 EXTF_202303_202302130601_1301 + * EXTF_202303_202302140604_1302 EXTF_202303_202302141840_1303 * EXTF_202303_202302160944_1304 - * + * */ int count = 0; diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationMahnerAusziffern.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationMahnerAusziffern.java index 1dafb38..925a900 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationMahnerAusziffern.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationMahnerAusziffern.java @@ -59,10 +59,10 @@ public class MigrationMahnerAusziffern { * Diese Test Method wandelt die Datei OPD_migration_230126.csv in eine EXTV * Datev Datei um, welche wir dann für einen einmaligen Import der * Rechnugnsdaten verwenden können. - * + * * @throws PluginException * @throws RestAPIException - * + * */ @Test @Disabled @@ -131,7 +131,7 @@ public class MigrationMahnerAusziffern { /** * Prüft eine einzelen Datenzeile - * + * * @param writer * @param data * @throws IOException @@ -231,9 +231,9 @@ public class MigrationMahnerAusziffern { /** * Diese Methode schließt die Rechnungen mit den übergebenen Rechnungsnummer ab - * + * * Beispiel: Bitte Ausziffern : 17755 = "190818" -> "190313" - * + * * @throws UnsupportedEncodingException * @throws RestAPIException * @throws PluginException @@ -265,7 +265,7 @@ public class MigrationMahnerAusziffern { /** * Teste ob es diese Rechnung im Office gibt. - * + * * @param rech1 * @throws RestAPIException * @throws UnsupportedEncodingException @@ -287,7 +287,7 @@ public class MigrationMahnerAusziffern { /** * Hilfsmethdoe die prüft ob es zu diesr Rechnung shcon eine passende * Gegenrechnung gibt. - * + * * @param rech * @return */ diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationMahnerDatev.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationMahnerDatev.java index c4588d7..c0ca012 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationMahnerDatev.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationMahnerDatev.java @@ -37,7 +37,7 @@ public class MigrationMahnerDatev { * Diese Test Method wandelt die Datei OPD_migration_230126.csv in eine EXTV * Datev Datei um, welche wir dann für einen einmaligen Import der * Rechnugnsdaten verwenden können. - * + * */ @Test @Disabled @@ -139,7 +139,7 @@ public class MigrationMahnerDatev { /** * Hilfsmethode die einen DATEV Header schreibt - * + * * @param writer * @throws IOException */ @@ -157,7 +157,7 @@ public class MigrationMahnerDatev { /** * Schreibt eine einzelen Datenzeile - * + * * @param writer * @param data * @throws IOException diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationSpaceZuordnung.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationSpaceZuordnung.java index 9d793d0..018034d 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationSpaceZuordnung.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/migration/MigrationSpaceZuordnung.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test; /** * Dieser Agent ordnet nachträglich Ausgangsrechnungen aus der Migraiton einem * Space zu. - * + * * @author rsoika * */ @@ -53,11 +53,11 @@ public class MigrationSpaceZuordnung { /** * Diese methode liest offene Ausgangsrechnungen aus udn prüft of space.ref * bereits getzt ist. - * + * * @throws PluginException * @throws RestAPIException * @throws UnsupportedEncodingException - * + * */ @Test @Disabled @@ -149,7 +149,7 @@ public class MigrationSpaceZuordnung { /** * Helper method that loads all expressions from the spaces - * + * * @return List of list of expressions * @throws UnsupportedEncodingException * @throws RestAPIException diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/rest/TestDataClient.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/rest/TestDataClient.java index 4e7546c..dd93722 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/rest/TestDataClient.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/rest/TestDataClient.java @@ -17,9 +17,9 @@ import jakarta.ws.rs.core.Cookie; import jakarta.ws.rs.core.Response; /** - * Test to request data via a JSESSIONID only.... - * This technique is used by the DataRestService - * + * Test to request data via a JSESSIONID only.... This technique is used by the + * DataRestService + * */ @Ignore public class TestDataClient { @@ -36,11 +36,8 @@ public class TestDataClient { @BeforeEach public void setup() throws PluginException { // Create a Cookie object with a name and a value - Cookie cookie = new Cookie.Builder("JSESSIONID") - .value(sessionID) - .path("/") - .domain("alexander-logistics.office-workflow.de") - .build(); + Cookie cookie = new Cookie.Builder("JSESSIONID").value(sessionID).path("/") + .domain("alexander-logistics.office-workflow.de").build(); client = new DocumentClient(restAPI.trim()); CookieAuthenticator aut = new CookieAuthenticator(cookie); @@ -51,7 +48,7 @@ public class TestDataClient { /** * Test to read data.... - * + * */ @Test @Disabled diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/sepa/TestSEPATransform.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/sepa/TestSEPATransform.java index 85c4970..62522a9 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/sepa/TestSEPATransform.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/sepa/TestSEPATransform.java @@ -12,71 +12,71 @@ import org.junit.jupiter.api.Test; /** * Diese Klasse testet die SEPA XSLT Transformation - * - * + * + * * @author rsoika */ public class TestSEPATransform { - final static String ENCODING = "UTF-8"; + final static String ENCODING = "UTF-8"; - private static Logger logger = Logger.getLogger(TestSEPATransform.class.getName()); + private static Logger logger = Logger.getLogger(TestSEPATransform.class.getName()); - @BeforeEach - public void setup() throws PluginException, ModelException { - logger.info("setup test environment ..."); + @BeforeEach + public void setup() throws PluginException, ModelException { + logger.info("setup test environment ..."); - } + } - /** - * XSL Transformation for SEPA Standard file - * - * @throws IOException - */ - @Test - public void testDefault() throws IOException { - String MODEL_PATH_XML = "sepa/beispiel-daten.xml"; - String MODEL_PATH_XSL = "sepa/sepa-2.0.5.xsl"; - try { - XSLTester.transform(MODEL_PATH_XML, MODEL_PATH_XSL, "../reports/sepa/result_sepa01.xml"); - } catch (Exception e) { - fail(); - } - } + /** + * XSL Transformation for SEPA Standard file + * + * @throws IOException + */ + @Test + public void testDefault() throws IOException { + String MODEL_PATH_XML = "sepa/beispiel-daten.xml"; + String MODEL_PATH_XSL = "sepa/sepa-2.0.5.xsl"; + try { + XSLTester.transform(MODEL_PATH_XML, MODEL_PATH_XSL, "../reports/sepa/result_sepa01.xml"); + } catch (Exception e) { + fail(); + } + } - /** - * XSL Transformation for SEPA file for Bank Polski - * - * We also test the two-digits case. 10,5 need to become 10,50 - * - * @throws IOException - */ - @Test - public void testBankPolski() throws IOException { - String MODEL_PATH_XML = "sepa/beispiel-daten.xml"; - String MODEL_PATH_XSL = "sepa/sepa-2.0.11-pl.xsl"; - try { - XSLTester.transform(MODEL_PATH_XML, MODEL_PATH_XSL, "../reports/sepa/result_sepa-pl.xml"); - } catch (Exception e) { - fail(); - } - } + /** + * XSL Transformation for SEPA file for Bank Polski + * + * We also test the two-digits case. 10,5 need to become 10,50 + * + * @throws IOException + */ + @Test + public void testBankPolski() throws IOException { + String MODEL_PATH_XML = "sepa/beispiel-daten.xml"; + String MODEL_PATH_XSL = "sepa/sepa-2.0.11-pl.xsl"; + try { + XSLTester.transform(MODEL_PATH_XML, MODEL_PATH_XSL, "../reports/sepa/result_sepa-pl.xml"); + } catch (Exception e) { + fail(); + } + } - /** - * XSL Transformation for SEPA file for Bank Polski - * - * Neue Bank in Polen - bankmillennium - * - * @throws IOException - */ - @Test - public void testBankPolskibankmillennium() throws IOException { - String MODEL_PATH_XML = "sepa/beispiel-daten.xml"; - String MODEL_PATH_XSL = "sepa/sepa-2.0.11-pl_bankmillennium.xsl"; - try { - XSLTester.transform(MODEL_PATH_XML, MODEL_PATH_XSL, "../reports/sepa/result_sepa-pl_bankmillennium.xml"); - } catch (Exception e) { - fail(); - } - } + /** + * XSL Transformation for SEPA file for Bank Polski + * + * Neue Bank in Polen - bankmillennium + * + * @throws IOException + */ + @Test + public void testBankPolskibankmillennium() throws IOException { + String MODEL_PATH_XML = "sepa/beispiel-daten.xml"; + String MODEL_PATH_XSL = "sepa/sepa-2.0.11-pl_bankmillennium.xsl"; + try { + XSLTester.transform(MODEL_PATH_XML, MODEL_PATH_XSL, "../reports/sepa/result_sepa-pl_bankmillennium.xml"); + } catch (Exception e) { + fail(); + } + } } diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/sepa/XSLTester.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/sepa/XSLTester.java index 6950b6c..fda237c 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/sepa/XSLTester.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/sepa/XSLTester.java @@ -13,12 +13,12 @@ import org.imixs.workflow.xml.XSLHandler; /** * The XSLTester is used in junit tests to transform XSL/XML files - * + * * Do not forget to change your pom.xml and add the following to your build * section - * + * *

    - * {@code		
    + * {@code
      * 
      *   
      *     ${basedir}/../reports
    @@ -29,64 +29,64 @@ import org.imixs.workflow.xml.XSLHandler;
      * 
      * }
      * 
    - * + * * @author rsoika */ public class XSLTester { - final static String ENCODING = "UTF-8"; + final static String ENCODING = "UTF-8"; - /** - * XSL Transformation - * - * @throws IOException - * @throws TransformerException - */ - public static void transform(String xmlSourcePath, String xslSourcePath, String outputPath) - throws IOException, TransformerException { - OutputStream outputStream = new FileOutputStream(outputPath); - // Use the class loader to load the XML file as a resource - ClassLoader classLoader = XSLTester.class.getClassLoader(); - InputStream xmlInputStream = classLoader.getResourceAsStream(xmlSourcePath); - InputStream xslInputStream = classLoader.getResourceAsStream(xslSourcePath); + /** + * XSL Transformation + * + * @throws IOException + * @throws TransformerException + */ + public static void transform(String xmlSourcePath, String xslSourcePath, String outputPath) + throws IOException, TransformerException { + OutputStream outputStream = new FileOutputStream(outputPath); + // Use the class loader to load the XML file as a resource + ClassLoader classLoader = XSLTester.class.getClassLoader(); + InputStream xmlInputStream = classLoader.getResourceAsStream(xmlSourcePath); + InputStream xslInputStream = classLoader.getResourceAsStream(xslSourcePath); - try { + try { - if (xmlInputStream == null) { - throw new RuntimeException("XML file not found: " + xmlSourcePath); - } - if (xslInputStream == null) { - throw new RuntimeException("XSL file not found: " + xslSourcePath); - } + if (xmlInputStream == null) { + throw new RuntimeException("XML file not found: " + xmlSourcePath); + } + if (xslInputStream == null) { + throw new RuntimeException("XSL file not found: " + xslSourcePath); + } - String xmlSource = readXml(xmlInputStream); - String xslSource = readXml(xslInputStream); - XSLHandler.transform(xmlSource, xslSource, ENCODING, outputStream); + String xmlSource = readXml(xmlInputStream); + String xslSource = readXml(xslInputStream); + XSLHandler.transform(xmlSource, xslSource, ENCODING, outputStream); - } finally { - if (xmlInputStream != null) - xmlInputStream.close(); - if (xslInputStream != null) - xslInputStream.close(); - if (outputStream != null) - outputStream.close(); - } - } + } finally { + if (xmlInputStream != null) + xmlInputStream.close(); + if (xslInputStream != null) + xslInputStream.close(); + if (outputStream != null) + outputStream.close(); + } + } - /** - * read and return the XML content as a String - * - */ - private static String readXml(InputStream inputStream) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { - StringBuilder xmlContent = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - xmlContent.append(line).append("\n"); - } - return xmlContent.toString(); - } catch (IOException e) { - throw new RuntimeException("Error reading XML file", e); - } - } + /** + * read and return the XML content as a String + * + */ + private static String readXml(InputStream inputStream) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + StringBuilder xmlContent = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + xmlContent.append(line).append("\n"); + } + return xmlContent.toString(); + } catch (IOException e) { + throw new RuntimeException("Error reading XML file", e); + } + } } diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/xml/EAkteImportTester.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/xml/EAkteImportTester.java index 31334de..9799829 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/xml/EAkteImportTester.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/xml/EAkteImportTester.java @@ -34,86 +34,86 @@ import com.alexanderlogistics.mahnlauf.MahnlaufService; /** * Testet den Import Vorgang anhand einer Cargosoft XML Datei - * - * - * + * + * + * * @author rsoika */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.WARN) public class EAkteImportTester { - @Spy - @InjectMocks - private CargosoftXMLEAkteImportService service; + @Spy + @InjectMocks + private CargosoftXMLEAkteImportService service; - @Mock - private DocumentService documentService; + @Mock + private DocumentService documentService; - @Mock - private BusinessPartnerService businessPartnerService; + @Mock + private BusinessPartnerService businessPartnerService; - @Mock - private MahnlaufService mahnlaufService; + @Mock + private MahnlaufService mahnlaufService; - @BeforeEach - public void setUp() { - // Mock protected Methode 'alreadyImported' -> always false - Mockito.when(service.alreadyImported(Mockito.anyString())).thenReturn(false); - } + @BeforeEach + public void setUp() { + // Mock protected Methode 'alreadyImported' -> always false + Mockito.when(service.alreadyImported(Mockito.anyString())).thenReturn(false); + } - /** - * Ein Einfacher test - * - * @throws TransformerException - * @throws XPathExpressionException - * @throws ModelException - * @throws PluginException - * @throws ProcessingErrorException - * @throws AccessDeniedException - * - * @throws Exception - */ - @Test - public void testEAkteBasic() throws Exception { + /** + * Ein Einfacher test + * + * @throws TransformerException + * @throws XPathExpressionException + * @throws ModelException + * @throws PluginException + * @throws ProcessingErrorException + * @throws AccessDeniedException + * + * @throws Exception + */ + @Test + public void testEAkteBasic() throws Exception { - ItemCollection source = new ItemCollection(); - String fileName = "Abgabenbescheid-IM-GCA-2408-116N-001-ATC400012650820242452-_20240806164256864_DEBUG.xml"; - byte[] xmlContent = readTestFile("cargosoft/" + fileName); - assertNotNull(xmlContent); + ItemCollection source = new ItemCollection(); + String fileName = "Abgabenbescheid-IM-GCA-2408-116N-001-ATC400012650820242452-_20240806164256864_DEBUG.xml"; + byte[] xmlContent = readTestFile("cargosoft/" + fileName); + assertNotNull(xmlContent); - // Act - ItemCollection result = service.createWorkitem(source, fileName, xmlContent, new HashMap<>()); + // Act + ItemCollection result = service.createWorkitem(source, fileName, xmlContent, new HashMap<>()); - // Assert - assertNotNull(result); - assertEquals("001", result.getItemValueString("mandant.id")); + // Assert + assertNotNull(result); + assertEquals("001", result.getItemValueString("mandant.id")); - // test Rows - List rows = InvoiceUtil.explodeChildList(result); - assertNotNull(rows); - assertEquals(2, rows.size()); - ItemCollection row1 = rows.get(0); - ItemCollection row2 = rows.get(1); - assertEquals("LA-ZEL-2403-004", row1.getItemValueString("filenumber")); - assertEquals("LA-ZEL-2403-00", row2.getItemValueString("filenumber")); - assertEquals("ZOLL", row1.getItemValueString("activity.type")); - assertEquals("EUST", row2.getItemValueString("activity.type")); + // test Rows + List rows = InvoiceUtil.explodeChildList(result); + assertNotNull(rows); + assertEquals(2, rows.size()); + ItemCollection row1 = rows.get(0); + ItemCollection row2 = rows.get(1); + assertEquals("LA-ZEL-2403-004", row1.getItemValueString("filenumber")); + assertEquals("LA-ZEL-2403-00", row2.getItemValueString("filenumber")); + assertEquals("ZOLL", row1.getItemValueString("activity.type")); + assertEquals("EUST", row2.getItemValueString("activity.type")); - } + } - /** - * Hilfsmethode zum einlesen einer xml test datei - * - * @return - * @throws IOException - */ - private byte[] readTestFile(String filename) throws IOException { - // Load the file from the resources folder - ClassLoader classLoader = getClass().getClassLoader(); - InputStream inputStream = classLoader.getResourceAsStream(filename); - byte[] data = inputStream.readAllBytes(); - inputStream.close(); - return data; - } + /** + * Hilfsmethode zum einlesen einer xml test datei + * + * @return + * @throws IOException + */ + private byte[] readTestFile(String filename) throws IOException { + // Load the file from the resources folder + ClassLoader classLoader = getClass().getClassLoader(); + InputStream inputStream = classLoader.getResourceAsStream(filename); + byte[] data = inputStream.readAllBytes(); + inputStream.close(); + return data; + } } diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/xml/InvoiceImportTester.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/xml/InvoiceImportTester.java index e31794b..80231ed 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/xml/InvoiceImportTester.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/xml/InvoiceImportTester.java @@ -34,131 +34,131 @@ import com.alexanderlogistics.mahnlauf.MahnlaufService; /** * Testet den Import Vorgang anhand einer Cargosoft XML Datei - * - * - * + * + * + * * @author rsoika */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.WARN) public class InvoiceImportTester { - @Spy - @InjectMocks - private CargosoftXMLInvoiceImportService service; + @Spy + @InjectMocks + private CargosoftXMLInvoiceImportService service; - @Mock - private DocumentService documentService; + @Mock + private DocumentService documentService; - @Mock - private MahnlaufService mahnlaufService; + @Mock + private MahnlaufService mahnlaufService; - @Mock - private BusinessPartnerService businessPartnerService; + @Mock + private BusinessPartnerService businessPartnerService; - @BeforeEach - public void setUp() { - // Mock protected Methode 'alreadyImported' -> always false - Mockito.when(service.alreadyImported(Mockito.anyString())).thenReturn(false); - } + @BeforeEach + public void setUp() { + // Mock protected Methode 'alreadyImported' -> always false + Mockito.when(service.alreadyImported(Mockito.anyString())).thenReturn(false); + } - /** - * Ein Einfacher test - * - * @throws TransformerException - * @throws XPathExpressionException - * @throws ModelException - * @throws PluginException - * @throws ProcessingErrorException - * @throws AccessDeniedException - * - * @throws Exception - */ - @Test - public void testInvoiceBasic() throws Exception { + /** + * Ein Einfacher test + * + * @throws TransformerException + * @throws XPathExpressionException + * @throws ModelException + * @throws PluginException + * @throws ProcessingErrorException + * @throws AccessDeniedException + * + * @throws Exception + */ + @Test + public void testInvoiceBasic() throws Exception { - ItemCollection source = new ItemCollection(); - String fileName = "001_R_224380_110136787.xml"; - byte[] xmlContent = readTestFile("cargosoft/" + fileName); - assertNotNull(xmlContent); + ItemCollection source = new ItemCollection(); + String fileName = "001_R_224380_110136787.xml"; + byte[] xmlContent = readTestFile("cargosoft/" + fileName); + assertNotNull(xmlContent); - // Act - ItemCollection result = service.createWorkitem(source, fileName, xmlContent, new HashMap<>()); + // Act + ItemCollection result = service.createWorkitem(source, fileName, xmlContent, new HashMap<>()); - // Assert - assertNotNull(result); - assertEquals("001", result.getItemValueString("mandant.id")); + // Assert + assertNotNull(result); + assertEquals("001", result.getItemValueString("mandant.id")); - // test Rows - List rows = InvoiceUtil.explodeChildList(result); - assertNotNull(rows); - assertEquals(2, rows.size()); - ItemCollection row1 = rows.get(0); - ItemCollection row2 = rows.get(1); - assertEquals("EX-GCA-2408-042", row1.getItemValueString("datev.text")); - assertEquals("EX-GCA-2408-042", row2.getItemValueString("datev.text")); + // test Rows + List rows = InvoiceUtil.explodeChildList(result); + assertNotNull(rows); + assertEquals(2, rows.size()); + ItemCollection row1 = rows.get(0); + ItemCollection row2 = rows.get(1); + assertEquals("EX-GCA-2408-042", row1.getItemValueString("datev.text")); + assertEquals("EX-GCA-2408-042", row2.getItemValueString("datev.text")); - } + } - /** - * Ein ATC test - * - * @throws IOException - * - * @throws Exception - */ - @Test - public void testInvoiceATC() throws Exception { + /** + * Ein ATC test + * + * @throws IOException + * + * @throws Exception + */ + @Test + public void testInvoiceATC() throws Exception { - ItemCollection source = new ItemCollection(); - String fileName = "001_R_224225_115103063.xml"; - byte[] xmlContent = readTestFile("cargosoft/" + fileName); - assertNotNull(xmlContent); + ItemCollection source = new ItemCollection(); + String fileName = "001_R_224225_115103063.xml"; + byte[] xmlContent = readTestFile("cargosoft/" + fileName); + assertNotNull(xmlContent); - // Act - ItemCollection result = service.createWorkitem(source, fileName, xmlContent, new HashMap<>()); + // Act + ItemCollection result = service.createWorkitem(source, fileName, xmlContent, new HashMap<>()); - // Assert - assertNotNull(result); - assertEquals("001", result.getItemValueString("mandant.id")); - assertEquals("ATC400012650820242452", result.getItemValueString("invoice.atc.number")); - assertEquals(3752.95, result.getItemValueDouble("invoice.total"), 0.0); + // Assert + assertNotNull(result); + assertEquals("001", result.getItemValueString("mandant.id")); + assertEquals("ATC400012650820242452", result.getItemValueString("invoice.atc.number")); + assertEquals(3752.95, result.getItemValueDouble("invoice.total"), 0.0); - // test Rows - List rows = InvoiceUtil.explodeChildList(result); - assertNotNull(rows); - assertEquals(2, rows.size()); - ItemCollection row1 = rows.get(0); - assertEquals("IM-GCA-2408-116", row1.getItemValueString("datev.text")); - assertEquals("EUST", row1.getItemValueString("category")); - assertEquals("EUST", row1.getItemValueString("BillingCode")); - // test BillingText - List billingTextList = row1.getItemValue("BillingText"); - assertEquals(2, billingTextList.size()); - // test ATC Number - assertEquals("ATC400012650820242452", row1.getItemValueString("atc.number")); - // Umsatz - assertEquals(3591.34, row1.getItemValueDouble("datev.umsatz"), 0.0); + // test Rows + List rows = InvoiceUtil.explodeChildList(result); + assertNotNull(rows); + assertEquals(2, rows.size()); + ItemCollection row1 = rows.get(0); + assertEquals("IM-GCA-2408-116", row1.getItemValueString("datev.text")); + assertEquals("EUST", row1.getItemValueString("category")); + assertEquals("EUST", row1.getItemValueString("BillingCode")); + // test BillingText + List billingTextList = row1.getItemValue("BillingText"); + assertEquals(2, billingTextList.size()); + // test ATC Number + assertEquals("ATC400012650820242452", row1.getItemValueString("atc.number")); + // Umsatz + assertEquals(3591.34, row1.getItemValueDouble("datev.umsatz"), 0.0); - ItemCollection row2 = rows.get(1); - assertEquals("IM-GCA-2408-116", row2.getItemValueString("datev.text")); - assertEquals("SONST", row2.getItemValueString("category")); - // Umsatz - assertEquals(161.61, row2.getItemValueDouble("datev.umsatz"), 0.0); - } + ItemCollection row2 = rows.get(1); + assertEquals("IM-GCA-2408-116", row2.getItemValueString("datev.text")); + assertEquals("SONST", row2.getItemValueString("category")); + // Umsatz + assertEquals(161.61, row2.getItemValueDouble("datev.umsatz"), 0.0); + } - /** - * Hilfsmethode zum einlesen einer xml test datei - * - * @return - * @throws IOException - */ - private byte[] readTestFile(String filename) throws IOException { - // Load the file from the resources folder - ClassLoader classLoader = getClass().getClassLoader(); - InputStream inputStream = classLoader.getResourceAsStream(filename); - byte[] data = inputStream.readAllBytes(); - inputStream.close(); - return data; - } + /** + * Hilfsmethode zum einlesen einer xml test datei + * + * @return + * @throws IOException + */ + private byte[] readTestFile(String filename) throws IOException { + // Load the file from the resources folder + ClassLoader classLoader = getClass().getClassLoader(); + InputStream inputStream = classLoader.getResourceAsStream(filename); + byte[] data = inputStream.readAllBytes(); + inputStream.close(); + return data; + } } diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/zoho/TestUpdateTokens.java b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/zoho/TestUpdateTokens.java index a201409..1e7a144 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/zoho/TestUpdateTokens.java +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/zoho/TestUpdateTokens.java @@ -26,247 +26,225 @@ import jakarta.json.bind.JsonbBuilder; /** * Diese Klasse generiert einen access und refresh token anhand eines Code * Tokens - * - * + * + * * @author rsoika */ @Disabled public class TestUpdateTokens { - final static String ENCODING = "UTF-8"; + final static String ENCODING = "UTF-8"; - private static Logger logger = Logger.getLogger(TestUpdateTokens.class.getName()); + private static Logger logger = Logger.getLogger(TestUpdateTokens.class.getName()); - private HttpClient httpClient; - private Jsonb jsonb; + private HttpClient httpClient; + private Jsonb jsonb; - private String redirectURI = "https://alexander-logistics-dwc.office-workflow.de/api/zoho/grant"; + private String redirectURI = "https://alexander-logistics-dwc.office-workflow.de/api/zoho/grant"; - private String clientID = "1000.GR5FTPZYB06HEWSFSSX4DJU3OBD4EJ"; - private String clientSecret = "3f3e74c5212bbc924a7ebf77f887c6296db1106c47"; + private String clientID = "1000.GR5FTPZYB06HEWSFSSX4DJU3OBD4EJ"; + private String clientSecret = "3f3e74c5212bbc924a7ebf77f887c6296db1106c47"; - private String code = null; - private String accessToken = null; - private String refreshToken = null; + private String code = null; + private String accessToken = null; + private String refreshToken = null; - @BeforeEach - public void setup() throws PluginException, ModelException { - logger.info("setup test environment ..."); - this.httpClient = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(10)) - .build(); - this.jsonb = JsonbBuilder.create(); - } + @BeforeEach + public void setup() throws PluginException, ModelException { + logger.info("setup test environment ..."); + this.httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); + this.jsonb = JsonbBuilder.create(); + } - /** - * Testet den Vorgang einen Access und Refresh Token zu generieren - * - * @throws IOException - */ - @Test - public void testUpdateTokens() throws IOException { + /** + * Testet den Vorgang einen Access und Refresh Token zu generieren + * + * @throws IOException + */ + @Test + public void testUpdateTokens() throws IOException { - logger.info("├── update Zoho tokens"); - refreshToken = null; - accessToken = null; - code = "1000.48bd85a3965cf724f4691757cfd8d93b.8ae5e3ee7d70f90d307da4d289a9b067"; - try { + logger.info("├── update Zoho tokens"); + refreshToken = null; + accessToken = null; + code = "1000.48bd85a3965cf724f4691757cfd8d93b.8ae5e3ee7d70f90d307da4d289a9b067"; + try { - String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" + - "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8) + - "&client_id=" + URLEncoder.encode(clientID, StandardCharsets.UTF_8) + - "&client_secret=" + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8) + - "&redirect_uri=" + URLEncoder.encode(redirectURI, StandardCharsets.UTF_8) + - "&grant_type=authorization_code"; + String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" + "?code=" + + URLEncoder.encode(code, StandardCharsets.UTF_8) + "&client_id=" + + URLEncoder.encode(clientID, StandardCharsets.UTF_8) + "&client_secret=" + + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8) + "&redirect_uri=" + + URLEncoder.encode(redirectURI, StandardCharsets.UTF_8) + "&grant_type=authorization_code"; - logger.info("│ ├── Request URI=" + requestUrl); + logger.info("│ ├── Request URI=" + requestUrl); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(requestUrl)) - .header("Content-Type", "application/x-www-form-urlencoded") - .POST(HttpRequest.BodyPublishers.noBody()) // Wichtig: Kein Body! - .build(); + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(requestUrl)) + .header("Content-Type", "application/x-www-form-urlencoded") + .POST(HttpRequest.BodyPublishers.noBody()) // Wichtig: Kein Body! + .build(); - HttpResponse response; + HttpResponse response; - response = httpClient.send(request, - HttpResponse.BodyHandlers.ofString()); + response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - logger.info("│ ├── Response statusCode=" + response.statusCode()); - if (response.statusCode() == 200) { + logger.info("│ ├── Response statusCode=" + response.statusCode()); + if (response.statusCode() == 200) { - // 1. Response-Logging für Debugging - logger.info("│ ├── Response Body=" + response.body()); + // 1. Response-Logging für Debugging + logger.info("│ ├── Response Body=" + response.body()); - assertNotNull(response.body()); - assertFalse(response.body().contains("error")); + assertNotNull(response.body()); + assertFalse(response.body().contains("error")); - TokenResponse tokenResponse = jsonb.fromJson(response.body(), - TokenResponse.class); + TokenResponse tokenResponse = jsonb.fromJson(response.body(), TokenResponse.class); - accessToken = tokenResponse.getAccess_token(); - assertNotNull(accessToken); - logger.info("│ ├── accessToken=" + accessToken); - refreshToken = tokenResponse.getRefresh_token(); - logger.info("│ ├── refreshToken=" + refreshToken); - logger.info("│ ├── expires_in=" + tokenResponse.getExpires_in()); - } else { - throw new RuntimeException("Failed to get tokens: " + - response.statusCode() + " - " + response.body()); - } + accessToken = tokenResponse.getAccess_token(); + assertNotNull(accessToken); + logger.info("│ ├── accessToken=" + accessToken); + refreshToken = tokenResponse.getRefresh_token(); + logger.info("│ ├── refreshToken=" + refreshToken); + logger.info("│ ├── expires_in=" + tokenResponse.getExpires_in()); + } else { + throw new RuntimeException("Failed to get tokens: " + response.statusCode() + " - " + response.body()); + } - } catch (IOException | InterruptedException e) { - throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage()); - } + } catch (IOException | InterruptedException e) { + throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage()); + } - } + } - /** - * Testet den Refresh Token - * - * POST: - * {Accounts_URL}/oauth/v2/token?refresh_token={refresh_token}&client_id={client_id}&client_secret={client_secret}&grant_type=refresh_token - * - * @throws IOException - */ - @Test - public void testRefreshAccessToken() throws IOException { + /** + * Testet den Refresh Token + * + * POST: + * {Accounts_URL}/oauth/v2/token?refresh_token={refresh_token}&client_id={client_id}&client_secret={client_secret}&grant_type=refresh_token + * + * @throws IOException + */ + @Test + public void testRefreshAccessToken() throws IOException { - logger.info("├── refresh Zoho access token"); - refreshToken = "1000.cf458816e97f848df8134ce1c7e923a1.5b9dbdee317e35d7f0c619e7648d55ff"; - try { + logger.info("├── refresh Zoho access token"); + refreshToken = "1000.cf458816e97f848df8134ce1c7e923a1.5b9dbdee317e35d7f0c619e7648d55ff"; + try { - String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" + - "?refresh_token=" + URLEncoder.encode(refreshToken, StandardCharsets.UTF_8) + - "&client_id=" + URLEncoder.encode(clientID, StandardCharsets.UTF_8) + - "&client_secret=" + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8) + - "&grant_type=refresh_token"; + String requestUrl = "https://accounts.zoho.eu/oauth/v2/token" + "?refresh_token=" + + URLEncoder.encode(refreshToken, StandardCharsets.UTF_8) + "&client_id=" + + URLEncoder.encode(clientID, StandardCharsets.UTF_8) + "&client_secret=" + + URLEncoder.encode(clientSecret, StandardCharsets.UTF_8) + "&grant_type=refresh_token"; - logger.info("│ ├── Request URI=" + requestUrl); + logger.info("│ ├── Request URI=" + requestUrl); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(requestUrl)) - .header("Content-Type", "application/x-www-form-urlencoded") - .POST(HttpRequest.BodyPublishers.noBody()) // Wichtig: Kein Body! - .build(); + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(requestUrl)) + .header("Content-Type", "application/x-www-form-urlencoded") + .POST(HttpRequest.BodyPublishers.noBody()) // Wichtig: Kein Body! + .build(); - HttpResponse response; + HttpResponse response; - response = httpClient.send(request, - HttpResponse.BodyHandlers.ofString()); + response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - logger.info("│ ├── Response statusCode=" + response.statusCode()); - if (response.statusCode() == 200) { + logger.info("│ ├── Response statusCode=" + response.statusCode()); + if (response.statusCode() == 200) { - // 1. Response-Logging für Debugging - logger.info("│ ├── Response Body=" + response.body()); + // 1. Response-Logging für Debugging + logger.info("│ ├── Response Body=" + response.body()); - TokenResponse tokenResponse = jsonb.fromJson(response.body(), - TokenResponse.class); + TokenResponse tokenResponse = jsonb.fromJson(response.body(), TokenResponse.class); - String accessToken = tokenResponse.getAccess_token(); - logger.info("│ ├── accessToken=" + accessToken); - logger.info("│ ├── expires_in=" + tokenResponse.getExpires_in()); - } else { - throw new RuntimeException("Failed to get tokens: " + - response.statusCode() + " - " + response.body()); - } + String accessToken = tokenResponse.getAccess_token(); + logger.info("│ ├── accessToken=" + accessToken); + logger.info("│ ├── expires_in=" + tokenResponse.getExpires_in()); + } else { + throw new RuntimeException("Failed to get tokens: " + response.statusCode() + " - " + response.body()); + } - } catch (IOException | InterruptedException e) { - throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage()); - } + } catch (IOException | InterruptedException e) { + throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage()); + } - } + } - /** - * Testet den eigentlichen API Zugriff mit Hilfe des zuvor generieren Access - * Token - * - * GET: https://{zohoapis_domain}/writer/api/v1/documents - * - * @see https://www.zoho.com/books/api/v3/introduction/#overview - * @throws IOException - */ - @Test - public void testGetOrganizations() throws IOException { + /** + * Testet den eigentlichen API Zugriff mit Hilfe des zuvor generieren Access + * Token + * + * GET: https://{zohoapis_domain}/writer/api/v1/documents + * + * @see https://www.zoho.com/books/api/v3/introduction/#overview + * @throws IOException + */ + @Test + public void testGetOrganizations() throws IOException { - logger.info("├── get Zoho organizations"); - accessToken = "1000.21912dc6af0906707f51d336fd98ab61.eb153952d0d86a828fcb7e18426c2c1a"; - try { + logger.info("├── get Zoho organizations"); + accessToken = "1000.21912dc6af0906707f51d336fd98ab61.eb153952d0d86a828fcb7e18426c2c1a"; + try { - String url = "https://www.zohoapis.eu/books/v3/organizations"; + String url = "https://www.zohoapis.eu/books/v3/organizations"; - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .GET() - .header("Authorization", "Zoho-oauthtoken " + accessToken) - .build(); + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).GET() + .header("Authorization", "Zoho-oauthtoken " + accessToken).build(); - HttpResponse response = httpClient.send( - request, - HttpResponse.BodyHandlers.ofString()); + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - logger.info("│ ├── Response statusCode=" + response.statusCode()); + logger.info("│ ├── Response statusCode=" + response.statusCode()); - // 1. Response-Logging für Debugging - logger.info("│ ├── Response Body=" + response.body()); + // 1. Response-Logging für Debugging + logger.info("│ ├── Response Body=" + response.body()); - if (response.statusCode() >= 300) { + if (response.statusCode() >= 300) { - throw new RuntimeException("API request failed with status code: " + response.statusCode()); - } + throw new RuntimeException("API request failed with status code: " + response.statusCode()); + } - } catch (IOException | InterruptedException e) { - throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage()); - } + } catch (IOException | InterruptedException e) { + throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage()); + } - } + } - /** - * Testet den eigentlichen API Zugriff mit Hilfe des zuvor generieren Access - * Token - * - * - * curl --request GET \ - * --url 'https://www.zohoapis.com/books/v3/invoices?organization_id=10234695' \ - * --header 'Authorization: Zoho-oauthtoken - * 1000.41d9xxxxxxxxxxxxxxxxxxxxxxxxc2d1.8fccxxxxxxxxxxxxxxxxxxxxxxxx125f' - * - * - * @throws IOException - */ - @Test - public void testGetInvoices() throws IOException { + /** + * Testet den eigentlichen API Zugriff mit Hilfe des zuvor generieren Access + * Token + * + * + * curl --request GET \ --url + * 'https://www.zohoapis.com/books/v3/invoices?organization_id=10234695' \ + * --header 'Authorization: Zoho-oauthtoken + * 1000.41d9xxxxxxxxxxxxxxxxxxxxxxxxc2d1.8fccxxxxxxxxxxxxxxxxxxxxxxxx125f' + * + * + * @throws IOException + */ + @Test + public void testGetInvoices() throws IOException { - logger.info("├── get Zoho documents"); - accessToken = "1000.21912dc6af0906707f51d336fd98ab61.eb153952d0d86a828fcb7e18426c2c1a"; - try { + logger.info("├── get Zoho documents"); + accessToken = "1000.21912dc6af0906707f51d336fd98ab61.eb153952d0d86a828fcb7e18426c2c1a"; + try { - String url = "https://www.zohoapis.eu/books/v3/invoices?organization_id=20105697367"; + String url = "https://www.zohoapis.eu/books/v3/invoices?organization_id=20105697367"; - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .GET() - .header("Authorization", "Zoho-oauthtoken " + accessToken) - .build(); + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).GET() + .header("Authorization", "Zoho-oauthtoken " + accessToken).build(); - HttpResponse response = httpClient.send( - request, - HttpResponse.BodyHandlers.ofString()); + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - logger.info("│ ├── Response statusCode=" + response.statusCode()); + logger.info("│ ├── Response statusCode=" + response.statusCode()); - // 1. Response-Logging für Debugging - logger.info("│ ├── Response Body=" + response.body()); + // 1. Response-Logging für Debugging + logger.info("│ ├── Response Body=" + response.body()); - if (response.statusCode() >= 300) { + if (response.statusCode() >= 300) { - throw new RuntimeException("API request failed with status code: " + response.statusCode()); - } + throw new RuntimeException("API request failed with status code: " + response.statusCode()); + } - } catch (IOException | InterruptedException e) { - throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage()); - } + } catch (IOException | InterruptedException e) { + throw new InvalidAccessException("Unable to resolve access tokens: " + e.getMessage()); + } - } + } } diff --git a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/zoho/test.json b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/zoho/test.json index 831eaed..630cd66 100644 --- a/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/zoho/test.json +++ b/office-alexander-logistics-app/src/test/java/com/alexanderlogistics/zoho/test.json @@ -1,97 +1,97 @@ { - "code": 0, - "message": "success", - "invoices": [ + "code" : 0, + "message" : "success", + "invoices" : [ { - "ach_payment_initiated": false, - "invoice_id": "777249000000055583", - "zcrm_potential_id": "", - "customer_id": "777249000000055569", - "zcrm_potential_name": "", - "customer_name": "Mr. Foo", - "company_name": "", - "status": "overdue", - "invoice_number": "INV-000001", - "reference_number": "23452345", - "date": "2025-04-21", - "due_date": "2025-04-21", - "due_days": "Overdue by 3 days", - "email": "ralph.soika@imixs.com", - "project_name": "", - "billing_address": { - "address": "", - "street2": "", - "city": "", - "state": "", - "zipcode": "", - "country": "", - "phone": "", - "fax": "", - "attention": "" + "ach_payment_initiated" : false, + "invoice_id" : "777249000000055583", + "zcrm_potential_id" : "", + "customer_id" : "777249000000055569", + "zcrm_potential_name" : "", + "customer_name" : "Mr. Foo", + "company_name" : "", + "status" : "overdue", + "invoice_number" : "INV-000001", + "reference_number" : "23452345", + "date" : "2025-04-21", + "due_date" : "2025-04-21", + "due_days" : "Overdue by 3 days", + "email" : "ralph.soika@imixs.com", + "project_name" : "", + "billing_address" : { + "address" : "", + "street2" : "", + "city" : "", + "state" : "", + "zipcode" : "", + "country" : "", + "phone" : "", + "fax" : "", + "attention" : "" }, - "shipping_address": { - "address": "", - "street2": "", - "city": "", - "state": "", - "zipcode": "", - "country": "", - "phone": "", - "fax": "", - "attention": "" + "shipping_address" : { + "address" : "", + "street2" : "", + "city" : "", + "state" : "", + "zipcode" : "", + "country" : "", + "phone" : "", + "fax" : "", + "attention" : "" }, - "country": "", - "phone": "", - "created_by": "Ralph Soika", - "total": 50.00, - "balance": 50.00, - "payment_expected_date": "", - "custom_fields": [], - "custom_field_hash": {}, - "tags": [], - "salesperson_name": "", - "shipping_charge": 0.00, - "adjustment": 0.00, - "created_time": "2025-04-21T13:36:26+0200", - "last_modified_time": "2025-04-21T13:42:56+0200", - "updated_time": "2025-04-21T13:42:56+0200", - "is_viewed_by_client": false, - "has_attachment": true, - "client_viewed_time": "", - "is_viewed_in_mail": false, - "is_emailed": true, - "mail_first_viewed_time": "", - "mail_last_viewed_time": "", - "is_correction_invoice": false, - "color_code": "", - "current_sub_status_id": "", - "current_sub_status": "overdue", - "currency_id": "777249000000000071", - "schedule_time": "", - "currency_code": "EUR", - "currency_symbol": "€", - "template_type": "standard", - "invoice_url": "https://zohosecurepay.eu/books/imixs/secure?CInvoiceID=2-64277fbd3cd8bc86f8e581a3a7e1544618c191c094de1ae938827291b0828c92b5f796a1ec94bfb1fdcce7621ddf18bf61770563d9fd283207b7fa0e24d232a416b0b5e010c61cd7 ", - "transaction_type": "renewal", - "reminders_sent": 0, - "last_reminder_sent_date": "", - "last_payment_date": "", - "template_id": "777249000000000103", - "documents": "", - "salesperson_id": "", - "write_off_amount": 0.00, - "exchange_rate": 1.00, - "unprocessed_payment_amount": 0.00 + "country" : "", + "phone" : "", + "created_by" : "Ralph Soika", + "total" : 50.0, + "balance" : 50.0, + "payment_expected_date" : "", + "custom_fields" : [ ], + "custom_field_hash" : { }, + "tags" : [ ], + "salesperson_name" : "", + "shipping_charge" : 0.0, + "adjustment" : 0.0, + "created_time" : "2025-04-21T13:36:26+0200", + "last_modified_time" : "2025-04-21T13:42:56+0200", + "updated_time" : "2025-04-21T13:42:56+0200", + "is_viewed_by_client" : false, + "has_attachment" : true, + "client_viewed_time" : "", + "is_viewed_in_mail" : false, + "is_emailed" : true, + "mail_first_viewed_time" : "", + "mail_last_viewed_time" : "", + "is_correction_invoice" : false, + "color_code" : "", + "current_sub_status_id" : "", + "current_sub_status" : "overdue", + "currency_id" : "777249000000000071", + "schedule_time" : "", + "currency_code" : "EUR", + "currency_symbol" : "€", + "template_type" : "standard", + "invoice_url" : "https://zohosecurepay.eu/books/imixs/secure?CInvoiceID=2-64277fbd3cd8bc86f8e581a3a7e1544618c191c094de1ae938827291b0828c92b5f796a1ec94bfb1fdcce7621ddf18bf61770563d9fd283207b7fa0e24d232a416b0b5e010c61cd7 ", + "transaction_type" : "renewal", + "reminders_sent" : 0, + "last_reminder_sent_date" : "", + "last_payment_date" : "", + "template_id" : "777249000000000103", + "documents" : "", + "salesperson_id" : "", + "write_off_amount" : 0.0, + "exchange_rate" : 1.0, + "unprocessed_payment_amount" : 0.0 } ], - "page_context": { - "page": 1, - "per_page": 200, - "has_more_page": false, - "report_name": "Invoices", - "applied_filter": "Status.All", - "custom_fields": [], - "sort_column": "created_time", - "sort_order": "D" + "page_context" : { + "page" : 1, + "per_page" : 200, + "has_more_page" : false, + "report_name" : "Invoices", + "applied_filter" : "Status.All", + "custom_fields" : [ ], + "sort_column" : "created_time", + "sort_order" : "D" } -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index 1f3abcc..581cf6b 100644 --- a/pom.xml +++ b/pom.xml @@ -128,8 +128,45 @@ ${project.build.sourceEncoding} - + + + net.revelc.code.formatter + formatter-maven-plugin + 2.26.0 + + + https://raw.githubusercontent.com/imixs/imixs-workflow/refs/heads/master/imixs-code-style.xml + LF + + + + validate + + format + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.0 + + + https://raw.githubusercontent.com/imixs/imixs-workflow/master/imixs-checkstyle-8.44.xml + true + + + + verify + + check + + + + + office-alexander-logistics diff --git a/workflow/businesspartner-en-1.0.0.bpmn b/workflow/businesspartner-en-1.0.0.bpmn index 40411b3..e4b4991 100644 --- a/workflow/businesspartner-en-1.0.0.bpmn +++ b/workflow/businesspartner-en-1.0.0.bpmn @@ -74,6 +74,7 @@ event_kT136g event_jkHxJA event_zzWQSA + event_k4Ms1Q @@ -155,6 +156,7 @@ sequenceFlow_03b60w sequenceFlow_u8PLGw sequenceFlow_ZTiO7w + sequenceFlow_gIdPow @@ -719,7 +721,40 @@ + + + + + + + + + + + SOA Template + + soa_dbtr.number_$lasteventdate.xlsx + + + F6 + $lasteventdate + date + + + A7 + dbtr.name +]]> + + + + + sequenceFlow_gIdPow + + + + + @@ -1070,6 +1105,18 @@ + + + + + + + + + + + +