From b3d0efad643fa8e6719bafdc0cd0ba78fb17e9e9 Mon Sep 17 00:00:00 2001 From: Ralph Soika Date: Sat, 14 Sep 2024 14:01:20 +0200 Subject: [PATCH] agl Analyse Steuerbescheide --- .../AGLAnalyticController.java | 245 ++++++++++ .../AnalyticController.java | 121 +++++ .../com/alexanderlogistics/AnalyticEvent.java | 57 +++ .../parts/alexander/analyze_chart.xhtml | 69 +++ .../parts/alexander/analyze_plain.xhtml | 53 +++ ...TC401471770920244851_20240912064210939.xml | 60 +++ workflow/analyse-de-1.0.0.bpmn | 420 ++++++++++++++++++ 7 files changed, 1025 insertions(+) create mode 100644 office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticController.java create mode 100644 office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticController.java create mode 100644 office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticEvent.java create mode 100644 office-alexander-logistics-app/src/main/webapp/pages/workitems/parts/alexander/analyze_chart.xhtml create mode 100644 office-alexander-logistics-app/src/main/webapp/pages/workitems/parts/alexander/analyze_plain.xhtml create mode 100644 office-alexander-logistics-app/src/test/resources/cargosoft/Abgabenbescheid-IM-GCA-2409-021N-005-ATC401471770920244851_20240912064210939.xml create mode 100644 workflow/analyse-de-1.0.0.bpmn diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticController.java new file mode 100644 index 0000000..7994eb0 --- /dev/null +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticController.java @@ -0,0 +1,245 @@ +package com.alexanderlogistics; + +import java.io.Serializable; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.imixs.workflow.ItemCollection; +import org.imixs.workflow.engine.DocumentService; +import org.imixs.workflow.exceptions.QueryException; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; + +/** + * Der AGLAnalyticController berechnet verschiedene Analyse Daten + * + * @author rsoika + * + */ +@Named +@RequestScoped +public class AGLAnalyticController implements Serializable { + + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(AnalyticController.class.getName()); + + @Inject + protected DocumentService documentService; + + @Inject + protected DebitorStatistikController debitorStatistikController; + + List steuerbescheide = null; + + public void onEvent(@Observes AnalyticEvent event) { + 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()); + + } + if ("analytic.steuer.zoll".equals(event.getKey())) { + event.setValue(computeSteuerbescheideZoll()); + event.setLabel("EUR"); + event.setDescription("Gesamt Zölle (ZOLLEU)"); + } + if ("analytic.steuer.due".equals(event.getKey())) { + event.setValue(computeSteuerbescheideUeberfaellig()); + event.setLabel("EUR"); + event.setDescription("Überfällige Steuerbescheide die noch nicht abgerechnet wurden."); + } + + if ("analytic.steuer.trend".equals(event.getKey())) { + event.setValue(computeSteuerbescheideByWeek()); + event.setLabel("Zoll & EUST"); + event.setDescription("Fälligkeiten nach KW"); + } + } + + /** + * 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.info("compute EUST..."); + List list = getSteuerbescheide(); + double result = 0; + + for (ItemCollection steuerItemCol : list) { + List positionsTabelle = InvoiceUtil.explodeChildList(steuerItemCol); + for (ItemCollection pos : positionsTabelle) { + if ("EUST".equalsIgnoreCase(pos.getItemValueString("activity.type"))) { + result = result + pos.getItemValueDouble("amount"); + } + } + + } + return InvoiceUtil.round(result); + } + + /** + * Berechnet die Summe aller überfälligen steuer Beträge $taskID=1200 + * + * @return + */ + private double computeSteuerbescheideUeberfaellig() { + logger.info("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 Summe aller offenen Zoll Beträge + * + * @return + */ + private double computeSteuerbescheideZoll() { + logger.info("compute Zoll..."); + List list = getSteuerbescheide(); + double result = 0; + + for (ItemCollection steuerItemCol : list) { + List positionsTabelle = InvoiceUtil.explodeChildList(steuerItemCol); + for (ItemCollection pos : positionsTabelle) { + if ("ZOLL".equalsIgnoreCase(pos.getItemValueString("activity.type"))) { + result = result + pos.getItemValueDouble("amount"); + } + } + + } + return InvoiceUtil.round(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 computeSteuerbescheideByWeek() { + logger.info("compute steuer by kw..."); + + List list = getSteuerbescheide(); + + Map saldoByWeek = new HashMap<>(); + // SimpleDateFormat weekFormat = new SimpleDateFormat("w"); // 'w' gives the + // week number + // SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy"); // 'yyyy' for the + // year + 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"); + 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 + saldoByWeek.put(yearWeek, saldoByWeek.getOrDefault(yearWeek, 0.0) + saldo); + + // 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 dataBuilder = 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 + dataBuilder.add(saldoByWeek.getOrDefault(yearWeek, 0.0)); + 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() + .add("data", dataBuilder))) + .add("labels", labelsBuilder)); + + // Step 4: Return the JSON structure as a string + JsonObject chartCfg = jsonBuilder.build(); + return chartCfg.toString(); // JSON as string + + } + +} diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticController.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticController.java new file mode 100644 index 0000000..a73cc7a --- /dev/null +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticController.java @@ -0,0 +1,121 @@ +package com.alexanderlogistics; + +import java.io.Serializable; +import java.util.logging.Logger; + +import org.imixs.workflow.faces.data.WorkflowController; + +import jakarta.enterprise.context.ConversationScoped; +import jakarta.enterprise.event.Event; +import jakarta.inject.Inject; +import jakarta.inject.Named; + +/** + * The AnalyticController is a conversationScoped controller that provides + * values for the analytic-custom parts. + *

+ * A custom implementation can react on AnalyticEvent to compute values and + * datasets. + *

+ * 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 + * + */ +@Named +@ConversationScoped +public class AnalyticController implements Serializable { + + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(AnalyticController.class.getName()); + + @Inject + protected Event analyticEvents; + + @Inject + protected WorkflowController workflowController; + + /** + * Returns a analytic value as a String for a given key. + * + * @param key + * @return + */ + public String getAsString(String key) { + computeValue(key); + return workflowController.getWorkitem().getItemValueString("analytic." + key); + } + + /** + * Returns a analytic value as a Double for a given key. + * + * @param key + * @return + */ + public double getAsDouble(String key) { + computeValue(key); + return workflowController.getWorkitem().getItemValueDouble("analytic." + key); + } + + /** + * Returns the analytic label for a given key + * + * @param key + * @return + */ + public String getLabel(String key) { + computeValue(key); + return workflowController.getWorkitem().getItemValueString("analytic." + key + ".label"); + } + + /** + * Returns the analytic description for a given key + * + * @param key + * @return + */ + public String getDescription(String key) { + computeValue(key); + return workflowController.getWorkitem().getItemValueString("analytic." + key + ".description"); + } + + /** + * Computes am analytic value. The method cache the value in the item + * 'analytic.KEY' to avoid feierring repeated AnalyticEvents. + * + * @param key + * @return + */ + protected void computeValue(String key) { + if (workflowController.getWorkitem() != null) { + // try to fetch value from cache + if (!workflowController.getWorkitem().hasItem("analytic." + key)) { + logger.info("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); + workflowController.getWorkitem().setItemValue("analytic." + key, event.getValue()); + workflowController.getWorkitem().setItemValue("analytic." + key + ".label", event.getLabel()); + workflowController.getWorkitem().setItemValue("analytic." + key + ".description", + event.getDescription()); + } + + if (event.getValue() == null) { + // set dummy value + workflowController.getWorkitem().setItemValue("analytic." + key, ""); + workflowController.getWorkitem().setItemValue("analytic." + key + ".label", "- undefined -"); + workflowController.getWorkitem().setItemValue("analytic." + key + ".description", + "No data available"); + } + } + } else { + // analytic value is already cached! + } + } + +} diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticEvent.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticEvent.java new file mode 100644 index 0000000..146aad7 --- /dev/null +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticEvent.java @@ -0,0 +1,57 @@ +package com.alexanderlogistics; + +import org.imixs.workflow.ItemCollection; + +/** + * An AnalyticEvent is send by the AnalyticController during initialization + */ +public class AnalyticEvent { + + private String key; + private Object value = null; + private String label = ""; + private String description = ""; + private ItemCollection workitem = null; + + public AnalyticEvent(String key, ItemCollection workitem) { + this.key = key; + this.workitem = workitem; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public ItemCollection getWorkitem() { + return workitem; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + +} diff --git a/office-alexander-logistics-app/src/main/webapp/pages/workitems/parts/alexander/analyze_chart.xhtml b/office-alexander-logistics-app/src/main/webapp/pages/workitems/parts/alexander/analyze_chart.xhtml new file mode 100644 index 0000000..7a9fc76 --- /dev/null +++ b/office-alexander-logistics-app/src/main/webapp/pages/workitems/parts/alexander/analyze_chart.xhtml @@ -0,0 +1,69 @@ + + + + + +

+
+ +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/office-alexander-logistics-app/src/main/webapp/pages/workitems/parts/alexander/analyze_plain.xhtml b/office-alexander-logistics-app/src/main/webapp/pages/workitems/parts/alexander/analyze_plain.xhtml new file mode 100644 index 0000000..c256fdc --- /dev/null +++ b/office-alexander-logistics-app/src/main/webapp/pages/workitems/parts/alexander/analyze_plain.xhtml @@ -0,0 +1,53 @@ + + + + +
+
+ + + +
+ + +
#{analyticController.getLabel(item.name)}
+ + + +
+ +
\ No newline at end of file diff --git a/office-alexander-logistics-app/src/test/resources/cargosoft/Abgabenbescheid-IM-GCA-2409-021N-005-ATC401471770920244851_20240912064210939.xml b/office-alexander-logistics-app/src/test/resources/cargosoft/Abgabenbescheid-IM-GCA-2409-021N-005-ATC401471770920244851_20240912064210939.xml new file mode 100644 index 0000000..a37b9c2 --- /dev/null +++ b/office-alexander-logistics-app/src/test/resources/cargosoft/Abgabenbescheid-IM-GCA-2409-021N-005-ATC401471770920244851_20240912064210939.xml @@ -0,0 +1,60 @@ + + + + CARGOSOFT + ATLSTB + 20240912064210939 + + 2024-09-12T06:42:10 + + + 2023.2g + 2023.2-1415 + + + + + + 2024-09-12T06:31:43 + + ATLAS + STEUERBESCHEID NEU + + + + 1 + Abgabenbescheid-IM-GCA-2409-021N-005-ATC401471770920244851.pdf + application/pdf + + 2024-09-12T19:22:46 + + 9D8A288A724FF91ED6FB688F66092B + 69533 + + + + 001 + 254374 + LR + 70259 + EUR + 2024-09-12T00:00:00 + 19049.41 + 0.00 + + ATC401471770920244851 + 202409 + 2024-09-12T00:00:00 + ATLAS Steuerbescheid + ZOLL + 3501.76 + 0 + IM-GCA-2409-021 + EUST + 15547.65 + 0 + IM-GCA-2409-021 + + + \ No newline at end of file diff --git a/workflow/analyse-de-1.0.0.bpmn b/workflow/analyse-de-1.0.0.bpmn new file mode 100644 index 0000000..470d4ee --- /dev/null +++ b/workflow/analyse-de-1.0.0.bpmn @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + +<itemvalue>$workflowstatus</itemvalue> + +]]> + + + +]]> + + + + + + + + + + + + + + StartEvent_1 + Task_1 + IntermediateCatchEvent_3 + EndEvent_3 + Task_2 + IntermediateCatchEvent_8 + + DataObject_1 + + + + SequenceFlow_4 + + + + + + $lasteventdate - dbtr.name (dbtr.number)]]> + + + true + + + + + + + + + + + + + + + + + + $workflowgroup - $workflowstatus]]> + SequenceFlow_10 + sequenceFlow_OL83kQ + + + + + + SequenceFlow_10 + + + + + + $lasteventdate - dbtr.name (dbtr.number)]]> + + + + + + + + + + + + true + + + + + + + + + + + + + + $workflowgroup - $workflowstatus]]> + SequenceFlow_14 + SequenceFlow_4 + sequenceFlow_17AQjg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + SequenceFlow_14 + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + sequenceFlow_17AQjg + sequenceFlow_OL83kQ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +