metric service verbessert

This commit is contained in:
Ralph Soika 2025-01-19 22:55:55 +01:00
parent 01cec74af6
commit 8fad186e39
12 changed files with 347 additions and 148 deletions

View file

@ -1,8 +1,15 @@
# Versionen
### 1.3.1 (Development - Office-Workflow 5.0.4 )
- Mulit Currecy Feature für Ausgangsrechnungen
- Neue Analyse Boards
- Neue SOA
- Neues Mahnwesen
### 1.3.0 (Development - Office-Workflow 5.0.2 )
### 1.3.0
- Migration auf Imixs-Office-Workflow 5.0.2
- Upgrade Imixs-Workflow 6.7

View file

@ -82,10 +82,23 @@ 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/
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
dbdata:
prometheusdata:

View file

@ -0,0 +1,220 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 1,
"links": [],
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "ee5zvwsdjb400f"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "green",
"mode": "fixed"
},
"decimals": 0,
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
},
{
"color": "red",
"value": 80
}
]
},
"unit": "locale"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 23,
"x": 0,
"y": 0
},
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"text": {
"titleSize": 16,
"valueSize": 30
},
"textMode": "value_and_name",
"wideLayout": true
},
"pluginVersion": "11.3.2",
"targets": [
{
"disableTextWrap": false,
"editorMode": "code",
"exemplar": false,
"expr": "sum by(department, currency) (application_dbtr_balance{department=~\".+\"})",
"fullMetaSearch": false,
"hide": false,
"includeNullMetadata": true,
"instant": false,
"interval": "",
"legendFormat": "{{department}} {{currency}}",
"range": true,
"refId": "A",
"useBackend": false
}
],
"title": "Debitoren - Saldo",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "ee5zvwsdjb400f"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 2,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 4
},
"id": 1,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "11.3.2",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "ee5zvwsdjb400f"
},
"disableTextWrap": false,
"editorMode": "code",
"exemplar": false,
"expr": "sum by(department) (application_dbtr_balance{department=~\".+\"})",
"fullMetaSearch": false,
"includeNullMetadata": true,
"legendFormat": "__auto",
"range": true,
"refId": "A",
"useBackend": false
}
],
"title": "Kreditor",
"type": "timeseries"
}
],
"preload": false,
"schemaVersion": 40,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "AGL",
"uid": "ce5zwc5vzt5hce",
"version": 14,
"weekStart": ""
}

View file

@ -0,0 +1,22 @@
global:
scrape_interval: 15s # By default, scrape targets every 15 seconds.
# Attach these labels to any time series or alerts when communicating with
# external systems (federation, remote storage, Alertmanager).
external_labels:
monitor: 'imixs-monitor'
scrape_configs:
# Prometheus itself
# - job_name: 'prometheus'
# scrape_interval: 5s
# static_configs:
# - targets: ['localhost:9090']
# Imixs-Office-Workflow Job (Wildfly Metrics)
- job_name: 'imixs'
scrape_interval: 60s
metrics_path: /metrics
static_configs:
- targets: ['app:9990']

View file

@ -34,8 +34,9 @@ public class InvoiceOutgoingPlugin extends AbstractPlugin {
String dbtrNumber = workitem.getItemValueString("dbtr.number");
ItemCollection debitor = kreditorService.findDebitor(dbtrNumber);
if (debitor != null) {
// update mail
// update mail and country
workitem.setItemValue("dbtr.mail", debitor.getItemValueString("cdtr.mail"));
workitem.setItemValue("invoice.country", debitor.getItemValueString("_vendor_country"));
}
String img = "";

View file

@ -361,6 +361,8 @@ public class InvoicePlugin extends AbstractPlugin {
// search the kreditor
ItemCollection cdtr = kreditorService.findCreditorDebitor(cdtrNumber);
if (cdtr != null) {
// update country
workitem.setItemValue("invoice.country", cdtr.getItemValueString("_vendor_country"));
// Prüfen ob wir die IBAN schon kennen
if (iban.equals(cdtr.getItemValueString("cdtr.iban")) && bic.equals(cdtr.getItemValueString("cdtr.bic"))) {
return;

View file

@ -496,7 +496,7 @@ public class DatevCargosoftImportAdapter implements SignalAdapter {
// Anhand der Creditoren Stammdaten errrechnen wir die Sprache für diese
// Rechnung
String country = dbtrItemCol.getItemValueString("_VENDOR_COUNTRY");
datevWorkitem.setItemValue("dbtr.country", country);
datevWorkitem.setItemValue("invoice.country", country);
if (country.equalsIgnoreCase("de") || country.equalsIgnoreCase("ch") || country.equalsIgnoreCase("at")) {
datevWorkitem.setItemValue("invoice.language", "DE");
} else {
@ -506,27 +506,6 @@ public class DatevCargosoftImportAdapter implements SignalAdapter {
// Jezt noch das Space mapping
mahnlaufService.mapInvoiceTextToSpace(datevWorkitem, spacePosMappings);
// // dazu kucken wir in alle spaces den config wert 'pos.mapping'
// String text = datevWorkitem.getItemValueString("invoice.text");
// if (!text.isEmpty()) {
// boolean found = false;
// for (Map.Entry<String, List<String>> entry : spacePosMappings.entrySet()) {
// List<String> expressionsList = entry.getValue();
// for (String expr : expressionsList) {
// Pattern p = Pattern.compile(expr);
// Matcher m = p.matcher(text);
// if (m.find()) {
// // we have a match
// datevWorkitem.setItemValue("space.ref", entry.getKey());
// found = true;
// break;
// }
// }
// if (found) {
// break;
// }
// }
// }
// Processe die Invoice
datevWorkitem.model(INITIAL_MODEL_VERSION_INVOICE).task(INITIAL_TASK_ID_INVOICE)

View file

@ -3,7 +3,6 @@ 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;
@ -20,6 +19,7 @@ import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.QueryException;
import com.alexanderlogistics.InvoiceUtil;
import com.alexanderlogistics.KreditorDebitorService;
import jakarta.annotation.security.DeclareRoles;
import jakarta.annotation.security.RolesAllowed;
@ -30,7 +30,7 @@ import jakarta.inject.Inject;
/**
* Dieser Service reagiert auch ProcessingEvents und speichert/aktualisiert die
* zugehörige Creditor Metric Entity (type=metric.debitor).
* zugehörige Creditor Metric Entity (type=metric.creditor).
* <p>
* Beim Rechnungseingang gilt eine Rechnung als Bezahlt wenn diese einen finalen
* Status erreicht hat (>=5800)
@ -68,6 +68,9 @@ public class MetricCreditorService {
@Inject
DocumentService documentService;
@Inject
KreditorDebitorService kreditorDebitorService;
@Inject
MetricDataService metricDataService;
@ -139,7 +142,7 @@ public class MetricCreditorService {
* @throws PluginException
*/
public ItemCollection getMetricByInvoice(ItemCollection invoice) throws PluginException {
String metricKey = buildKeyByInvoice(invoice);
String metricKey = MetricDataService.buildKeyByInvoice(invoice);
ItemCollection metricData = metricCache.get(metricKey);
if (metricData == null) {
metricData = loadMetric(invoice);
@ -180,40 +183,6 @@ public class MetricCreditorService {
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
@ -224,20 +193,20 @@ public class MetricCreditorService {
* @throws PluginException
*/
private ItemCollection loadMetric(ItemCollection invoice) throws PluginException {
ItemCollection debitorMetric = null;
ItemCollection creditorMetric = null;
try {
String metricKey = buildKeyByInvoice(invoice);
String metricKey = MetricDataService.buildKeyByInvoice(invoice);
String query = "(type:" + TYPE_METRIC_CREDITOR + ") AND (name:" + metricKey + ")";
List<ItemCollection> result = documentService.find(query, 1, 0, "$modified", true);
if (result.size() > 0) {
debitorMetric = result.get(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 debitorMetric;
return creditorMetric;
}
/**
@ -250,41 +219,35 @@ public class MetricCreditorService {
* @return
*/
private ItemCollection createMetaData(ItemCollection invoice) {
String key = buildKeyByInvoice(invoice);
String key = MetricDataService.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"));
metricData.setItemValue("country", invoice.getItemValueString("invoice.country"));
metricData.setItemValue("currency", invoice.getItemValueString("invoice.currency"));
metricData.setItemValue("department", invoice.getItemValueString("space.name"));
return metricData;
}
/**
* Helper method to register a gauge for a debitor
* Helper method to register a gauge for a creditor
*
* @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");
String country = metricData.getItemValueString("country");
String department = metricData.getItemValueString("department");
String currency = metricData.getItemValueString("currency");
// Prüfen ob Gauge bereits registriert ist
if (registeredGauges.add(metricKey)) { // returns true newly added
List<Tag> 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 +
tags.add(new Tag("country", country));
tags.add(new Tag("currency", currency));
tags.add(new Tag("department", department));
logger.info("register new metric for department: " + department +
", " + metricData.getItemValueString(ITEM_SALDO) +
" " + metricData.getItemValueString("invoice.currency"));
Metadata metadata = Metadata.builder()
@ -297,7 +260,7 @@ public class MetricCreditorService {
() -> metricCache.get(metricKey).getItemValueDouble(ITEM_SALDO),
tags.toArray(new Tag[0]));
} else {
logger.fine("Gauge already registered for creditor: " + cdtrNumber);
logger.fine("Cdtr Gauge already registered for department: " + department);
}
}
@ -343,13 +306,10 @@ public class MetricCreditorService {
// 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 {
// subtract only if metric saldo exists
if (metricData.hasItem(ITEM_SALDO)) {
double lastSaldo = metricData.getItemValueDouble(ITEM_SALDO);
metricData.setItemValue(ITEM_SALDO, lastSaldo - invoiceTotal);
metricData.setItemValue(ITEM_SALDO, InvoiceUtil.round(lastSaldo - invoiceTotal));
}
}

View file

@ -1,5 +1,7 @@
package com.alexanderlogistics.metrics;
import java.util.Objects;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.DocumentService;
@ -46,4 +48,32 @@ public class MetricDataService {
return dirtyInvoice;
}
/**
* 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
*/
public static String buildKeyByInvoice(ItemCollection invoice) {
// Validate input
Objects.requireNonNull(invoice, "Invoice must not be null");
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",
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;
}
}

View file

@ -3,7 +3,6 @@ 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;
@ -133,7 +132,7 @@ public class MetricDebitorService {
* @throws PluginException
*/
public ItemCollection getMetricByInvoice(ItemCollection invoice) throws PluginException {
String metricKey = buildKeyByInvoice(invoice);
String metricKey = MetricDataService.buildKeyByInvoice(invoice);
ItemCollection metricData = metricCache.get(metricKey);
if (metricData == null) {
metricData = loadMetric(invoice);
@ -174,37 +173,6 @@ public class MetricDebitorService {
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 dbtrNumber = invoice.getItemValueString("dbtr.number");
String dbtrName = invoice.getItemValueString("dbtr.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",
dbtrNumber,
dbtrName,
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
@ -217,7 +185,7 @@ public class MetricDebitorService {
private ItemCollection loadMetric(ItemCollection invoice) throws PluginException {
ItemCollection debitorMetric = null;
try {
String metricKey = buildKeyByInvoice(invoice);
String metricKey = MetricDataService.buildKeyByInvoice(invoice);
String query = "(type:" + TYPE_METRIC_DEBITOR + ") AND (name:" + metricKey + ")";
List<ItemCollection> result = documentService.find(query, 1, 0, "$modified", true);
if (result.size() > 0) {
@ -240,38 +208,38 @@ public class MetricDebitorService {
* @return
*/
private ItemCollection createMetaData(ItemCollection invoice) {
String key = buildKeyByInvoice(invoice);
String key = MetricDataService.buildKeyByInvoice(invoice);
ItemCollection metricData = new ItemCollection();
metricData.setType(TYPE_METRIC_DEBITOR);
metricData.setItemValue("name", key);
metricData.setItemValue("dbtr.number", invoice.getItemValueString("dbtr.number"));
metricData.setItemValue("dbtr.name", invoice.getItemValueString("dbtr.name"));
metricData.setItemValue("invoice.currency", invoice.getItemValueString("invoice.currency"));
metricData.setItemValue("space.name", invoice.getItemValueString("space.name"));
metricData.setItemValue("country", invoice.getItemValueString("invoice.country"));
metricData.setItemValue("currency", invoice.getItemValueString("invoice.currency"));
metricData.setItemValue("department", invoice.getItemValueString("space.name"));
return metricData;
}
/**
* Helper method to register a gauge for a debitor
*
* @param dbtrNumber - the debitor number
* @param dbtrName - the debitor name
* @param metricData - the metricData ItemCollection
*/
private void updateGauge(ItemCollection metricData) {
String dbtrNumber = metricData.getItemValueString("dbtr.number");
String dbtrName = metricData.getItemValueString("dbtr.name");
// String dbtrNumber = metricData.getItemValueString("dbtr.number");
// String dbtrName = metricData.getItemValueString("dbtr.name");
String country = metricData.getItemValueString("country");
String department = metricData.getItemValueString("department");
String currency = metricData.getItemValueString("currency");
String metricKey = metricData.getItemValueString("name");
// Prüfen ob Gauge bereits registriert ist
if (registeredGauges.add(metricKey)) { // returns true newly added
List<Tag> tags = new ArrayList<>();
tags.add(new Tag("type", "dbtr"));
tags.add(new Tag("number", dbtrNumber));
tags.add(new Tag("name", dbtrName));
tags.add(new Tag("currency", metricData.getItemValueString("invoice.currency")));
tags.add(new Tag("department", metricData.getItemValueString("space.name")));
logger.info("register new metric for debitor: " + dbtrNumber +
tags.add(new Tag("country", country));
tags.add(new Tag("currency", currency));
tags.add(new Tag("department", department));
logger.info("register new metric for department: " + department +
", " + metricData.getItemValueString(ITEM_SALDO) +
" " + metricData.getItemValueString("invoice.currency"));
" " + currency);
Metadata metadata = Metadata.builder()
.withName("dbtr.balance")
.withDescription("Debitor Balance")
@ -282,7 +250,7 @@ public class MetricDebitorService {
() -> metricCache.get(metricKey).getItemValueDouble(ITEM_SALDO),
tags.toArray(new Tag[0]));
} else {
logger.fine("Gauge already registered for debitor: " + dbtrNumber);
logger.fine("Gauge already registered for department: " + department);
}
}
@ -328,11 +296,8 @@ public class MetricDebitorService {
// 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 {
// 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));
}

View file

@ -426,7 +426,7 @@ public class CargosoftXMLEAkteImportService {
// Anhand der Creditoren Stammdaten errrechnen wir die Sprache für diese
// Rechnung
String country = cdtr.getItemValueString("_VENDOR_COUNTRY");
workitem.setItemValue("cdtr.country", country);
workitem.setItemValue("invoice.country", country);
if (country.equalsIgnoreCase("de") || country.equalsIgnoreCase("ch")
|| country.equalsIgnoreCase("at")) {
workitem.setItemValue("invoice.language", "DE");

View file

@ -457,7 +457,7 @@ public class CargosoftXMLInvoiceImportService {
// Anhand der Creditoren Stammdaten errrechnen wir die Sprache für diese
// Rechnung
String country = dbtr.getItemValueString("_VENDOR_COUNTRY");
workitem.setItemValue("dbtr.country", country);
workitem.setItemValue("invoice.country", country);
if (country.equalsIgnoreCase("de") || country.equalsIgnoreCase("ch")
|| country.equalsIgnoreCase("at")) {
workitem.setItemValue("invoice.language", "DE");