From 16c91089b0c9b893537621853ee545dfc5b8f00e Mon Sep 17 00:00:00 2001 From: Ralph Soika Date: Wed, 18 Sep 2024 15:22:19 +0200 Subject: [PATCH] update monitoring --- .../AGLAnalyticController.java | 190 ++++++++++++++++-- .../AnalyticController.java | 121 ----------- .../office/forms/AnalyticController.java | 169 ++++++++++++++++ .../workflow/office/forms}/AnalyticEvent.java | 11 +- .../parts/alexander/analyze_chart.xhtml | 4 +- .../parts/alexander/analyze_plain.xhtml | 16 +- workflow/analyse-zolleust-de-1.0.0.bpmn | 4 +- 7 files changed, 376 insertions(+), 139 deletions(-) delete mode 100644 office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticController.java create mode 100644 office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/forms/AnalyticController.java rename office-alexander-logistics-app/src/main/java/{com/alexanderlogistics => org/imixs/workflow/office/forms}/AnalyticEvent.java (84%) 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 index bfd2bf6..3cb43d2 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticController.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AGLAnalyticController.java @@ -1,6 +1,7 @@ package com.alexanderlogistics; import java.io.Serializable; +import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; @@ -8,9 +9,12 @@ import java.util.List; import java.util.Map; import java.util.logging.Logger; +import org.imixs.marty.team.TeamService; import org.imixs.workflow.ItemCollection; import org.imixs.workflow.engine.DocumentService; import org.imixs.workflow.exceptions.QueryException; +import org.imixs.workflow.office.forms.AnalyticController; +import org.imixs.workflow.office.forms.AnalyticEvent; import jakarta.enterprise.context.RequestScoped; import jakarta.enterprise.event.Observes; @@ -37,28 +41,38 @@ public class AGLAnalyticController implements Serializable { @Inject protected DocumentService documentService; + @Inject + TeamService teamService; + @Inject protected DebitorStatistikController debitorStatistikController; List steuerbescheide = null; public void onEvent(@Observes AnalyticEvent event) { + ItemCollection process = teamService.getProcessByName("Mahnwesen"); + String link = "/pages/workitems/worklist.xhtml?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.due".equals(event.getKey())) { event.setValue(computeSteuerbescheideUeberfaellig()); event.setLabel("EUR"); event.setDescription("Überfällige Steuerbescheide die noch nicht abgerechnet wurden."); + event.setLink(link + "&workflowstatus=Überfällig"); } if ("analytic.steuer.trend".equals(event.getKey())) { @@ -66,6 +80,13 @@ public class AGLAnalyticController implements Serializable { 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"); + } + } /** @@ -82,13 +103,6 @@ public class AGLAnalyticController implements Serializable { for (ItemCollection steuerItemCol : list) { result = result + getZollBySteuerBeleg(steuerItemCol, "EUST"); - // 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); @@ -201,6 +215,7 @@ public class AGLAnalyticController implements Serializable { // 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 @@ -213,7 +228,7 @@ public class AGLAnalyticController implements Serializable { SteuerData steuerDataByWeek = saldoByWeek.getOrDefault(yearWeek, new SteuerData()); double eust = getZollBySteuerBeleg(steuerItemCol, "EUST"); double zoll = getZollBySteuerBeleg(steuerItemCol, "ZOLL"); - steuerDataByWeek.add(eust, zoll, saldo); + steuerDataByWeek.add(eust, zoll, total, saldo, ""); saldoByWeek.put(yearWeek, steuerDataByWeek); @@ -283,7 +298,155 @@ public class AGLAnalyticController implements Serializable { } /** - * Helper method to hold zoll and eust totls in an object. Needed to build + * Berechnet Chart daten aller Steuerbescheide bei KW + * + *
+	 * 
+	   chartCfg = {
+		  type: 'bar',
+		  data: {
+			datasets: [{
+			  data: [20, 10],
+			}],
+			labels: ['1', '2']
+		  }
+	    }
+	 * 
+	 * 
+ * + * @return + */ + private String accumulateSteuerbescheideAbteilungByWeek() { + logger.info("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.info(".... " + 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.info(" " + 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 { @@ -291,6 +454,7 @@ public class AGLAnalyticController implements Serializable { double zoll = 0.0; double eust = 0.0; double saldo = 0.0; + String abteilung = ""; public SteuerData() { // default constructor @@ -302,12 +466,12 @@ public class AGLAnalyticController implements Serializable { * @param eustNew eust value to be added * @param zollNew zoll value to be added */ - public void add(double eustNew, double zollNew, double saldoNew) { + public void add(double eustNew, double zollNew, double totalNew, double saldoNew, String abteilungNew) { eust = eust + eustNew; zoll = zoll + zollNew; saldo = saldo + saldoNew; - // update total - total = eust + zoll; + total = total + totalNew; + abteilung = abteilungNew; } } 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 deleted file mode 100644 index a73cc7a..0000000 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticController.java +++ /dev/null @@ -1,121 +0,0 @@ -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/org/imixs/workflow/office/forms/AnalyticController.java b/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/forms/AnalyticController.java new file mode 100644 index 0000000..88b6697 --- /dev/null +++ b/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/forms/AnalyticController.java @@ -0,0 +1,169 @@ +package org.imixs.workflow.office.forms; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.imixs.workflow.ItemCollection; +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) { + ItemCollection analyticData = computeValue(key); + return analyticData.getItemValueString("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 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"); + } + + /** + * Computes am analytic value. The method cache the value in the item + * 'analytic.KEY' to avoid feierring repeated AnalyticEvents. + * + * @param key + * @return + */ + protected ItemCollection 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); + ItemCollection details = new ItemCollection(); + details.setItemValue("value", event.getValue()); + details.setItemValue("label", event.getLabel()); + details.setItemValue("description", event.getDescription()); + details.setItemValue("link", event.getLink()); + implodeDetails(key, details); + return details; + } + + if (event.getValue() == null) { + // set dummy value + ItemCollection details = new ItemCollection(); + details.setItemValue("value", ""); + details.setItemValue("label", ""); + details.setItemValue("description", "No data available"); + implodeDetails(key, details); + return details; + } + } + } + + // analytic value is already cached! + return explodeDetails(key); + } + + /** + * Convert the List of ItemCollections back into a List of Map elements + * + * @param workitem + */ + @SuppressWarnings({ "rawtypes" }) + public 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" }) + public 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 + return new ItemCollection(); + } + +} diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticEvent.java b/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/forms/AnalyticEvent.java similarity index 84% rename from office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticEvent.java rename to office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/forms/AnalyticEvent.java index 146aad7..365583a 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/AnalyticEvent.java +++ b/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/forms/AnalyticEvent.java @@ -1,4 +1,4 @@ -package com.alexanderlogistics; +package org.imixs.workflow.office.forms; import org.imixs.workflow.ItemCollection; @@ -11,6 +11,7 @@ public class AnalyticEvent { private Object value = null; private String label = ""; private String description = ""; + private String link = ""; private ItemCollection workitem = null; public AnalyticEvent(String key, ItemCollection workitem) { @@ -26,6 +27,14 @@ public class AnalyticEvent { this.key = key; } + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } + public ItemCollection getWorkitem() { return workitem; } 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 index 7a9fc76..e68b854 100644 --- 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 @@ -38,7 +38,7 @@
- +
@@ -55,7 +55,7 @@ $(document).ready( function () { // update the diagram - var ctx = document.getElementById("analytics_canvas").getContext("2d"); + var ctx = document.getElementById("analytics_canvas_#{item.name}").getContext("2d"); var chartData = #{ analyticController.getAsString(item.name) }; console.log(chartData); 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 index c256fdc..daddd50 100644 --- 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 @@ -34,6 +34,12 @@ flex: 1; font-size: 20px; } + + .analytic-card-link { + float: right; + font-size: 18px; + margin-left: 20px; + }
@@ -46,8 +52,16 @@
#{analyticController.getLabel(item.name)}
- \ No newline at end of file diff --git a/workflow/analyse-zolleust-de-1.0.0.bpmn b/workflow/analyse-zolleust-de-1.0.0.bpmn index cb74177..20d0de8 100644 --- a/workflow/analyse-zolleust-de-1.0.0.bpmn +++ b/workflow/analyse-zolleust-de-1.0.0.bpmn @@ -180,7 +180,9 @@ th { font-weight: bold;} - + + + ]]>