From 51ccd40f18c6c3f0ee0a941752f16db6e3eea935 Mon Sep 17 00:00:00 2001 From: Ralph Soika Date: Sat, 1 Feb 2025 12:49:05 +0100 Subject: [PATCH] refactoring inmemory metrics --- .../metrics/MetricCreditorRestService.java | 82 +------- .../metrics/MetricCreditorService.java | 188 +++++++----------- .../metrics/MetricDataService.java | 60 ------ .../metrics/MetricDebitorRestService.java | 93 +-------- .../metrics/MetricDebitorService.java | 180 +++++++---------- 5 files changed, 148 insertions(+), 455 deletions(-) diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorRestService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorRestService.java index 773252a..c7ee05e 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorRestService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorRestService.java @@ -1,6 +1,5 @@ package com.alexanderlogistics.metrics; -import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; @@ -15,7 +14,6 @@ import jakarta.ejb.Stateless; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @@ -62,9 +60,6 @@ public class MetricCreditorRestService { // first clear the metric cache metricCreditorService.reset(); log("│   ├── reset metric cache", messageBuffer); - // run in new transaction! - metricDataService.deleteAllMetrics(MetricCreditorService.TYPE_METRIC_CREDITOR); - log("│   ├── delete metrics", messageBuffer); computeMetrics(); log("│   ├── computing metrics finished in " + (System.currentTimeMillis() - l) + "ms", messageBuffer); @@ -87,81 +82,6 @@ public class MetricCreditorRestService { } } - /** - * This method initializes the metrics for all creditors with open invoices. - * - * The method deletes all existing metrics and creates new metric entires for - * each creditor. - * - * @return - */ - @GET - @Path("/rebuild/{cdtrnumber}") - @Produces({ MediaType.TEXT_PLAIN }) - public Response rebuildMetricsForCreditor(@PathParam("cdtrnumber") String cdtrnumber) { - StringBuffer messageBuffer = new StringBuffer(); - long l = System.currentTimeMillis(); - log("├── init cdtr metrics for " + cdtrnumber + "...", messageBuffer); - try { - - logger.info("│   │   ├── group invoices by creditor " + cdtrnumber + "..."); - - int count = 0; - - // First we need to remove all metrics for this creditor - String bpID = InvoiceUtil.buildBPID(cdtrnumber); - logger.info("│   │   ├── delete metrics for " + bpID + "..."); - List oldMetricKeys = metricDataService - .deleteAllMetricsByBPID(MetricCreditorService.TYPE_METRIC_CREDITOR, bpID); - logger.info("│   │   ├── found " + oldMetricKeys.size() + " metrics for " + bpID + "..."); - metricCreditorService.reset(oldMetricKeys); - // next rebuild gauges and metrics for this creditor - List invoices = documentService.find( - "($modelversion:rechnungseingang-* OR $modelversion:gutschriftabgleich-*) " + - " AND type:workitem AND cdtr.number:" + cdtrnumber, - 9999, 0, "invoice.number", false); - logger.info("│   │   ├──found " + invoices.size() + " open invoices"); - - List newMetricKeys = new ArrayList<>(); - for (ItemCollection invoice : invoices) { - try { - ItemCollection metricData = metricCreditorService.getMetricByInvoice(invoice); - newMetricKeys.add(metricData.getItemValueString("name")); - // Jetzt Rechnung addieren - metricCreditorService.addInvoice(metricData, invoice); - logger.info("│   │   │   ├──update metric " + InvoiceUtil.getBPId(invoice) + " Invoice: " - + invoice.getItemValueString("invoice.number") + " Saldo: " - + invoice.getItemValueDouble(MetricCreditorService.ITEM_TOTAL)); - - metricCreditorService.putMetric(metricData); - count++; - } catch (PluginException e) { - // invalid invoice - e.g. no cdtr. number - } - } - - logger.info("│   │   ├── updated metric for " + count + " invoices."); - - log("│   ├── computing metrics finished in " + (System.currentTimeMillis() - l) + "ms", messageBuffer); - - logger.info("│   ├── save and init metrics..."); - // run in new transaction! - metricCreditorService.refreshGauges(newMetricKeys); - - String message = "├── rebuild cdtr metrics completed in " - + (System.currentTimeMillis() - l) - + "ms"; - log(message, messageBuffer); - return Response.ok().entity(messageBuffer.toString()).build(); - - } catch (Exception e) { - e.printStackTrace(); - return Response.serverError() - .entity("Failed to initialize metrics: " + e.getMessage()) - .build(); - } - } - /** * Diese Methode berechnet alle Metriken auf basis der existierenden Rechnungen * neu @@ -184,7 +104,7 @@ public class MetricCreditorRestService { // Jetzt Rechnung addieren metricCreditorService.addInvoice(metricData, invoice); logger.info("│   │   │   ├──update metric " + InvoiceUtil.getBPId(invoice)); - metricCreditorService.putMetric(metricData); + metricCreditorService.updateMetric(metricData); count++; } catch (PluginException e) { // invalid invoice - e.g. no cdtr. number diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorService.java index 1905456..867bb2a 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorService.java @@ -16,7 +16,6 @@ import org.imixs.workflow.ItemCollection; import org.imixs.workflow.engine.DocumentService; import org.imixs.workflow.engine.ProcessingEvent; import org.imixs.workflow.exceptions.PluginException; -import org.imixs.workflow.exceptions.QueryException; import com.alexanderlogistics.InvoiceUtil; import com.alexanderlogistics.KreditorDebitorService; @@ -24,8 +23,6 @@ import com.alexanderlogistics.KreditorDebitorService; import jakarta.annotation.security.DeclareRoles; import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RunAs; -import jakarta.ejb.TransactionAttribute; -import jakarta.ejb.TransactionAttributeType; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; @@ -60,8 +57,10 @@ public class MetricCreditorService { private final Set registeredGauges = ConcurrentHashMap.newKeySet(); public static final String TYPE_METRIC_CREDITOR = "metric.creditor"; - public static final String ITEM_TOTAL = "invoice.total"; - public static final String ITEM_SALDO = "invoice.saldo"; + public static final String ITEM_INVOICE_TOTAL = "invoice.total"; + public static final String ITEM_INVOICE_SALDO = "invoice.saldo"; + public static final String ITEM_METRIC_BALANCE = "invoice.balance"; + public static final String ITEM_METRIC_SALES = "invoice.sales"; @Inject @RegistryScope(scope = MetricRegistry.APPLICATION_SCOPE) @@ -88,17 +87,6 @@ public class MetricCreditorService { registeredGauges.clear(); } - /** - * Reset the internal metricCache and clears all registered Gauges for a list of - * metric keys. - */ - public void reset(List metricKeys) { - for (String metricKey : metricKeys) { - metricCache.remove(metricKey); - metricRegistry.remove(metricKey); - } - } - /** * Process Metric only if some data has changed.... * @@ -124,9 +112,11 @@ public class MetricCreditorService { if (lastInvoice != null) { try { ItemCollection lastMetricData = getMetricByInvoice(lastInvoice); - subtractInvoice(lastMetricData, lastInvoice); - putMetric(lastMetricData); - metricDataService.saveMetric(lastMetricData); + // update last metric only if exists... + if (!isNewMetric(lastMetricData)) { + subtractInvoice(lastMetricData, lastInvoice); + updateMetric(lastMetricData); + } } catch (PluginException e) { // invalid invoice - e.g. no cdtr. number } @@ -137,11 +127,7 @@ public class MetricCreditorService { ItemCollection metricData = getMetricByInvoice(invoice); // Saldo-Berechnung addInvoice(metricData, invoice); - putMetric(metricData); - metricDataService.saveMetric(metricData); - - // Update the Gauge - updateGauge(metricData); + updateMetric(metricData); logger.info("Metric cdtr update took " + (System.currentTimeMillis() - l) + "ms"); } catch (PluginException e) { // invalid invoice - e.g. no cdtr. number @@ -150,6 +136,17 @@ public class MetricCreditorService { } + /** + * Returns true if the metric is not yet registered. This means we do not have + * sales or balances for this metric + * + * @param metricData + * @return + */ + public boolean isNewMetric(ItemCollection metricData) { + return (!metricCache.containsKey(metricData.getItemValueString("name"))); + } + /** * Returns the corresponding metric data object for an invoice. The method uses * an internal cache. If the metric was not yet cached the method loads the @@ -169,12 +166,9 @@ public class MetricCreditorService { } String metricKey = MetricDataService.buildKeyByInvoice(invoice); ItemCollection metricData = metricCache.get(metricKey); - if (metricData == null) { - metricData = loadMetric(invoice); - } // if metric still null we create a new metric data object. if (metricData == null) { - metricData = createMetaData(invoice); + metricData = createMetricData(invoice); } return metricData; } @@ -189,15 +183,6 @@ public class MetricCreditorService { return metricCache.get(key); } - /** - * Puts a metric data object into the cache. - * - * @param metricData - */ - public void putMetric(ItemCollection metricData) { - metricCache.put(metricData.getItemValueString("name"), metricData); - } - /** * Returns a list with all cached metric keys. * The method returns an unmodifiable list to prevent modifications. @@ -209,44 +194,17 @@ public class MetricCreditorService { } /** - * This method loads a metric entity for a given invoice workitem. If no metric - * entity exits, the method - * creates a new metric entity. - * - * @param invoice - * @return - * @throws PluginException - */ - private ItemCollection loadMetric(ItemCollection invoice) throws PluginException { - ItemCollection creditorMetric = null; - if (invoice == null) { - return null; - } - try { - String metricKey = MetricDataService.buildKeyByInvoice(invoice); - String query = "(type:" + TYPE_METRIC_CREDITOR + ") AND (name:" + metricKey + ")"; - List result = documentService.find(query, 1, 0, "$modified", true); - if (result.size() > 0) { - creditorMetric = result.get(0); - } - } catch (IllegalArgumentException | QueryException e) { - throw new PluginException(PluginException.class.getName(), - "Failed to load metric object for invoice " + invoice.getUniqueID() + ": ", e.getMessage(), e); - } - return creditorMetric; - } - - /** - * Creates an empty Creditor Meta Data Object (ItemCollection) + * Creates an empty Creditor Metric Data Object (ItemCollection) *

- * The ItemCollection stores the name and number and also all saldos for all - * currencies + * The ItemCollection stores the name and number and all categories. + * A new metric object does not yet have the items 'invoice.saldo' and + * 'invoice.total' * * @param invoice - invoice ItemCollection * @return * @throws PluginException */ - private ItemCollection createMetaData(ItemCollection invoice) throws PluginException { + private ItemCollection createMetricData(ItemCollection invoice) throws PluginException { if (invoice == null) { return null; } @@ -269,17 +227,20 @@ public class MetricCreditorService { * @param cdtrNumber - the creditor number * @param cdtrName - the creditor name */ - public void updateGauge(ItemCollection metricData) { + public void updateMetric(ItemCollection metricData) { String metricKey = metricData.getItemValueString("name"); - String bpName = metricData.getItemValueString("bp.name"); - String bpId = metricData.getItemValueString("bp.id"); - String country = metricData.getItemValueString("country"); - String department = metricData.getItemValueString("department"); - String currency = metricData.getItemValueString("currency"); + // Cache aktualisieren + metricCache.put(metricKey, metricData); // Prüfen ob Gauge bereits registriert ist if (registeredGauges.add(metricKey)) { // returns true newly added + String bpName = metricData.getItemValueString("bp.name"); + String bpId = metricData.getItemValueString("bp.id"); + String country = metricData.getItemValueString("country"); + String department = metricData.getItemValueString("department"); + String currency = metricData.getItemValueString("currency"); + List tags = new ArrayList<>(); tags.add(new Tag("type", "cdtr")); tags.add(new Tag("id", bpId)); @@ -287,20 +248,24 @@ public class MetricCreditorService { tags.add(new Tag("country", country)); tags.add(new Tag("currency", currency)); tags.add(new Tag("department", department)); - logger.fine("register new metric for department: " + department + - ", " + metricData.getItemValueString(ITEM_SALDO) + - " " + currency); - Metadata metadata = Metadata.builder() + + // Saldo Gauge + Metadata balanceMetadata = Metadata.builder() .withName("cdtr.balance") .withDescription("Creditor Balance") .build(); - - metricCache.get(metricKey); - metricRegistry.gauge(metadata, - () -> metricCache.get(metricKey).getItemValueDouble(ITEM_SALDO), + metricRegistry.gauge(balanceMetadata, + () -> metricCache.get(metricKey).getItemValueDouble(ITEM_METRIC_BALANCE), + tags.toArray(new Tag[0])); + + // Umsatz Gauge + Metadata revenueMetadata = Metadata.builder() + .withName("cdtr.sales") + .withDescription("Creditor Sales") + .build(); + metricRegistry.gauge(revenueMetadata, + () -> metricCache.get(metricKey).getItemValueDouble(ITEM_METRIC_SALES), tags.toArray(new Tag[0])); - } else { - logger.fine("Cdtr Gauge already registered for department: " + department); } } @@ -312,57 +277,54 @@ public class MetricCreditorService { * @param invoice */ public void addInvoice(ItemCollection metricData, ItemCollection invoice) { - double invoiceTotal = invoice.getItemValueDouble(ITEM_TOTAL); + if (metricData == null || invoice == null) { + return; + } + double invoiceSaldo = invoice.getItemValueDouble(ITEM_INVOICE_TOTAL); + double invoiceTotal = invoice.getItemValueDouble(ITEM_INVOICE_TOTAL); if (!"workitem".equals(invoice.getType()) || invoice.getTaskID() >= 5800) { // vorgang ist archiviert oder gelöscht worden => saldo = 0! - invoiceTotal = 0.0; - } - // is the metric new? - if (!metricData.hasItem(ITEM_SALDO)) { - // init metric with the total value! - metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(invoiceTotal)); - } else { - double lastSaldo = metricData.getItemValueDouble(ITEM_SALDO); - metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(lastSaldo + invoiceTotal)); + invoiceSaldo = 0.0; } + // update balance + double lastBalance = metricData.getItemValueDouble(ITEM_METRIC_BALANCE); + metricData.setItemValue(ITEM_METRIC_BALANCE, InvoiceUtil.round(lastBalance + invoiceSaldo)); + + // update sales + double lastTotal = metricData.getItemValueDouble(ITEM_METRIC_SALES); + metricData.setItemValue(ITEM_METRIC_SALES, InvoiceUtil.round(lastTotal + invoiceTotal)); + } public void subtractInvoice(ItemCollection metricData, ItemCollection invoice) { if (metricData == null || invoice == null) { return; } - double invoiceTotal = invoice.getItemValueDouble(ITEM_TOTAL); + double invoiceTotal = invoice.getItemValueDouble(ITEM_INVOICE_TOTAL); + double invoiceSaldo = invoice.getItemValueDouble(ITEM_INVOICE_TOTAL); if (!"workitem".equals(invoice.getType()) || invoice.getTaskID() >= 5800) { // vorgang ist archiviert oder gelöscht worden => saldo = 0! - invoiceTotal = 0.0; + invoiceSaldo = 0.0; } // subtract only if metric saldo exists - if (metricData.hasItem(ITEM_SALDO)) { - double lastSaldo = metricData.getItemValueDouble(ITEM_SALDO); - metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(lastSaldo - invoiceTotal)); - } + double lastBalance = metricData.getItemValueDouble(ITEM_METRIC_BALANCE); + metricData.setItemValue(ITEM_METRIC_BALANCE, InvoiceUtil.round(lastBalance - invoiceSaldo)); + + // Neue Umsatz Logik + double lastSales = metricData.getItemValueDouble(ITEM_METRIC_SALES); + metricData.setItemValue(ITEM_METRIC_SALES, InvoiceUtil.round(lastSales - invoiceTotal)); + } /** * Helper Method that refreshes all gauges. The method is called by the * RestService during a rebuild. */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) public void refreshGauges() { List keys = getMetricKeys(); - refreshGauges(keys); - } - - /** - * Helper Method that refreshes all gauges. The method is called by the - * RestService during a rebuild. - */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public void refreshGauges(List keys) { for (String hashKey : keys) { ItemCollection metricData = getMetric(hashKey); - documentService.save(metricData); - updateGauge(metricData); + updateMetric(metricData); } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDataService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDataService.java index aa0b03e..52fbf0d 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDataService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDataService.java @@ -1,14 +1,10 @@ package com.alexanderlogistics.metrics; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; import org.imixs.workflow.ItemCollection; import org.imixs.workflow.engine.DocumentService; -import org.imixs.workflow.engine.index.SearchService; import org.imixs.workflow.exceptions.PluginException; -import org.imixs.workflow.exceptions.QueryException; import com.alexanderlogistics.InvoiceUtil; @@ -34,15 +30,6 @@ public class MetricDataService { @Inject DocumentService documentService; - /** - * Runs with Manager access - * - * @param metricData - */ - public void saveMetric(ItemCollection metricData) { - documentService.save(metricData); - } - /** * This helper method reads a 'dirty' workitem in a new transaction. This is * used for calculating the new metric values @@ -55,53 +42,6 @@ public class MetricDataService { return dirtyInvoice; } - /** - * This method deletes all metrics - * - * @throws PluginException - * - */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public void deleteAllMetrics(String metricType) throws PluginException { - try { - String query = "(type:" + metricType + ")"; - List result = documentService.find(query, SearchService.DEFAULT_MAX_SEARCH_RESULT, 0); - for (ItemCollection metric : result) { - documentService.remove(metric); - } - - } catch (IllegalArgumentException | QueryException e) { - throw new PluginException(MetricDataService.class.getName(), - "Failed to delete metrics", e.getMessage(), e); - } - } - - /** - * This method deletes all metrics for a given BP ID - * - * @throws PluginException - * - */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public List deleteAllMetricsByBPID(String metricType, String bpID) throws PluginException { - List result = new ArrayList<>(); - try { - String query = "(type:" + metricType + ")"; - List metricList = documentService.find(query, SearchService.DEFAULT_MAX_SEARCH_RESULT, 0); - for (ItemCollection metric : metricList) { - if (metric.getItemValueString("bp.id").equals(bpID)) { - result.add(metric.getItemValueString("name")); - documentService.remove(metric); - } - } - - } catch (IllegalArgumentException | QueryException e) { - throw new PluginException(MetricDataService.class.getName(), - "Failed to delete metrics", e.getMessage(), e); - } - return result; - } - /** * Builds the metric hash key by the invoice attributes. The returned key can be * used for caching the metric. diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDebitorRestService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDebitorRestService.java index 34c9c35..f41b97b 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDebitorRestService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDebitorRestService.java @@ -1,6 +1,5 @@ package com.alexanderlogistics.metrics; -import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; @@ -15,7 +14,6 @@ import jakarta.ejb.Stateless; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @@ -64,9 +62,6 @@ public class MetricDebitorRestService { // first clear the metric cache metricDebitorService.reset(); logger.info("│   ├── reset metric cache"); - // run in new transaction! - metricDataService.deleteAllMetrics(MetricDebitorService.TYPE_METRIC_DEBITOR); - logger.info("│   ├── delete metrics"); computeMetrics(); logger.info("│   ├── computing metrics finished in " + (System.currentTimeMillis() - l) + "ms"); @@ -89,92 +84,6 @@ public class MetricDebitorRestService { } } - /** - * This method initializes the metrics for all creditors with open invoices. - * - * The method deletes all existing metrics and creates new metric entires for - * each creditor. - * - * @return - */ - @GET - @Path("/rebuild/{dbtrnumber}") - @Produces({ MediaType.TEXT_PLAIN }) - public Response rebuildMetricsForDebitor(@PathParam("dbtrnumber") String dbtrnumber) { - StringBuffer messageBuffer = new StringBuffer(); - long l = System.currentTimeMillis(); - log("├── init dbtr metrics for " + dbtrnumber + "...", messageBuffer); - try { - - logger.info("│   │   ├── group invoices by creditor " + dbtrnumber + "..."); - - int count = 0; - - // First we need to remove all metrics for this creditor - String bpID = InvoiceUtil.buildBPID(dbtrnumber); - logger.info("│   │   ├── delete metrics for " + bpID + "..."); - List oldMetricKeys = metricDataService - .deleteAllMetricsByBPID(MetricDebitorService.TYPE_METRIC_DEBITOR, bpID); - logger.info("│   │   ├── found " + oldMetricKeys.size() + " metrics for " + bpID + "..."); - metricDebitorService.reset(oldMetricKeys); - // next rebuild gauges and metrics for this creditor - String queryDbtrNumber = dbtrnumber; - if (queryDbtrNumber.startsWith("D")) { - queryDbtrNumber = queryDbtrNumber.substring(1); // DAS IST DER CARGOSOFT IRRSINN - } - String query = "$modelversion:rechnungsausgang-* AND type:workitem " + - " AND type:workitem AND dbtr.number:" + queryDbtrNumber; - logger.fine("Query = " + query); - List invoices = documentService.find( - query, - 9999, 0, "invoice.number", false); - logger.info("│   │   ├──found " + invoices.size() + " open invoices"); - - List newMetricKeys = new ArrayList<>(); - for (ItemCollection invoice : invoices) { - try { - ItemCollection metricData = metricDebitorService.getMetricByInvoice(invoice); - String metricKey = metricData.getItemValueString("name"); - newMetricKeys.add(metricKey); - // Jetzt Rechnung addieren - logger.info("│   │   │   ├──update metric (" + metricKey + ")" + InvoiceUtil.getBPId(invoice) - + " Invoice: " - + invoice.getItemValueString("invoice.number") + " Saldo: " - + invoice.getItemValueDouble(MetricDebitorService.ITEM_SALDO)); - logger.info("│   │   │   │   ├── last metric saldo: " - + metricData.getItemValueDouble(MetricDebitorService.ITEM_SALDO)); - metricDebitorService.addInvoice(metricData, invoice); - logger.info("│   │   │   │   ├── new metric saldo: " - + metricData.getItemValueDouble(MetricDebitorService.ITEM_SALDO)); - metricDebitorService.putMetric(metricData); - count++; - } catch (PluginException e) { - // invalid invoice - e.g. no dbtr. number - } - } - - logger.info("│   │   ├── updated metric for " + count + " invoices."); - - log("│   ├── computing metrics finished in " + (System.currentTimeMillis() - l) + "ms", messageBuffer); - - logger.info("│   ├── save and init metrics..."); - // run in new transaction! - metricDebitorService.refreshGauges(newMetricKeys); - - String message = "├── rebuild dbtr metrics completed in " - + (System.currentTimeMillis() - l) - + "ms"; - log(message, messageBuffer); - return Response.ok().entity(messageBuffer.toString()).build(); - - } catch (Exception e) { - e.printStackTrace(); - return Response.serverError() - .entity("Failed to initialize metrics: " + e.getMessage()) - .build(); - } - } - /** * Diese Methode berechnet alle Metriken auf basis der existierenden Rechnungen * neu @@ -199,7 +108,7 @@ public class MetricDebitorRestService { // Jetzt Rechnung addieren metricDebitorService.addInvoice(metricData, invoice); logger.info("│   │   │   ├──update metric " + InvoiceUtil.getBPId(invoice)); - metricDebitorService.putMetric(metricData); + metricDebitorService.updateMetric(metricData); count++; } catch (PluginException e) { // invalid invoice - e.g. no dbtr. number diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDebitorService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDebitorService.java index f2af247..5fc057f 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDebitorService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDebitorService.java @@ -16,15 +16,12 @@ import org.imixs.workflow.ItemCollection; import org.imixs.workflow.engine.DocumentService; import org.imixs.workflow.engine.ProcessingEvent; import org.imixs.workflow.exceptions.PluginException; -import org.imixs.workflow.exceptions.QueryException; import com.alexanderlogistics.InvoiceUtil; import jakarta.annotation.security.DeclareRoles; import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RunAs; -import jakarta.ejb.TransactionAttribute; -import jakarta.ejb.TransactionAttributeType; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; @@ -56,7 +53,10 @@ public class MetricDebitorService { private final Set registeredGauges = ConcurrentHashMap.newKeySet(); public static final String TYPE_METRIC_DEBITOR = "metric.debitor"; - public static final String ITEM_SALDO = "invoice.saldo"; + public static final String ITEM_INVOICE_TOTAL = "invoice.total"; + public static final String ITEM_INVOICE_SALDO = "invoice.saldo"; + public static final String ITEM_METRIC_BALANCE = "invoice.balance"; + public static final String ITEM_METRIC_SALES = "invoice.sales"; @Inject @RegistryScope(scope = MetricRegistry.APPLICATION_SCOPE) @@ -80,17 +80,6 @@ public class MetricDebitorService { registeredGauges.clear(); } - /** - * Reset the internal metricCache and clears all registered Gauges for a list of - * metric keys. - */ - public void reset(List metricKeys) { - for (String metricKey : metricKeys) { - metricCache.remove(metricKey); - metricRegistry.remove(metricKey); - } - } - /** * Process Metric only if some data has changed.... * @@ -116,9 +105,10 @@ public class MetricDebitorService { if (lastInvoice != null) { try { ItemCollection lastMetricData = getMetricByInvoice(lastInvoice); - subtractInvoice(lastMetricData, lastInvoice); - putMetric(lastMetricData); - metricDataService.saveMetric(lastMetricData); + if (!isNewMetric(lastMetricData)) { + subtractInvoice(lastMetricData, lastInvoice); + updateMetric(lastMetricData); + } } catch (PluginException e) { // invalid invoice - e.g. no cdtr. number } @@ -129,11 +119,8 @@ public class MetricDebitorService { ItemCollection metricData = getMetricByInvoice(invoice); // Saldo-Berechnung addInvoice(metricData, invoice); - putMetric(metricData); - metricDataService.saveMetric(metricData); + updateMetric(metricData); - // Update the Gauge - updateGauge(metricData); logger.info("Metric dbtr update took " + (System.currentTimeMillis() - l) + "ms"); } catch (PluginException e) { // invalid invoice - e.g. no dbtr. number @@ -142,6 +129,17 @@ public class MetricDebitorService { } + /** + * Returns true if the metric is not yet registered. This means we do not have + * sales or balances for this metric + * + * @param metricData + * @return + */ + public boolean isNewMetric(ItemCollection metricData) { + return (!metricCache.containsKey(metricData.getItemValueString("name"))); + } + /** * Returns the corresponding metric data object for an invoice. The method uses * an internal cache. If the metric was not yet cached the method loads the @@ -161,9 +159,6 @@ public class MetricDebitorService { } String metricKey = MetricDataService.buildKeyByInvoice(invoice); ItemCollection metricData = metricCache.get(metricKey); - if (metricData == null) { - metricData = loadMetric(invoice); - } // if metric still null we create a new metric data object. if (metricData == null) { metricData = createMetaData(invoice); @@ -181,15 +176,6 @@ public class MetricDebitorService { return metricCache.get(key); } - /** - * Puts a metric data object into the cache. - * - * @param metricData - */ - public void putMetric(ItemCollection metricData) { - metricCache.put(metricData.getItemValueString("name"), metricData); - } - /** * Returns a list with all cached metric keys. * The method returns an unmodifiable list to prevent modifications. @@ -200,34 +186,6 @@ public class MetricDebitorService { return Collections.unmodifiableList(new ArrayList<>(metricCache.keySet())); } - /** - * This method loads a metric entity for a given invoice workitem. If no metric - * entity exits, the method - * creates a new metric entity. - * - * @param invoice - * @return - * @throws PluginException - */ - private ItemCollection loadMetric(ItemCollection invoice) throws PluginException { - ItemCollection debitorMetric = null; - if (invoice == null) { - return null; - } - try { - String metricKey = MetricDataService.buildKeyByInvoice(invoice); - String query = "(type:" + TYPE_METRIC_DEBITOR + ") AND (name:" + metricKey + ")"; - List result = documentService.find(query, 1, 0, "$modified", true); - if (result.size() > 0) { - debitorMetric = result.get(0); - } - } catch (IllegalArgumentException | QueryException e) { - throw new PluginException(PluginException.class.getName(), - "Failed to load metric object for invoice " + invoice.getUniqueID() + ": ", e.getMessage(), e); - } - return debitorMetric; - } - /** * Creates an empty Debitor Meta Data Object (ItemCollection) *

@@ -260,17 +218,20 @@ public class MetricDebitorService { * * @param metricData - the metricData ItemCollection */ - public void updateGauge(ItemCollection metricData) { - + public void updateMetric(ItemCollection metricData) { String metricKey = metricData.getItemValueString("name"); - String bpName = metricData.getItemValueString("bp.name"); - String bpId = metricData.getItemValueString("bp.id"); - String country = metricData.getItemValueString("country"); - String department = metricData.getItemValueString("department"); - String currency = metricData.getItemValueString("currency"); + + // Cache aktualisieren + metricCache.put(metricKey, metricData); // Prüfen ob Gauge bereits registriert ist if (registeredGauges.add(metricKey)) { // returns true newly added + String bpName = metricData.getItemValueString("bp.name"); + String bpId = metricData.getItemValueString("bp.id"); + String country = metricData.getItemValueString("country"); + String department = metricData.getItemValueString("department"); + String currency = metricData.getItemValueString("currency"); + List tags = new ArrayList<>(); tags.add(new Tag("type", "dbtr")); tags.add(new Tag("id", bpId)); @@ -279,19 +240,25 @@ public class MetricDebitorService { tags.add(new Tag("currency", currency)); tags.add(new Tag("department", department)); logger.fine("register new metric for department: " + department + - ", " + metricData.getItemValueString(ITEM_SALDO) + + ", " + metricData.getItemValueString(ITEM_METRIC_BALANCE) + " " + currency); - Metadata metadata = Metadata.builder() + // Saldo Gauge + Metadata balanceMetadata = Metadata.builder() .withName("dbtr.balance") .withDescription("Debitor Balance") .build(); - - metricCache.get(metricKey); - metricRegistry.gauge(metadata, - () -> metricCache.get(metricKey).getItemValueDouble(ITEM_SALDO), + metricRegistry.gauge(balanceMetadata, + () -> metricCache.get(metricKey).getItemValueDouble(ITEM_METRIC_BALANCE), + tags.toArray(new Tag[0])); + + // Umsatz Gauge + Metadata revenueMetadata = Metadata.builder() + .withName("dbtr.sales") + .withDescription("Debitor Sales") + .build(); + metricRegistry.gauge(revenueMetadata, + () -> metricCache.get(metricKey).getItemValueDouble(ITEM_METRIC_SALES), tags.toArray(new Tag[0])); - } else { - logger.fine("Gauge already registered for department: " + department); } } @@ -303,64 +270,59 @@ public class MetricDebitorService { * @param invoice */ public void addInvoice(ItemCollection metricData, ItemCollection invoice) { - double invoiceTotal = invoice.getItemValueDouble(ITEM_SALDO); + double invoiceSaldo = invoice.getItemValueDouble(ITEM_INVOICE_SALDO); + double invoiceTotal = invoice.getItemValueDouble(ITEM_INVOICE_TOTAL); if (!"workitem".equals(invoice.getType()) || invoice.getTaskID() >= 5800) { // vorgang ist archiviert oder gelöscht worden => saldo = 0! - invoiceTotal = 0.0; + invoiceSaldo = 0.0; } logger.fine("│   │   │   │   ├── Invoice: " + invoice.getItemValueString("invoice.number") + " Saldo=" - + invoiceTotal); - // is the metric new? - if (!metricData.hasItem(ITEM_SALDO)) { - // init metric with the total value! - metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(invoiceTotal)); - } else { - double lastSaldo = metricData.getItemValueDouble(ITEM_SALDO); - logger.fine("│   │   │   │   ├──letzter Metric Saldo=" + lastSaldo); - metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(lastSaldo + invoiceTotal)); - } + + invoiceSaldo); + // update saldo + double lastSaldo = metricData.getItemValueDouble(ITEM_METRIC_BALANCE); + logger.fine("│   │   │   │   ├── last metric balance=" + lastSaldo); + metricData.setItemValue(ITEM_METRIC_BALANCE, InvoiceUtil.round(lastSaldo + invoiceSaldo)); + + // Umsatz-Berechnung + double lastTotal = metricData.getItemValueDouble(ITEM_METRIC_SALES); + metricData.setItemValue(ITEM_METRIC_SALES, InvoiceUtil.round(lastTotal + invoiceTotal)); + } public void subtractInvoice(ItemCollection metricData, ItemCollection invoice) { if (metricData == null || invoice == null) { return; } - double invoiceTotal = invoice.getItemValueDouble(ITEM_SALDO); + double invoiceSaldo = invoice.getItemValueDouble(ITEM_INVOICE_SALDO); + double invoiceTotal = invoice.getItemValueDouble(ITEM_INVOICE_TOTAL); logger.fine("│   │   │   │   ├──Invoice: " + invoice.getItemValueString("invoice.number") + " Saldo=" - + invoiceTotal); + + invoiceSaldo); if (!"workitem".equals(invoice.getType()) || invoice.getTaskID() >= 5800) { // vorgang ist archiviert oder gelöscht worden => saldo = 0! - invoiceTotal = 0.0; + invoiceSaldo = 0.0; } // subtract only if metric saldo exists - if (metricData.hasItem(ITEM_SALDO)) { - double lastSaldo = metricData.getItemValueDouble(ITEM_SALDO); - logger.fine("│   │   │   │   ├──letzter Metric Saldo=" + lastSaldo); - metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(lastSaldo - invoiceTotal)); - } + double lastSaldo = metricData.getItemValueDouble(ITEM_METRIC_BALANCE); + logger.fine("│   │   │   │   ├── last Metric balance=" + lastSaldo); + metricData.setItemValue(ITEM_METRIC_BALANCE, InvoiceUtil.round(lastSaldo - invoiceSaldo)); + + // update Umsatz + double lastTotal = metricData.getItemValueDouble(ITEM_METRIC_SALES); + metricData.setItemValue(ITEM_METRIC_SALES, InvoiceUtil.round(lastTotal - invoiceTotal)); + } /** * Helper Method that refreshes all gauges. The method is called by the * RestService during a rebuild. */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + public void refreshGauges() { List keys = getMetricKeys(); - refreshGauges(keys); - } - - /** - * Helper Method that refreshes all gauges. The method is called by the - * RestService during a rebuild. - */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public void refreshGauges(List keys) { for (String hashKey : keys) { ItemCollection metricData = getMetric(hashKey); - documentService.save(metricData); - updateGauge(metricData); + updateMetric(metricData); } }