From 07ed3a30d0f3d086a4e1afac33bf7df6f269a119 Mon Sep 17 00:00:00 2001 From: Ralph Soika Date: Sun, 8 Dec 2024 11:20:22 +0100 Subject: [PATCH] impl metric service --- .../metrics/MetricCreditorRestService.java | 145 +++++++ .../metrics/MetricCreditorService.java | 356 ++++++++++++++++++ .../metrics/MetricDataService.java | 49 +++ .../metrics/MetricDebitorRestService.java | 24 +- .../metrics/MetricDebitorService.java | 103 ++--- 5 files changed, 613 insertions(+), 64 deletions(-) create mode 100644 office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorRestService.java create mode 100644 office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorService.java create mode 100644 office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDataService.java 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 new file mode 100644 index 0000000..c653815 --- /dev/null +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorRestService.java @@ -0,0 +1,145 @@ +package com.alexanderlogistics.metrics; + +import java.util.List; +import java.util.logging.Logger; + +import org.imixs.workflow.ItemCollection; +import org.imixs.workflow.engine.DocumentService; +import org.imixs.workflow.exceptions.PluginException; +import org.imixs.workflow.exceptions.QueryException; + +import jakarta.ejb.Stateless; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +@Path("metrics/cdtr") +@Stateless +public class MetricCreditorRestService { + + private static Logger logger = Logger.getLogger(MetricCreditorRestService.class.getName()); + + @Inject + DocumentService documentService; + + @Inject + MetricCreditorService metricService; + + @GET + @Path("/ping") + @Produces({ MediaType.TEXT_PLAIN }) + public String ping() { + logger.info("GET ping"); + return "ping: " + System.currentTimeMillis(); + } + + /** + * This method refreshes all creditor metrics by iterating through the metric + * entities. + * + * @return + */ + @GET + @Path("/refresh") + @Produces({ MediaType.TEXT_PLAIN }) + public Response refreshMetrics() { + try { + // Alle Creditor Metriken laden + String query = "(type:" + MetricCreditorService.TYPE_METRIC_CREDITOR + ")"; + List metrics = documentService.find(query, 9999, 0); + + // Metriken initialisieren + for (ItemCollection metric : metrics) { + metricService.initMetric(metric); + } + return Response.ok().entity("Initialized " + metrics.size() + " creditor metrics").build(); + } catch (Exception e) { + return Response.serverError() + .entity("Failed to initialize metrics: " + e.getMessage()) + .build(); + } + } + + /** + * This method initializes the metrics for all creditors with open invoices. + * The method creates or updates the metric entires for each creditor. + * + * @return + */ + @GET + @Path("/init") + @Produces({ MediaType.TEXT_PLAIN }) + public Response initMetrics() { + // Map metricCache = new HashMap(); + long l = System.currentTimeMillis(); + logger.info("├── init cdtr metrics..."); + try { + groupInvoicesByCreditor(); + logger.info("│   ├── grouping invoices finished in " + (System.currentTimeMillis() - l) + "ms"); + + rebuildMetrics(); + String message = "├── init cdtr metrics completed in " + + (System.currentTimeMillis() - l) + + "ms"; + logger.info(message); + return Response.ok().entity(message).build(); + + } catch (Exception e) { + e.printStackTrace(); + return Response.serverError() + .entity("Failed to initialize metrics: " + e.getMessage()) + .build(); + } + } + + private void rebuildMetrics() throws PluginException { + logger.info("│   ├── rebuild metrics..."); + List keys = metricService.getMetricKeys(); + for (String hashKey : keys) { + ItemCollection metricData = metricService.getMetric(hashKey); + // metricCache.get(dbtrNumber); + documentService.save(metricData); + metricService.initMetric(metricData); + } + } + + /** + * Diese Methode gruppiert eine Rechnungsliste nach creditoren + * + * @param spaceID - Space Ref to select a list of invoices associated with + * a + * space + * @param metricCache - a local cache storing all invoices by week + * + * + */ + private void groupInvoicesByCreditor() { + logger.info("│   │   ├── group invoices by creditor..."); + try { + List invoices = documentService.find( + "($modelversion:rechnungseingang-* OR $modelversion:gutschriftabgleich-*) " + + " AND type:workitem", + 9999, 0, + "invoice.number", false); + logger.info("│   │   ├──found " + invoices.size() + " open invoices"); + for (ItemCollection invoice : invoices) { + if (invoice.getItemValueString("cdtr.number").trim().isEmpty()) { + // skip event + continue; + } + ItemCollection metricData = metricService.getMetricByInvoice(invoice); + // Jetzt Rechnung addieren + metricService.addInvoice(metricData, invoice); + logger.fine("....put invoice " + invoice.getUniqueID()); + metricService.putMetric(metricData); + } + logger.info("│   │   ├── grouped " + invoices.size() + " invoices."); + } catch (QueryException | PluginException e) { + e.printStackTrace(); + } + } +} 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 new file mode 100644 index 0000000..d132230 --- /dev/null +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorService.java @@ -0,0 +1,356 @@ +package com.alexanderlogistics.metrics; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.metrics.Metadata; +import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.Tag; +import org.eclipse.microprofile.metrics.annotation.RegistryScope; +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.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; + +/** + * Dieser Service reagiert auch ProcessingEvents und speichert/aktualisiert die + * zugehörige Creditor Metric Entity (type=metric.debitor). + *

+ * Beim Rechnungseingang gilt eine Rechnung als Bezahlt wenn diese einen finalen + * Status erreicht hat (>=5800) + *

+ * Der Service liest in einem AFTER_PROCESS Event den alten invoice.saldo aus. + * Hierzu wird die Rechnung in einer neuen Transaktion geladen was einem + * 'Dirty-Read' entspricht. Dadurch kennt die Methode den letzten saldo der + * Rechnung. Hat sich nun der aktuelle Saldo geändert, aktualisiert der Serivce + * die entsprechende Metric. + * + * + */ +@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", + "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" }) +@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") +@ApplicationScoped +public class MetricCreditorService { + + private static Logger logger = Logger.getLogger(MetricCreditorService.class.getName()); + private final ConcurrentHashMap metricCache = new ConcurrentHashMap<>(); + 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"; + + @Inject + @RegistryScope(scope = MetricRegistry.APPLICATION_SCOPE) + MetricRegistry metricRegistry; + + @Inject + DocumentService documentService; + + @Inject + MetricDataService metricDataService; + + @Inject + @ConfigProperty(name = "metrics.enabled", defaultValue = "false") + private boolean metricsEnabled; + + /** + * Process Metric only if some data has changed.... + * + * @param processingEvent + * @throws PluginException + */ + public void onProcessingEvent(@Observes ProcessingEvent processingEvent) { + long l = System.currentTimeMillis(); + if (!metricsEnabled) { + return; + } + + // double creditorSaldo = 0; + ItemCollection invoice = processingEvent.getDocument(); + if (!invoice.getModelVersion().startsWith("rechnungseingang-") + && !invoice.getModelVersion().startsWith("gutschriftabgleich-")) { + // skip event + return; + } + + // if we do not have a cdtr.number skip + if (invoice.getItemValueString("cdtr.number").trim().isEmpty()) { + // skip event + return; + } + + try { + // update metric and the metric cache + if (processingEvent.getEventType() == ProcessingEvent.AFTER_PROCESS) { + + // load the last invoice metric and reduce the saldo... + ItemCollection lastInvoice = metricDataService.readDirtyWorkitem(invoice.getUniqueID()); + ItemCollection lastMetricData = getMetricByInvoice(lastInvoice); + subtractInvoice(lastMetricData, lastInvoice); + putMetric(lastMetricData); + metricDataService.saveMetric(lastMetricData); + + // load the invoice metric and add the saldo... + ItemCollection metricData = getMetricByInvoice(invoice); + // Saldo-Berechnung + addInvoice(metricData, invoice); + putMetric(metricData); + metricDataService.saveMetric(metricData); + + // Update the Gauge + updateGauge(metricData); + logger.info("Metric cdtr update took " + (System.currentTimeMillis() - l) + "ms"); + } + } catch (PluginException e) { + logger.warning("unable to process metric: " + e.getMessage()); + } + } + + /** + * 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 + * metric from the database. If not metric exists in the database the method + * automatically creates a new metric data object. + * + * @param invoice + * @return + * @throws PluginException + */ + public ItemCollection getMetricByInvoice(ItemCollection invoice) throws PluginException { + String metricKey = 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); + } + return metricData; + } + + /** + * Returns a metric data object by key + * + * @param key + * @return + */ + public ItemCollection getMetric(String key) { + 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. + * + * @return + */ + public List getMetricKeys() { + return Collections.unmodifiableList(new ArrayList<>(metricCache.keySet())); + } + + /** + * Builds the metric hash key by the invoice attributes. The returned key can be + * used for caching the metric. + * + * @param invoice - The invoice ItemCollection containing the necessary + * attributes + * @return A unique hash string based on the invoice attributes + * @throws IllegalArgumentException if the invoice is null + */ + private static String buildKeyByInvoice(ItemCollection invoice) { + // Validate input + Objects.requireNonNull(invoice, "Invoice must not be null"); + + String cdtrNumber = invoice.getItemValueString("cdtr.number"); + if (cdtrNumber.startsWith("D") || cdtrNumber.startsWith("K")) { + cdtrNumber = cdtrNumber.substring(1); + } + String cdtrName = invoice.getItemValueString("cdtr.name"); + String currency = invoice.getItemValueString("invoice.currency"); + String department = invoice.getItemValueString("space.name"); + + // Concatenate the values and create a hash + String combinedValue = String.format("%s::%s::%s::%s", + cdtrNumber, + cdtrName, + currency, + department); + + String hash = String.valueOf(combinedValue.hashCode()); + // a hash can start with '-' which we need to avoid and create a alphanumeric + // key instead! + return "HASH" + hash; + } + + /** + * 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; + try { + String metricKey = buildKeyByInvoice(invoice); + String query = "(type:" + TYPE_METRIC_CREDITOR + ") 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) + *

+ * The ItemCollection stores the name and number and also all saldos for all + * currencies + * + * @param invoice - invoice ItemCollection + * @return + */ + private ItemCollection createMetaData(ItemCollection invoice) { + String key = buildKeyByInvoice(invoice); + ItemCollection metricData = new ItemCollection(); + metricData.setType(TYPE_METRIC_CREDITOR); + metricData.setItemValue("name", key); + + String cdtrNumber = invoice.getItemValueString("cdtr.number"); + if (cdtrNumber.startsWith("D") || cdtrNumber.startsWith("K")) { + cdtrNumber = cdtrNumber.substring(1); + } + metricData.setItemValue("cdtr.number", cdtrNumber); + metricData.setItemValue("cdtr.name", invoice.getItemValueString("cdtr.name")); + metricData.setItemValue("invoice.currency", invoice.getItemValueString("invoice.currency")); + metricData.setItemValue("space.name", invoice.getItemValueString("space.name")); + return metricData; + } + + /** + * Helper method to register a gauge for a debitor + * + * @param cdtrNumber - the creditor number + * @param cdtrName - the creditor name + */ + private void updateGauge(ItemCollection metricData) { + String cdtrNumber = metricData.getItemValueString("cdtr.number"); + String cdtrName = metricData.getItemValueString("cdtr.name"); + String metricKey = metricData.getItemValueString("name"); + // Prüfen ob Gauge bereits registriert ist + if (registeredGauges.add(metricKey)) { // returns true newly added + List tags = new ArrayList<>(); + tags.add(new Tag("type", "cdtr")); + tags.add(new Tag("number", cdtrNumber)); + tags.add(new Tag("name", cdtrName)); + tags.add(new Tag("currency", metricData.getItemValueString("invoice.currency"))); + tags.add(new Tag("department", metricData.getItemValueString("space.name"))); + logger.info("register new metric for creditor: " + cdtrNumber + + ", " + metricData.getItemValueString(ITEM_SALDO) + + " " + metricData.getItemValueString("invoice.currency")); + Metadata metadata = Metadata.builder() + .withName("cdtr.balance") + .withDescription("Creditor Balance") + .build(); + + metricCache.get(metricKey); + metricRegistry.gauge(metadata, + () -> metricCache.get(metricKey).getItemValueDouble(ITEM_SALDO), + tags.toArray(new Tag[0])); + } else { + logger.fine("Gauge already registered for creditor: " + cdtrNumber); + } + + } + + /** + * Hilfsmethode um Metriken initial aufzubauen. Wird vom Metric Rest Service + * verwendet. + * + */ + protected void initMetric(ItemCollection metric) { + String key = metric.getItemValueString("name"); + // Aktuelle metric cachen + metricCache.put(key, metric); + logger.info("│   │   ├──init metric for creditor " + key); + updateGauge(metric); + } + + /** + * Addiert den saldo einer Invoice zu einem metricData object + * + * @param metricData + * @param invoice + */ + public void addInvoice(ItemCollection metricData, ItemCollection invoice) { + double invoiceTotal = invoice.getItemValueDouble(ITEM_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)); + } + } + + public void subtractInvoice(ItemCollection metricData, ItemCollection invoice) { + double invoiceTotal = invoice.getItemValueDouble(ITEM_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, invoiceTotal); + } else { + double lastSaldo = metricData.getItemValueDouble(ITEM_SALDO); + metricData.setItemValue(ITEM_SALDO, lastSaldo - invoiceTotal); + } + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000..045d454 --- /dev/null +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDataService.java @@ -0,0 +1,49 @@ +package com.alexanderlogistics.metrics; + +import org.imixs.workflow.ItemCollection; +import org.imixs.workflow.engine.DocumentService; + +import jakarta.annotation.security.DeclareRoles; +import jakarta.annotation.security.RunAs; +import jakarta.ejb.Stateless; +import jakarta.ejb.TransactionAttribute; +import jakarta.ejb.TransactionAttributeType; +import jakarta.inject.Inject; + +/** + * The MetricDataService provides methods to lookup and update metric data + * objects. + * The service runs with manager access. + * + * + */ +@DeclareRoles({ "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) +@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") +@Stateless +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 + * + * @param uniqueId + */ + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + public ItemCollection readDirtyWorkitem(String uniqueId) { + ItemCollection dirtyInvoice = documentService.load(uniqueId); + return dirtyInvoice; + } + +} \ No newline at end of file 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 0b4129d..c752cd7 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 @@ -8,8 +8,6 @@ import org.imixs.workflow.engine.DocumentService; import org.imixs.workflow.exceptions.PluginException; import org.imixs.workflow.exceptions.QueryException; -import com.alexanderlogistics.InvoiceUtil; - import jakarta.ejb.Stateless; import jakarta.inject.Inject; import jakarta.ws.rs.GET; @@ -18,7 +16,7 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -@Path("metrics") +@Path("metrics/dbtr") @Stateless public class MetricDebitorRestService { @@ -80,13 +78,13 @@ public class MetricDebitorRestService { // Map metricCache = new HashMap(); long l = System.currentTimeMillis(); - logger.info("├── init metrics..."); + logger.info("├── init dbtr metrics..."); try { groupInvoicesByDebitor(); logger.info("│   ├── grouping invoices finished in " + (System.currentTimeMillis() - l) + "ms"); rebuildMetrics(); - String message = "├── init metrics completed in " + String message = "├── init cdtr metrics completed in " + (System.currentTimeMillis() - l) + "ms"; logger.info(message); @@ -135,11 +133,9 @@ public class MetricDebitorRestService { for (ItemCollection invoice : invoices) { ItemCollection metricData = metricService.getMetricByInvoice(invoice); // Jetzt Rechnung addieren - addInvoice(metricData, invoice); - // invoiceData.add(invoice); + metricService.addInvoice(metricData, invoice); logger.fine("....put invoice " + invoice.getUniqueID()); metricService.putMetric(metricData); - // metricCache.put(metricKey, metricData); } logger.info("│   │   ├── grouped " + invoices.size() + " invoices."); } catch (QueryException | PluginException e) { @@ -148,16 +144,4 @@ public class MetricDebitorRestService { } - /** - * Addiert den saldo einer Invoice zu einem metricData object - * - * @param metricData - * @param invoice - */ - public void addInvoice(ItemCollection metricData, ItemCollection invoice) { - double saldo = invoice.getItemValueDouble(MetricDebitorService.ITEM_SALDO); - double saldoOld = metricData.getItemValueDouble(MetricDebitorService.ITEM_SALDO); - double saldoNew = InvoiceUtil.round(saldoOld + saldo); - metricData.setItemValue(MetricDebitorService.ITEM_SALDO, saldoNew); - } } 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 2e9edd2..7eb2c8b 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 @@ -24,8 +24,6 @@ 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; @@ -66,6 +64,9 @@ public class MetricDebitorService { @Inject DocumentService documentService; + @Inject + MetricDataService metricDataService; + @Inject @ConfigProperty(name = "metrics.enabled", defaultValue = "false") private boolean metricsEnabled; @@ -77,48 +78,44 @@ public class MetricDebitorService { * @throws PluginException */ public void onProcessingEvent(@Observes ProcessingEvent processingEvent) { - + long l = System.currentTimeMillis(); if (!metricsEnabled) { return; } - double debitorSaldo = 0; + + // double debitorSaldo = 0; ItemCollection invoice = processingEvent.getDocument(); if (!invoice.getModelVersion().startsWith("rechnungsausgang-")) { // skip event return; } + // if we do not have a dbtr.number skip + if (invoice.getItemValueString("dbtr.number").trim().isEmpty()) { + // skip event + return; + } try { // update metric and the metric cache if (processingEvent.getEventType() == ProcessingEvent.AFTER_PROCESS) { - // verify if saldo has changed..... - double invoiceSaldo = invoice.getItemValueDouble(ITEM_SALDO); - if (!"workitem".equals(invoice.getType())) { - // vorgang ist archiviert oder gelöscht worden => saldo = 0! - invoiceSaldo = 0.0; - } - // load last metric... - double lastInvoiceSaldo = readDirtySaldo(invoice.getUniqueID()); - if (invoiceSaldo == lastInvoiceSaldo) { - // no change - no metric update! - return; - } + // load the last invoice metric and reduce the saldo... + ItemCollection lastInvoice = metricDataService.readDirtyWorkitem(invoice.getUniqueID()); + ItemCollection lastMetricData = getMetricByInvoice(lastInvoice); + subtractInvoice(lastMetricData, lastInvoice); + putMetric(lastMetricData); + metricDataService.saveMetric(lastMetricData); + // load the invoice metric and add the saldo... ItemCollection metricData = getMetricByInvoice(invoice); - // Saldo-Berechnung - debitorSaldo = metricData.getItemValueDouble(ITEM_SALDO); - debitorSaldo = debitorSaldo - lastInvoiceSaldo; - - // update debitor saldo - debitorSaldo = InvoiceUtil.round(debitorSaldo + invoiceSaldo); - metricData.setItemValue(ITEM_SALDO, debitorSaldo); + addInvoice(metricData, invoice); putMetric(metricData); - documentService.save(metricData); + metricDataService.saveMetric(metricData); - // Gauge registrieren + // Update the Gauge updateGauge(metricData); + logger.info("Metric dbtr update took " + (System.currentTimeMillis() - l) + "ms"); } } catch (PluginException e) { logger.warning("unable to process metric: " + e.getMessage()); @@ -208,24 +205,6 @@ public class MetricDebitorService { return "HASH" + hash; } - /** - * This helper method reads the 'dirty' saldo in a new transaction. This is used - * for calculating the new total saldo - * - * @param uniqueid - */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public double readDirtySaldo(String uniqueid) { - double result = 0; - ItemCollection dirtyInvoice = documentService.load(uniqueid); - if (dirtyInvoice == null) { - return 0.0; - } - result = dirtyInvoice.getItemValueDouble(ITEM_SALDO); - // logger.info("---Dirty last Saldo was : " + result); - return result; - } - /** * This method loads a metric entity for a given invoice workitem. If no metric * entity exits, the method @@ -248,7 +227,6 @@ public class MetricDebitorService { throw new PluginException(PluginException.class.getName(), "Failed to load metric object for invoice " + invoice.getUniqueID() + ": ", e.getMessage(), e); } - return debitorMetric; } @@ -322,4 +300,41 @@ public class MetricDebitorService { updateGauge(metric); } + /** + * Addiert den saldo einer Invoice zu einem metricData object + * + * @param metricData + * @param invoice + */ + public void addInvoice(ItemCollection metricData, ItemCollection invoice) { + double invoiceTotal = invoice.getItemValueDouble(ITEM_SALDO); + 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)); + } + } + + public void subtractInvoice(ItemCollection metricData, ItemCollection invoice) { + double invoiceTotal = invoice.getItemValueDouble(ITEM_SALDO); + 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)); + } + } } \ No newline at end of file