diff --git a/doc/METRICS.md b/doc/METRICS.md index 1b790d3..b7326b1 100644 --- a/doc/METRICS.md +++ b/doc/METRICS.md @@ -1,3 +1,15 @@ +# Metrics + +**Konzept Verworfen!** + +## Background: + +https://blog.imixs.org/2025/02/02/business-intelligence-built-on-metrics-part-ii/ + +**Die Implementierung war Teil von Version 1.3.1!** + +## Testing + Curl $ curl -s http://localhost:9990/metrics diff --git a/docker-compose.yml b/docker-compose.yml index e2a4687..5c1c8b4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,7 +44,7 @@ services: LLM_SERVICE_ENDPOINT_USER: "admin" LLM_SERVICE_ENDPOINT_PASSWORD: "imixs4.null" - METRICS_ENABLED: "true" + METRICS_ENABLED: "false" ports: - "8080:8080" @@ -105,18 +105,18 @@ services: EXIM_PASSWORD: "www149.your-server.de:webmaster@imixs.com:$MAILPASSWORD" EXIM_ALLOWED_SENDERS: "10.0.0.0/8:172.18.0.0/12:192.168.0.0/16" - prometheus: - image: prom/prometheus:latest - ports: - - "9090:9090" - volumes: - - ./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - - prometheusdata:/prometheus/ + # prometheus: + # image: prom/prometheus:latest + # ports: + # - "9090:9090" + # volumes: + # - ./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + # - prometheusdata:/prometheus/ - grafana: - image: grafana/grafana:latest - ports: - - "3000:3000" + # grafana: + # image: grafana/grafana:latest + # ports: + # - "3000:3000" volumes: dbdata: diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerService.java index 8533054..9addc98 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/BusinessPartnerService.java @@ -27,7 +27,6 @@ package com.alexanderlogistics; -import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -37,11 +36,9 @@ import java.util.Optional; import java.util.logging.Logger; import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.imixs.melman.DocumentClient; -import org.imixs.melman.FormAuthenticator; -import org.imixs.melman.RestAPIException; import org.imixs.workflow.ItemCollection; import org.imixs.workflow.ItemCollectionComparator; +import org.imixs.workflow.engine.DocumentService; import org.imixs.workflow.engine.index.SchemaService; import jakarta.annotation.PostConstruct; @@ -77,8 +74,6 @@ public class BusinessPartnerService { private static Logger logger = Logger.getLogger(BusinessPartnerService.class.getName()); - private FormAuthenticator formAuthenticator; - @Inject @ConfigProperty(name = WORKFLOW_SERVICE_ENDPOINT) Optional workflowServiceEndpoint; @@ -91,8 +86,8 @@ public class BusinessPartnerService { @ConfigProperty(name = WORKFLOW_SERVICE_PASSWORD) Optional workflowServicePassword; - // @Inject - // DocumentService documentService; + @Inject + DocumentService documentService; @Inject SchemaService schemaService; @@ -102,12 +97,7 @@ public class BusinessPartnerService { */ @PostConstruct public void init() { - // init clients - try { - initAuthenticator(); - } catch (RestAPIException e) { - logger.severe("Failed to initalize Rest Clients: " + e.getMessage()); - } + } /** @@ -118,27 +108,20 @@ public class BusinessPartnerService { * @return - matching business partner */ public ItemCollection getBusinessPartnerByID(String bpid) { - long l = System.currentTimeMillis(); if (bpid == null || bpid.isEmpty()) { return null; } List result; try { - String sQuery = "(type:\"workitem\") AND ($modelversion:businesspartner-*)"; + String sQuery = "(type:\"workitem\" OR type:\"workitemarchive\" ) AND ($modelversion:businesspartner-*)"; sQuery += " AND (name:" + bpid.trim() + ")"; logger.finest("SearchQuery= " + sQuery); - - // register client - DocumentClient documentClient = getDocumentClient(); - documentClient.setPageSize(MAX_SEARCH_RESULT); - documentClient.setPageSize(1); - result = documentClient.searchDocuments(sQuery); + result = documentService.find(sQuery, 1, 0); if (result.size() > 0) { - logger.info("🕐 get BusinessPartner by id in " + (System.currentTimeMillis() - l) + "ms"); return result.get(0); } - } catch (RestAPIException | UnsupportedEncodingException e) { - logger.warning("Failed to get BusinessPartner: " + e.getMessage()); + } catch (Exception e) { + logger.warning(" lucene error - " + e.getMessage()); } return null; } @@ -151,7 +134,7 @@ public class BusinessPartnerService { * @return - list of matching business partners */ public List search(String phrase) { - long l = System.currentTimeMillis(); + List searchResult = new ArrayList(); if (phrase == null || phrase.isEmpty()) { return searchResult; @@ -161,45 +144,21 @@ public class BusinessPartnerService { phrase = phrase.trim(); // phrase = LuceneSearchService.escapeSearchTerm(phrase); phrase = schemaService.normalizeSearchTerm(phrase); - String sQuery = "(type:\"workitem\") AND ($modelversion:businesspartner-*)"; + String sQuery = "(type:\"workitem\" OR type:\"workitemarchive\") AND ($modelversion:businesspartner-*)"; sQuery += " AND (" + phrase + "*)"; logger.finest("SearchQuery= " + sQuery); - // register client - DocumentClient documentClient = getDocumentClient(); - documentClient.setPageSize(MAX_SEARCH_RESULT); - searchResult = documentClient.searchDocuments(sQuery); - - } catch (RestAPIException | UnsupportedEncodingException e) { + searchResult = documentService.find(sQuery, MAX_SEARCH_RESULT, 0); + } catch (Exception e) { logger.warning(" lucene error - " + e.getMessage()); - formAuthenticator = null; } // sort by txtname.. Collections.sort(searchResult, new ItemCollectionComparator("$workflowsummary", true)); - - logger.info("🕐 BusinessPartner search: " + searchResult.size() + " entries in " - + (System.currentTimeMillis() - l) + "ms"); return searchResult; } - private DocumentClient getDocumentClient() { - DocumentClient documentClient = null; - try { - if (formAuthenticator == null) { - initAuthenticator(); - } - documentClient = new DocumentClient(workflowServiceEndpoint.get()); - documentClient.registerClientRequestFilter(formAuthenticator); - } catch (RestAPIException e) { - logger.warning("Failed to init RestClient: " + e.getMessage()); - formAuthenticator = null; - } - - return documentClient; - } - /** * Packt die Liste der Bank Details (ItemCollections) in ein Workitem * @@ -262,17 +221,4 @@ public class BusinessPartnerService { return result; } - /** - * Helper method to initalize a Melman FormAuthenticator - * - * @throws RestAPIException - */ - public void initAuthenticator() throws RestAPIException { - logger.info("⚡ Init FormAuthenticator..."); - - // form authenticator - formAuthenticator = new FormAuthenticator(workflowServiceEndpoint.get(), workflowServiceUser.get(), - workflowServicePassword.get()); - - } } diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceUtil.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceUtil.java index b6bc313..24cdc3e 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceUtil.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/InvoiceUtil.java @@ -8,9 +8,6 @@ import java.util.Map; import java.util.regex.Pattern; import org.imixs.workflow.ItemCollection; -import org.imixs.workflow.exceptions.PluginException; - -import com.alexanderlogistics.metrics.MetricDataService; /** * Hilfsmethoden für die Berechnung und Validierung von Ein und @@ -185,30 +182,6 @@ public class InvoiceUtil { } - /** - * Diese Hilfsmethode berechnet die BusinessPartnerID aus einer Invoice - * - * @param Invoice - * @return - * @throws PluginException - */ - public static String getBPId(ItemCollection invoice) throws PluginException { - String key = ""; - if (isCreditorInvoice(invoice)) { - // key ist cdtr.number - key = invoice.getItemValueString("cdtr.number"); - } else { - // key ist dbtr.number - key = invoice.getItemValueString("dbtr.number"); - } - if (key.isEmpty()) { - throw new PluginException(MetricDataService.class.getName(), ERROR_INVALID_INVOICEDATA, - "Failed to create metrics - missing dbtr/cdtr number!"); - } - - return InvoiceUtil.buildBPID(key); - } - /** * Diese Hilfmethode berechnet die BusinessPartner Namen aus einer Invoice * diff --git a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/CargosoftMigrationRestService.java b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/CargosoftMigrationRestService.java index 4a20ba9..5de1489 100644 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/CargosoftMigrationRestService.java +++ b/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/CargosoftMigrationRestService.java @@ -63,11 +63,15 @@ import org.w3c.dom.Node; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import com.alexanderlogistics.BusinessPartnerService; import com.alexanderlogistics.InvoiceUtil; +import com.alexanderlogistics.KreditorDebitorService; import com.alexanderlogistics.mahnlauf.MahnlaufService; import com.alexanderlogistics.xml.CargosoftXMLInvoiceImportService; import jakarta.ejb.Stateless; +import jakarta.ejb.TransactionAttribute; +import jakarta.ejb.TransactionAttributeType; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @@ -112,6 +116,9 @@ public class CargosoftMigrationRestService implements Serializable { @Inject TeamService teamService; + @Inject + BusinessPartnerService businessPartnerService; + private static Logger logger = Logger.getLogger(CargosoftMigrationRestService.class.getName()); public CargosoftMigrationRestService() { @@ -641,6 +648,98 @@ public class CargosoftMigrationRestService implements Serializable { } } + /** + * Dieser agent prüft ob es für Cargosoft Krediotren/Debitoren Daten schon ein + * passendes Businesspartner Workitem gibt. + * Wenn nicht speichert der Service einmal das cargosft entity was dann zur + * automatischen Neuanlage des businesspartner workflows führt. + * + * @return + * @throws QueryException + * @throws AccessDeniedException + * @throws ProcessingErrorException + * @throws PluginException + * @throws ModelException + */ + @GET + @Path("/bp-import") + @Produces({ MediaType.TEXT_PLAIN }) + public String importBusinessPartner() + throws QueryException, AccessDeniedException, ProcessingErrorException, PluginException, ModelException { + StringBuffer messageBuffer = new StringBuffer(); + int MAX_COUNT = 1000; + String query = "(type:" + KreditorDebitorService.TYPE_CARGOSOFTKREDITOR + ")"; + int updates = 0; + long l = System.currentTimeMillis(); + int batchSize = 500; + int totalObjects = 0; + log("├── Migrate " + KreditorDebitorService.TYPE_CARGOSOFTKREDITOR, messageBuffer); + + // Gesamtanzahl ermitteln + int totalCount = documentService.count(query); + log("│   ├── found " + totalCount + " " + KreditorDebitorService.TYPE_CARGOSOFTKREDITOR + " entities ", + messageBuffer); + + // Berechne Anzahl der benötigten Pages + int totalPages = (int) Math.ceil((double) totalCount / batchSize); + + // Verarbeite Page für Page + for (int pageIndex = 0; pageIndex < totalPages; pageIndex++) { + + List cargosoftDataList = documentService.find(query, batchSize, pageIndex); + + for (ItemCollection cargosoftItemCol : cargosoftDataList) { + totalObjects++; + if (verifyBusinessPartner(cargosoftItemCol, messageBuffer)) { + updates++; + } + if (updates >= MAX_COUNT) + break; + } + + // Fortschritt loggen + log("│ ├── Processed page " + (pageIndex + 1) + " of " + totalPages + + " (" + updates + " total updates)", messageBuffer); + // Optional: Kurze Pause nach jedem 5. Batch + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + if (updates >= MAX_COUNT) + break; + + } + long duration = System.currentTimeMillis() - l; + double objectsPerSecond = totalObjects / (duration / 1000.0); + log("├── Successfully imported " + updates + " business partner objects in " + + duration + "ms (" + String.format("%.1f", objectsPerSecond) + " objects/sec)", messageBuffer); + + return messageBuffer.toString(); + } + + /** + * Hilfsmethode speichert eine cargoosft kreditor object... + * + * @param bpID + * @param messageBuffer + * @return + */ + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + public boolean verifyBusinessPartner(ItemCollection cargosoftItemCol, StringBuffer messageBuffer) { + String vendorNummer = cargosoftItemCol.getItemValueString("_vendor_num"); + String bpID = InvoiceUtil.buildBPID(vendorNummer); + ItemCollection businessPartner = businessPartnerService.getBusinessPartnerByID(bpID); + if (businessPartner == null) { + documentService.save(cargosoftItemCol); + logger.finest("│   │   ├── import " + bpID); + return true; + } + return false; + } + /** * Erzeugt einen XML Baum aus dem XML Raw Daten * @@ -721,4 +820,10 @@ public class CargosoftMigrationRestService implements Serializable { } } + + private void log(String message, StringBuffer messageLog) { + logger.info(message); + messageLog.append(message + "\n"); + + } } 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 deleted file mode 100644 index bbb0fe3..0000000 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorRestService.java +++ /dev/null @@ -1,183 +0,0 @@ -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 com.alexanderlogistics.InvoiceUtil; - -import jakarta.ejb.Stateless; -import jakarta.ejb.TransactionAttribute; -import jakarta.ejb.TransactionAttributeType; -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 metricCreditorService; - - @Inject - MetricDataService metricDataService; - - @GET - @Path("/ping") - @Produces({ MediaType.TEXT_PLAIN }) - public String ping() { - logger.info("GET ping"); - return "ping: " + System.currentTimeMillis(); - } - - /** - * 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") - @Produces({ MediaType.TEXT_PLAIN }) - public Response rebuildMetrics() { - StringBuffer messageBuffer = new StringBuffer(); - long l = System.currentTimeMillis(); - log("├── rebuild cdtr metrics...", messageBuffer); - try { - log("│   ├── delete all metrics", messageBuffer); - metricDataService.deleteAllMetrics(MetricCreditorService.TYPE_METRIC_CREDITOR); - // first clear the metric cache - metricCreditorService.reset(); - log("│   ├── reset metric cache", messageBuffer); - - computeMetrics(messageBuffer); - - 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(); - log("Failed to initialize metrics: " + e.getMessage(), messageBuffer); - return Response.serverError() - .entity(messageBuffer.toString() + e.getMessage()) - .build(); - } - } - - /** - * Diese Methode berechnet alle Metriken auf basis der existierenden Rechnungen - * neu - * - * @throws QueryException - * @throws InterruptedException - * @throws PluginException - * - */ - public void computeMetrics(StringBuffer messageBuffer) - throws QueryException, InterruptedException, PluginException { - long l = System.currentTimeMillis(); - int batchSize = 500; - int totalInvoices = 0; - log("│   │   ├── recalculate metrics...", messageBuffer); - - String query = "($modelversion:rechnungseingang-* OR $modelversion:gutschriftabgleich-*) " + - " AND type:workitem"; - // Gesamtanzahl ermitteln - int totalCount = documentService.count(query); - log("│   │   ├── found " + totalCount + " open invoices", messageBuffer); - - // Berechne Anzahl der benötigten Pages - int totalPages = (int) Math.ceil((double) totalCount / batchSize); - - // Verarbeite Page für Page - for (int pageIndex = 0; pageIndex < totalPages; pageIndex++) { - - totalInvoices = totalInvoices + computeInvoiceMetrics(query, batchSize, pageIndex); - // List invoices = documentService.find(query, batchSize, - // pageIndex); - - // for (ItemCollection invoice : invoices) { - // try { - // ItemCollection metricData = - // metricCreditorService.getMetricByInvoice(invoice); - // // Jetzt Rechnung addieren - // metricCreditorService.addInvoice(metricData, invoice); - // logger.fine("│   │   │   ├── update metric " + InvoiceUtil.getBPId(invoice)); - // metricCreditorService.updateMetric(metricData, true); - // totalInvoices++; - // } catch (PluginException e) { - // // invalid invoice - e.g. no dbtr. number - // } - // } - - // Fortschritt loggen - log("│   │ ├── Processed page " + (pageIndex + 1) + " of " + totalPages + - " (" + totalInvoices + " of " + totalCount + " invoices)", messageBuffer); - // Optional: Kurze Pause nach jedem 5. Batch - Thread.sleep(100); - - } - long duration = System.currentTimeMillis() - l; - double invoicesPerSecond = totalInvoices / (duration / 1000.0); - log("│   │   ├── Successfully processed " + totalInvoices + " invoices in " + - duration + "ms (" + String.format("%.1f", invoicesPerSecond) + " invoices/sec)", messageBuffer); - log("│   │   ├── Updated " + metricCreditorService.getMetricCount() + " metrics.", messageBuffer); - - } - - /** - * Helper method runs in new transaction - * - * @param query - * @param batchSize - * @param pageIndex - * @throws PluginException - * @throws QueryException - */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public int computeInvoiceMetrics(String query, int batchSize, int pageIndex) - throws PluginException, QueryException { - - int updates = 0; - List invoices = documentService.find(query, batchSize, pageIndex); - - for (ItemCollection invoice : invoices) { - try { - ItemCollection metricData = metricCreditorService.getMetricByInvoice(invoice); - // Jetzt Rechnung addieren - metricCreditorService.addInvoice(metricData, invoice); - logger.fine("│   │   │   ├── update metric " + InvoiceUtil.getBPId(invoice)); - metricCreditorService.updateMetric(metricData, true); - updates++; - } catch (PluginException e) { - // invalid invoice - e.g. no dbtr. number - } - } - return updates; - } - - private void log(String message, StringBuffer messageLog) { - logger.info(message); - messageLog.append(message + "\n"); - - } - -} 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 deleted file mode 100644 index 3da4b99..0000000 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricCreditorService.java +++ /dev/null @@ -1,394 +0,0 @@ -package com.alexanderlogistics.metrics; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -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.archive.core.SnapshotService; -import org.imixs.workflow.ItemCollection; -import org.imixs.workflow.engine.DocumentService; -import org.imixs.workflow.engine.ProcessingEvent; -import org.imixs.workflow.engine.SetupEvent; -import org.imixs.workflow.exceptions.PluginException; - -import com.alexanderlogistics.InvoiceUtil; -import com.alexanderlogistics.KreditorDebitorService; - -import jakarta.annotation.security.DeclareRoles; -import jakarta.annotation.security.RolesAllowed; -import jakarta.annotation.security.RunAs; -import jakarta.ejb.Singleton; -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.creditor). - *

- * 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") -@Singleton -@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_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) - MetricRegistry metricRegistry; - - @Inject - DocumentService documentService; - - @Inject - KreditorDebitorService kreditorDebitorService; - - @Inject - MetricDataService metricDataService; - - @Inject - @ConfigProperty(name = "metrics.enabled", defaultValue = "false") - private boolean metricsEnabled; - - /** - * Init all metrics during setup. Called by the Imixs SetupService - */ - public void initializeMetrics(@Observes SetupEvent setupEvent) { - if (!metricsEnabled) { - return; - } - - long l = System.currentTimeMillis(); - int batchSize = 500; - int totalMetrics = 0; - - try { - logger.info("├── Initializing creditor metrics from database..."); - String query = "(type:" + TYPE_METRIC_CREDITOR + ")"; - - // Gesamtanzahl ermitteln - int totalCount = documentService.count(query); - - // Berechne Anzahl der benötigten Pages - int totalPages = (int) Math.ceil((double) totalCount / batchSize); - - // Verarbeite Page für Page - for (int pageIndex = 0; pageIndex < totalPages; pageIndex++) { - List metrics = documentService.find(query, batchSize, pageIndex); - - for (ItemCollection metric : metrics) { - updateMetric(metric, false); - totalMetrics++; - } - // Fortschritt loggen - logger.info("│ ├── Processed page " + (pageIndex + 1) + " of " + totalPages + - " (" + totalMetrics + " of " + totalCount + " metrics)"); - // Optional: Kurze Pause nach jedem Batch - Thread.sleep(100); - } - - long duration = System.currentTimeMillis() - l; - double metricsPerSecond = totalMetrics / (duration / 1000.0); - - logger.info("├── Successfully initialized " + totalMetrics + " metrics in " + - duration + "ms (" + String.format("%.1f", metricsPerSecond) + " metrics/sec)"); - - } catch (Exception e) { - logger.warning("Failed to initialize metrics: " + e.getMessage()); - } - } - - /** - * Reset the internal metricCache and clears all registered Gauges. - */ - public void reset() { - metricCache.clear(); - registeredGauges.clear(); - } - - public long getMetricCount() { - return metricCache.size(); - } - - /** - * 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; - } - - ItemCollection invoice = processingEvent.getDocument(); - if (!InvoiceUtil.isCreditorInvoice(invoice)) { - // skip event - return; - } - - // 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()); - if (lastInvoice != null) { - try { - ItemCollection lastMetricData = getMetricByInvoice(lastInvoice); - // update last metric only if exists... - if (!isNewMetric(lastMetricData)) { - subtractInvoice(lastMetricData, lastInvoice); - updateMetric(lastMetricData, true); - } - } catch (PluginException e) { - // invalid invoice - e.g. no cdtr. number - } - } - - try { - // load the invoice metric and add the saldo... - ItemCollection metricData = getMetricByInvoice(invoice); - // Saldo-Berechnung - addInvoice(metricData, invoice); - updateMetric(metricData, true); - logger.info("Metric cdtr update took " + (System.currentTimeMillis() - l) + "ms"); - } catch (PluginException e) { - // invalid invoice - e.g. no cdtr. number - } - } - - } - - /** - * 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 - * metric from the database. If not metric exists in the database the method - * automatically creates a new metric data object. - * - * The method throws a PluginException if no metric can be build from this - * invoice (e.g. missing cdtr.number). - * - * @param invoice - * @return - * @throws PluginException - */ - public ItemCollection getMetricByInvoice(ItemCollection invoice) throws PluginException { - if (invoice == null) { - return null; - } - String metricKey = MetricDataService.buildKeyByInvoice(invoice); - ItemCollection metricData = metricCache.get(metricKey); - // if metric still null we create a new metric data object. - if (metricData == null) { - metricData = createMetricData(invoice); - } - return metricData; - } - - /** - * Returns a metric data object by key - * - * @param key - * @return - */ - public ItemCollection getMetric(String key) { - return metricCache.get(key); - } - - /** - * 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())); - } - - /** - * Creates an empty Creditor Metric Data Object (ItemCollection) - *

- * 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 createMetricData(ItemCollection invoice) throws PluginException { - if (invoice == null) { - return null; - } - String key = MetricDataService.buildKeyByInvoice(invoice); - ItemCollection metricData = new ItemCollection(); - metricData.setType(TYPE_METRIC_CREDITOR); - metricData.setItemValue("name", key); - metricData.setItemValue(SnapshotService.NOSNAPSHOT, true); - metricData.setItemValue("bp.id", InvoiceUtil.getBPId(invoice)); - metricData.setItemValue("bp.name", InvoiceUtil.getBPName(invoice)); - metricData.setItemValue("country", invoice.getItemValueString("invoice.country")); - metricData.setItemValue("currency", invoice.getItemValueString("invoice.currency")); - metricData.setItemValue("department", invoice.getItemValueString("space.name")); - - return metricData; - } - - /** - * This method registers and updates the metric meta data objects based on a - * given metricData object. Optional the metric data object is persisted. - * - * @param metricData - the metricData ItemCollection - * @param persist - if true the metricData entity will be persisted - */ - public void updateMetric(ItemCollection metricData, boolean persist) { - String metricKey = metricData.getItemValueString("name"); - - // Cache aktualisieren - metricCache.put(metricKey, metricData); - - // In Datenbank persistieren - if (persist) { - metricData.setItemValue(SnapshotService.NOSNAPSHOT, true); - documentService.save(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)); - tags.add(new Tag("name", bpName)); - tags.add(new Tag("country", country)); - tags.add(new Tag("currency", currency)); - tags.add(new Tag("department", department)); - - // Saldo Gauge - Metadata balanceMetadata = Metadata.builder() - .withName("cdtr.balance") - .withDescription("Creditor Balance") - .build(); - 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])); - } - } - - // /** - // * Helper Method that refreshes all gauges. The method is called by the - // * RestService during a rebuild. - // */ - // public void updateAllMetrics() { - // List keys = getMetricKeys(); - // for (String hashKey : keys) { - // ItemCollection metricData = getMetric(hashKey); - // updateMetric(metricData, false); - // } - // } - - /** - * Addiert den saldo einer Invoice zu einem metricData object - * - * @param metricData - * @param invoice - */ - public void addInvoice(ItemCollection metricData, ItemCollection invoice) { - 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! - 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_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! - invoiceSaldo = 0.0; - } - // subtract only if metric saldo exists - 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)); - - } - -} \ 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 deleted file mode 100644 index f49dd31..0000000 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDataService.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.alexanderlogistics.metrics; - -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; - -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; - - /** - * 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; - } - - /** - * Builds the metric hash key by the invoice attributes. The returned key can be - * used for caching the metric. - * - * The metric key does not include the dbtr/cdtr name as this name is not - * relevant for building a hashkey. - * - * @param invoice - The invoice ItemCollection containing the necessary - * attributes - * @return A unique hash string based on the invoice attributes - * @throws PluginException - * @throws IllegalArgumentException if the invoice is null - */ - public static String buildKeyByInvoice(ItemCollection invoice) throws PluginException { - // Validate input - Objects.requireNonNull(invoice, "Invoice must not be null"); - - String bpNumber = InvoiceUtil.getBPId(invoice); - String country = invoice.getItemValueString("invoice.country"); - 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", - bpNumber, - country, - 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 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); - } - } -} \ 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 deleted file mode 100644 index dba54f0..0000000 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDebitorRestService.java +++ /dev/null @@ -1,180 +0,0 @@ -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 com.alexanderlogistics.InvoiceUtil; - -import jakarta.ejb.Stateless; -import jakarta.ejb.TransactionAttribute; -import jakarta.ejb.TransactionAttributeType; -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/dbtr") -@Stateless -public class MetricDebitorRestService { - - private static Logger logger = Logger.getLogger(MetricDebitorRestService.class.getName()); - - @Inject - DocumentService documentService; - - @Inject - MetricDebitorService metricDebitorService; - - @Inject - MetricDataService metricDataService; - - @GET - @Path("/ping") - @Produces({ MediaType.TEXT_PLAIN }) - public String ping() { - logger.info("GET ping"); - return "ping: " + System.currentTimeMillis(); - } - - /** - * This method initializes the metrics for all debitors with open invoices. - * - * The method first deletes all existing metrics and than creates or updates the - * metric entires for each debitor. - * - * @return - */ - @GET - @Path("/rebuild") - @Produces({ MediaType.TEXT_PLAIN }) - public Response rebuildMetrics() { - - StringBuffer messageBuffer = new StringBuffer(); - long l = System.currentTimeMillis(); - log("├── rebuild dbtr metrics...", messageBuffer); - - try { - log("│   ├── delete all metrics", messageBuffer); - // first clear the metric cache - metricDebitorService.reset(); - log("│   ├── reset metric cache", messageBuffer); - - computeMetrics(messageBuffer); - - 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(); - log("Failed to initialize metrics: " + e.getMessage(), messageBuffer); - return Response.serverError() - .entity(messageBuffer.toString() + e.getMessage()) - .build(); - } - } - - /** - * Diese Methode berechnet alle Metriken auf basis der existierenden Rechnungen - * neu - * - * @throws QueryException - * @throws InterruptedException - * @throws PluginException - * - */ - public void computeMetrics(StringBuffer messageBuffer) - throws QueryException, InterruptedException, PluginException { - long l = System.currentTimeMillis(); - int batchSize = 500; - int totalInvoices = 0; - log("│   │   ├── recalculate metrics...", messageBuffer); - - String query = "$modelversion:rechnungsausgang-* AND type:workitem"; - // Gesamtanzahl ermitteln - int totalCount = documentService.count(query); - log("│   │   ├── found " + totalCount + " open invoices", messageBuffer); - - // Berechne Anzahl der benötigten Pages - int totalPages = (int) Math.ceil((double) totalCount / batchSize); - - // Verarbeite Page für Page - for (int pageIndex = 0; pageIndex < totalPages; pageIndex++) { - totalInvoices = totalInvoices + computeInvoiceMetrics(query, batchSize, pageIndex); - // List invoices = documentService.find(query, batchSize, - // pageIndex); - - // for (ItemCollection invoice : invoices) { - // try { - // ItemCollection metricData = metricDebitorService.getMetricByInvoice(invoice); - // // Jetzt Rechnung addieren - // metricDebitorService.addInvoice(metricData, invoice); - // logger.fine("│   │   │   ├── update metric " + InvoiceUtil.getBPId(invoice)); - // metricDebitorService.updateMetric(metricData, true); - // totalInvoices++; - // } catch (PluginException e) { - // // invalid invoice - e.g. no dbtr. number - // } - // } - - // Fortschritt loggen - log("│   │ ├── Processed page " + (pageIndex + 1) + " of " + totalPages + - " (" + totalInvoices + " of " + totalCount + " invoices)", messageBuffer); - // Optional: Kurze Pause nach jedem 5. Batch - Thread.sleep(100); - - } - long duration = System.currentTimeMillis() - l; - double invoicesPerSecond = totalInvoices / (duration / 1000.0); - log("│   │   ├── Successfully processed " + totalInvoices + " invoices in " + - duration + "ms (" + String.format("%.1f", invoicesPerSecond) + " invoices/sec)", messageBuffer); - log("│   │   ├── Updated " + metricDebitorService.getMetricCount() + " metrics.", messageBuffer); - - } - - /** - * Helper method runs in new transaction - * - * @param query - * @param batchSize - * @param pageIndex - * @throws PluginException - * @throws QueryException - */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public int computeInvoiceMetrics(String query, int batchSize, int pageIndex) - throws PluginException, QueryException { - - int updates = 0; - List invoices = documentService.find(query, batchSize, pageIndex); - - for (ItemCollection invoice : invoices) { - try { - ItemCollection metricData = metricDebitorService.getMetricByInvoice(invoice); - // Jetzt Rechnung addieren - metricDebitorService.addInvoice(metricData, invoice); - logger.fine("│   │   │   ├── update metric " + InvoiceUtil.getBPId(invoice)); - metricDebitorService.updateMetric(metricData, true); - updates++; - } catch (PluginException e) { - // invalid invoice - e.g. no dbtr. number - } - } - return updates; - } - - private void log(String message, StringBuffer messageLog) { - logger.info(message); - messageLog.append(message + "\n"); - - } -} 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 deleted file mode 100644 index 1e18e07..0000000 --- a/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/metrics/MetricDebitorService.java +++ /dev/null @@ -1,382 +0,0 @@ -package com.alexanderlogistics.metrics; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -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.archive.core.SnapshotService; -import org.imixs.workflow.ItemCollection; -import org.imixs.workflow.engine.DocumentService; -import org.imixs.workflow.engine.ProcessingEvent; -import org.imixs.workflow.engine.SetupEvent; -import org.imixs.workflow.exceptions.PluginException; - -import com.alexanderlogistics.InvoiceUtil; - -import jakarta.annotation.security.DeclareRoles; -import jakarta.annotation.security.RolesAllowed; -import jakarta.annotation.security.RunAs; -import jakarta.ejb.Singleton; -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 Debitoren Metric Entity (type=metric.debitor). - *

- * 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") -@Singleton -@ApplicationScoped -public class MetricDebitorService { - - private static Logger logger = Logger.getLogger(MetricDebitorService.class.getName()); - private final ConcurrentHashMap metricCache = new ConcurrentHashMap<>(); - private final Set registeredGauges = ConcurrentHashMap.newKeySet(); - - public static final String TYPE_METRIC_DEBITOR = "metric.debitor"; - 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) - MetricRegistry metricRegistry; - - @Inject - DocumentService documentService; - - @Inject - MetricDataService metricDataService; - - @Inject - @ConfigProperty(name = "metrics.enabled", defaultValue = "false") - private boolean metricsEnabled; - - /** - * Init all metrics during setup. Called by the Imixs SetupService - */ - public void initializeMetrics(@Observes SetupEvent setupEvent) { - if (!metricsEnabled) { - return; - } - - long l = System.currentTimeMillis(); - int batchSize = 500; - int totalMetrics = 0; - - try { - logger.info("├── Initializing debitor metrics from database..."); - String query = "(type:" + TYPE_METRIC_DEBITOR + ")"; - - // Gesamtanzahl ermitteln - int totalCount = documentService.count(query); - - // Berechne Anzahl der benötigten Pages - int totalPages = (int) Math.ceil((double) totalCount / batchSize); - - // Verarbeite Page für Page - for (int pageIndex = 0; pageIndex < totalPages; pageIndex++) { - List metrics = documentService.find(query, batchSize, pageIndex); - - for (ItemCollection metric : metrics) { - updateMetric(metric, false); - totalMetrics++; - } - // Fortschritt loggen - logger.info("│ ├── Processed page " + (pageIndex + 1) + " of " + totalPages + - " (" + totalMetrics + " of " + totalCount + " metrics)"); - // Optional: Kurze Pause nach jedem Batch - Thread.sleep(100); - } - - long duration = System.currentTimeMillis() - l; - double metricsPerSecond = totalMetrics / (duration / 1000.0); - - logger.info("├── Successfully initialized " + totalMetrics + " metrics in " + - duration + "ms (" + String.format("%.1f", metricsPerSecond) + " metrics/sec)"); - - } catch (Exception e) { - logger.warning("Failed to initialize metrics: " + e.getMessage()); - } - } - - /** - * Reset internal metricCache and clear registered Gauges. - */ - public void reset() { - metricCache.clear(); - registeredGauges.clear(); - } - - public long getMetricCount() { - return metricCache.size(); - } - - /** - * 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; - } - - ItemCollection invoice = processingEvent.getDocument(); - if (!invoice.getModelVersion().startsWith("rechnungsausgang-")) { - // skip event - return; - } - - // 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()); - if (lastInvoice != null) { - try { - ItemCollection lastMetricData = getMetricByInvoice(lastInvoice); - if (!isNewMetric(lastMetricData)) { - subtractInvoice(lastMetricData, lastInvoice); - updateMetric(lastMetricData, true); - } - } catch (PluginException e) { - // invalid invoice - e.g. no cdtr. number - } - } - - try { - // load the invoice metric and add the saldo... - ItemCollection metricData = getMetricByInvoice(invoice); - // Saldo-Berechnung - addInvoice(metricData, invoice); - updateMetric(metricData, true); - - logger.info("Metric dbtr update took " + (System.currentTimeMillis() - l) + "ms"); - } catch (PluginException e) { - // invalid invoice - e.g. no dbtr. number - } - } - - } - - /** - * 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 - * metric from the database. If not metric exists in the database the method - * automatically creates a new metric data object. - * - * The method throws a PluginException if no metric can be build from this - * invoice (e.g. missing dbtr.number). - * - * @param invoice - * @return - * @throws PluginException - */ - public ItemCollection getMetricByInvoice(ItemCollection invoice) throws PluginException { - if (invoice == null) { - return null; - } - String metricKey = MetricDataService.buildKeyByInvoice(invoice); - ItemCollection metricData = metricCache.get(metricKey); - // 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); - } - - /** - * 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())); - } - - /** - * 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 - * @throws PluginException - */ - private ItemCollection createMetaData(ItemCollection invoice) throws PluginException { - if (invoice == null) { - return null; - } - String key = MetricDataService.buildKeyByInvoice(invoice); - ItemCollection metricData = new ItemCollection(); - metricData.setType(TYPE_METRIC_DEBITOR); - metricData.setItemValue("name", key); - metricData.setItemValue(SnapshotService.NOSNAPSHOT, true); - metricData.setItemValue("bp.id", InvoiceUtil.getBPId(invoice)); - metricData.setItemValue("bp.name", InvoiceUtil.getBPName(invoice)); - metricData.setItemValue("country", invoice.getItemValueString("invoice.country")); - metricData.setItemValue("currency", invoice.getItemValueString("invoice.currency")); - metricData.setItemValue("department", invoice.getItemValueString("space.name")); - - return metricData; - } - - /** - * This method registers and updates the metric meta data objects based on a - * given metricData object. Optional the metric data object is persisted. - * - * @param metricData - the metricData ItemCollection - * @param persist - if true the metricData entity will be persisted - */ - public void updateMetric(ItemCollection metricData, boolean persist) { - String metricKey = metricData.getItemValueString("name"); - - // Cache aktualisieren - metricCache.put(metricKey, metricData); - - // In Datenbank persistieren - if (persist) { - metricData.setItemValue(SnapshotService.NOSNAPSHOT, true); - documentService.save(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)); - tags.add(new Tag("name", bpName)); - 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_METRIC_BALANCE) + - " " + currency); - - // Saldo Gauge - Metadata balanceMetadata = Metadata.builder() - .withName("dbtr.balance") - .withDescription("Debitor Balance") - .build(); - 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])); - } - - } - - /** - * Addiert den saldo einer Invoice zu einem metricData object - * - * @param metricData - * @param invoice - */ - public void addInvoice(ItemCollection metricData, ItemCollection invoice) { - 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! - invoiceSaldo = 0.0; - } - - logger.fine("│   │   │   │   ├── Invoice: " + invoice.getItemValueString("invoice.number") + " Saldo=" - + 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 invoiceSaldo = invoice.getItemValueDouble(ITEM_INVOICE_SALDO); - double invoiceTotal = invoice.getItemValueDouble(ITEM_INVOICE_TOTAL); - logger.fine("│   │   │   │   ├──Invoice: " + invoice.getItemValueString("invoice.number") + " Saldo=" - + invoiceSaldo); - if (!"workitem".equals(invoice.getType()) || invoice.getTaskID() >= 5800) { - // vorgang ist archiviert oder gelöscht worden => saldo = 0! - invoiceSaldo = 0.0; - } - // subtract only if metric saldo exists - 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)); - - } - -} \ No newline at end of file diff --git a/workflow/businesspartner-en-1.0.0.bpmn b/workflow/businesspartner-en-1.0.0.bpmn new file mode 100644 index 0000000..fb3fc66 --- /dev/null +++ b/workflow/businesspartner-en-1.0.0.bpmn @@ -0,0 +1,883 @@ + + + + + + + association_83sZPw + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + task_ToMZaQ + event_WmkBvw + event_1clvlg + task_Q80A1Q + task_B8Pi7A + event_aSdkwg + event_Tr0aug + dataObject_UQIXAQ + event_vwH0cA + textAnnotation_LyGTCw + event_TuyuwQ + event_h0Kk5g + task_delO7Q + event_1OVorA + event_baWU4w + event_ofjVfg + event_i7yQVw + gateway_uK0c6g + event_6250fg + event_VM0fEQ + gateway_s0J53g + task_NohgrQ + event_AMs05g + event_feLZFQ + event_GTXFMw + event_u8OBFg + + + + + + + + + + + + + + + + + + + sequenceFlow_25otUg + sequenceFlow_S9LVXg + + + + sequenceFlow_25otUg + + + + sequenceFlow_rwdWxw + + + + + + + + + + + partner.name (partner.id)]]> + + + Adresse:
+partner.name
+partner.address
+partner.zip partner.city
+
+ +BPID: partner.id
+Kreditoren-Nr.: cdtr.number
+Debitoren-Nr.: dbtr.number
+]]>
+
+ + + + + + + + + + + + + + + +
+ + sequenceFlow_avahXg + sequenceFlow_9atLFA + sequenceFlow_T5L9aQ + sequenceFlow_b4BcVA + sequenceFlow_8O1EtQ + sequenceFlow_ShuXXA + sequenceFlow_03b60w +
+ + + + partner.name (partner.id)]]> + + + + + + Adresse:
+partner.name
+partner.address
+partner.zip partner.city
+
+ +BPID: partner.id
+Kreditoren-Nr.: cdtr.number
+Debitoren-Nr.: dbtr.number
+]]>
+
+ + + + + + + + + + + + + + + + + + +
+ + sequenceFlow_1mXO8A + sequenceFlow_rwdWxw + sequenceFlow_gC0I9w + sequenceFlow_kS2fMA +
+ + + + + + + + + + Partnermanagement]]> + + + + sequenceFlow_jAU3VA + sequenceFlow_8O1EtQ + + + + + + + + + + + home]]> + + + + sequenceFlow_1mXO8A + sequenceFlow_b0Bj3Q + sequenceFlow_neSsFQ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + sequenceFlow_avahXg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sequenceFlow_9atLFA + + + + + + + + + + + + + + + sequenceFlow_gC0I9w + + + + + + + + + + + partner.name (partner.id)]]> + + + Adresse:
+partner.name
+partner.address
+partner.zip partner.city
+
+ +BPID: partner.id
+Kreditoren-Nr.: cdtr.number
+Debitoren-Nr.: dbtr.number
+]]>
+
+ + + + + + + + + + + + + + + + + + +
+ + sequenceFlow_NNbMhg + sequenceFlow_avahXg + sequenceFlow_pvIENQ + sequenceFlow_BWOtzA + sequenceFlow_ofWWuw + sequenceFlow_b0Bj3Q + sequenceFlow_BHsRkQ +
+ + + + + + + + + + Partnermanagement]]> + + + + sequenceFlow_BHsRkQ + sequenceFlow_5pT4Tw + + + + + + + + + + + home]]> + + + + sequenceFlow_pvIENQ + sequenceFlow_b4BcVA + + + + + + + + + + + + + + + + + + sequenceFlow_BWOtzA + + + + + + + + Partnermanagement]]> + + + + + + + sequenceFlow_ofWWuw + + + + + + + + + + + + + sequenceFlow_S9LVXg + sequenceFlow_jAU3VA + sequenceFlow_8BTEtw + + + + + + + + + + + + + + + + sequenceFlow_kS2fMA + + + + + + + + Partnermanagement]]> + + + + + + + sequenceFlow_T5L9aQ + sequenceFlow_MPlPww + + sequenceFlow_rNZ3tw + + + + sequenceFlow_MPlPww + sequenceFlow_aH0vCQ + sequenceFlow_ShuXXA + + + + + + + + partner.name (partner.id)]]> + + + Adresse:
+partner.name
+partner.address
+partner.zip partner.city
+
+ +BPID: partner.id
+Kreditoren-Nr.: cdtr.number
+Debitoren-Nr.: dbtr.number
+]]>
+
+ + + + + + + + + + + + + + + + + + +
+ + sequenceFlow_9atLFA + sequenceFlow_8O1EtQ + sequenceFlow_aH0vCQ + sequenceFlow_neSsFQ + sequenceFlow_zd6RWA + sequenceFlow_rNZ3tw +
+ + + + + + + + + + + + + + + + + sequenceFlow_5pT4Tw + + + + + sequenceFlow_8BTEtw + + + + + + + + + + + sequenceFlow_03b60w + + + + + + + + sequenceFlow_zd6RWA + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +