business partner import service

This commit is contained in:
Ralph Soika 2025-02-24 18:59:02 +01:00
parent 632f674bfa
commit 021e7cec34
11 changed files with 1024 additions and 1347 deletions

View file

@ -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

View file

@ -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:

View file

@ -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<String> workflowServiceEndpoint;
@ -91,8 +86,8 @@ public class BusinessPartnerService {
@ConfigProperty(name = WORKFLOW_SERVICE_PASSWORD)
Optional<String> 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<ItemCollection> 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<ItemCollection> search(String phrase) {
long l = System.currentTimeMillis();
List<ItemCollection> searchResult = new ArrayList<ItemCollection>();
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());
}
}

View file

@ -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
*

View file

@ -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<ItemCollection> 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");
}
}

View file

@ -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<ItemCollection> 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<ItemCollection> 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");
}
}

View file

@ -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).
* <p>
* Beim Rechnungseingang gilt eine Rechnung als Bezahlt wenn diese einen finalen
* Status erreicht hat (>=5800)
* <p>
* 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<String, ItemCollection> metricCache = new ConcurrentHashMap<>();
private final Set<String> 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<ItemCollection> 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<String> getMetricKeys() {
return Collections.unmodifiableList(new ArrayList<>(metricCache.keySet()));
}
/**
* Creates an empty Creditor Metric Data Object (ItemCollection)
* <p>
* 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<Tag> 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<String> 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));
}
}

View file

@ -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<ItemCollection> 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);
}
}
}

View file

@ -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<ItemCollection> 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<ItemCollection> 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");
}
}

View file

@ -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).
* <p>
* 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<String, ItemCollection> metricCache = new ConcurrentHashMap<>();
private final Set<String> 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<ItemCollection> 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<String> getMetricKeys() {
return Collections.unmodifiableList(new ArrayList<>(metricCache.keySet()));
}
/**
* Creates an empty Debitor Meta Data Object (ItemCollection)
* <p>
* 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<Tag> 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));
}
}

View file

@ -0,0 +1,883 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:BPMN2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:imixs="http://www.imixs.org/bpmn2" xmlns:open-bpmn="http://open-bpmn.org/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" exporter="org.openbpmn" exporterVersion="1.0.0" targetNamespace="http://open-bpmn.org">
<bpmn2:collaboration id="collaboration_1" name="Default Collaboration">
<bpmn2:participant id="participant_d6UFsQ" name="Default Process" processRef="process_1"/>
<bpmn2:participant id="participant_oxb3dg" name="Business Partner" processRef="process_Hw9Ryg">
<bpmn2:documentation id="documentation_Jerngw"/>
<bpmn2:outgoing>association_83sZPw</bpmn2:outgoing>
</bpmn2:participant>
</bpmn2:collaboration>
<bpmn2:extensionElements>
<open-bpmn:auto-align>true</open-bpmn:auto-align>
<imixs:item name="txtworkflowmodelversion" type="xs:string">
<imixs:value><![CDATA[businesspartner-de-1.0]]></imixs:value>
</imixs:item>
<imixs:item name="txtplugins" type="xs:string">
<imixs:value><![CDATA[org.imixs.workflow.engine.plugins.RulePlugin]]></imixs:value>
<imixs:value><![CDATA[org.imixs.workflow.engine.plugins.SplitAndJoinPlugin]]></imixs:value>
<imixs:value><![CDATA[org.imixs.workflow.engine.plugins.OwnerPlugin]]></imixs:value>
<imixs:value><![CDATA[org.imixs.workflow.engine.plugins.ApproverPlugin]]></imixs:value>
<imixs:value><![CDATA[org.imixs.workflow.engine.plugins.HistoryPlugin]]></imixs:value>
<imixs:value><![CDATA[org.imixs.workflow.engine.plugins.ApplicationPlugin]]></imixs:value>
<imixs:value><![CDATA[org.imixs.workflow.engine.plugins.IntervalPlugin]]></imixs:value>
<imixs:value><![CDATA[org.imixs.workflow.engine.plugins.MailPlugin]]></imixs:value>
<imixs:value><![CDATA[org.imixs.workflow.engine.plugins.ResultPlugin]]></imixs:value>
<imixs:value><![CDATA[org.imixs.marty.team.TeamPlugin]]></imixs:value>
<imixs:value><![CDATA[org.imixs.marty.plugins.SequenceNumberPlugin]]></imixs:value>
</imixs:item>
<imixs:item name="txtfieldmapping" type="xs:string">
<imixs:value><![CDATA[Ersteller | $creator]]></imixs:value>
<imixs:value><![CDATA[Aktueller Bearbeiter | $editor]]></imixs:value>
<imixs:value><![CDATA[Eigentümer | $owner]]></imixs:value>
<imixs:value><![CDATA[Prozess-Verantwortliche| process.manager]]></imixs:value>
<imixs:value><![CDATA[Prozess-Team | process.team]]></imixs:value>
<imixs:value><![CDATA[Prozess-Assistenz | process.assist]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:process definitionalCollaborationRef="collaboration_1" id="process_1" name="Default Process" processType="Public">
<bpmn2:documentation id="documentation_bowXyw"/>
</bpmn2:process>
<bpmn2:process definitionalCollaborationRef="collaboration_1" id="process_Hw9Ryg" isExecutable="true" name="Business Partner" processType="Private">
<bpmn2:laneSet id="laneset_nKajSg" name="Lane Set">
<bpmn2:lane id="lane_Zxa2TA" name="Buchhaltung">
<bpmn2:documentation id="documentation_jEAKkQ"/>
<bpmn2:flowNodeRef>task_ToMZaQ</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_WmkBvw</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_1clvlg</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>task_Q80A1Q</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>task_B8Pi7A</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_aSdkwg</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_Tr0aug</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>dataObject_UQIXAQ</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_vwH0cA</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>textAnnotation_LyGTCw</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_TuyuwQ</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_h0Kk5g</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>task_delO7Q</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_1OVorA</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_baWU4w</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_ofjVfg</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_i7yQVw</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>gateway_uK0c6g</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_6250fg</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_VM0fEQ</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>gateway_s0J53g</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>task_NohgrQ</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_AMs05g</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_feLZFQ</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_GTXFMw</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>event_u8OBFg</bpmn2:flowNodeRef>
</bpmn2:lane>
</bpmn2:laneSet>
<bpmn2:task id="task_ToMZaQ" imixs:processid="1000" name="Neuanlage">
<bpmn2:extensionElements>
<imixs:item name="keyupdateacl" type="xs:string">
<imixs:value><![CDATA[true]]></imixs:value>
</imixs:item>
<imixs:item name="namownershipnames" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="namaddreadaccess" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="namaddwriteaccess" type="xs:string">
<imixs:value/>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_a83GTA"/>
<bpmn2:incoming>sequenceFlow_25otUg</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_S9LVXg</bpmn2:outgoing>
</bpmn2:task>
<bpmn2:startEvent id="event_WmkBvw" name="Start">
<bpmn2:documentation id="documentation_QXQddg"/>
<bpmn2:outgoing>sequenceFlow_25otUg</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:endEvent id="event_1clvlg" name="Ende">
<bpmn2:documentation id="documentation_obvnAg"/>
<bpmn2:incoming>sequenceFlow_rwdWxw</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="sequenceFlow_25otUg" sourceRef="event_WmkBvw" targetRef="task_ToMZaQ">
<bpmn2:documentation id="documentation_Tzm1bw"/>
</bpmn2:sequenceFlow>
<bpmn2:task id="task_Q80A1Q" imixs:processid="1100" name="Activ">
<bpmn2:extensionElements>
<imixs:item name="txtimageurl" type="xs:string">
<imixs:value><![CDATA[typcn-user|typcn-tick,imixs-success]]></imixs:value>
</imixs:item>
<imixs:item name="txtworkflowsummary" type="xs:string">
<imixs:value><![CDATA[<itemvalue>partner.name</itemvalue> (<itemvalue>partner.id</itemvalue>)]]></imixs:value>
</imixs:item>
<imixs:item name="txtworkflowabstract" type="xs:string">
<imixs:value><![CDATA[<strong>Adresse:</strong><br />
<itemvalue>partner.name</itemvalue><br />
<itemvalue>partner.address</itemvalue><br />
<itemvalue>partner.zip</itemvalue> <itemvalue>partner.city</itemvalue><br />
<br />
<strong>BPID:</strong> <itemvalue>partner.id</itemvalue><br />
<strong>Kreditoren-Nr.:</strong> <itemvalue>cdtr.number</itemvalue><br />
<strong>Debitoren-Nr.:</strong> <itemvalue>dbtr.number</itemvalue><br />
]]></imixs:value>
</imixs:item>
<imixs:item name="keyupdateacl" type="xs:string">
<imixs:value><![CDATA[true]]></imixs:value>
</imixs:item>
<imixs:item name="namownershipnames" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="namaddreadaccess" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="namaddwriteaccess" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="keyaddwritefields" type="xs:string">
<imixs:value><![CDATA[process.team]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_M7b42w"/>
<bpmn2:incoming>sequenceFlow_avahXg</bpmn2:incoming>
<bpmn2:incoming>sequenceFlow_9atLFA</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_T5L9aQ</bpmn2:outgoing>
<bpmn2:incoming>sequenceFlow_b4BcVA</bpmn2:incoming>
<bpmn2:incoming>sequenceFlow_8O1EtQ</bpmn2:incoming>
<bpmn2:incoming>sequenceFlow_ShuXXA</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_03b60w</bpmn2:outgoing>
</bpmn2:task>
<bpmn2:task id="task_B8Pi7A" imixs:processid="1800" name="Locked">
<bpmn2:extensionElements>
<imixs:item name="txtworkflowsummary" type="xs:string">
<imixs:value><![CDATA[<itemvalue>partner.name</itemvalue> (<itemvalue>partner.id</itemvalue>)]]></imixs:value>
</imixs:item>
<imixs:item name="txtimageurl" type="xs:string">
<imixs:value><![CDATA[typcn-user|typcn-times,imixs-error]]></imixs:value>
</imixs:item>
<imixs:item name="txtworkflowabstract" type="xs:string">
<imixs:value><![CDATA[<strong>Adresse:</strong><br />
<itemvalue>partner.name</itemvalue><br />
<itemvalue>partner.address</itemvalue><br />
<itemvalue>partner.zip</itemvalue> <itemvalue>partner.city</itemvalue><br />
<br />
<strong>BPID:</strong> <itemvalue>partner.id</itemvalue><br />
<strong>Kreditoren-Nr.:</strong> <itemvalue>cdtr.number</itemvalue><br />
<strong>Debitoren-Nr.:</strong> <itemvalue>dbtr.number</itemvalue><br />
]]></imixs:value>
</imixs:item>
<imixs:item name="keyupdateacl" type="xs:string">
<imixs:value><![CDATA[true]]></imixs:value>
</imixs:item>
<imixs:item name="namownershipnames" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="namaddreadaccess" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="namaddwriteaccess" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="keyaddwritefields" type="xs:string">
<imixs:value><![CDATA[process.team]]></imixs:value>
</imixs:item>
<imixs:item name="txttype" type="xs:string">
<imixs:value><![CDATA[workitemarchive]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_0B9wKQ"/>
<bpmn2:incoming>sequenceFlow_1mXO8A</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_rwdWxw</bpmn2:outgoing>
<bpmn2:incoming>sequenceFlow_gC0I9w</bpmn2:incoming>
<bpmn2:incoming>sequenceFlow_kS2fMA</bpmn2:incoming>
</bpmn2:task>
<bpmn2:intermediateCatchEvent id="event_aSdkwg" imixs:activityid="100" name="[import]">
<bpmn2:extensionElements>
<imixs:item name="keypublicresult" type="xs:string">
<imixs:value><![CDATA[0]]></imixs:value>
</imixs:item>
<imixs:item name="rtfresultlog" type="xs:string">
<imixs:value><![CDATA[Business Partner aus Cargosoft importiert]]></imixs:value>
</imixs:item>
<imixs:item name="txtactivityresult" type="xs:string">
<imixs:value><![CDATA[<item name="process">Partnermanagement</item>]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_5409Kg"/>
<bpmn2:incoming>sequenceFlow_jAU3VA</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_8O1EtQ</bpmn2:outgoing>
</bpmn2:intermediateCatchEvent>
<bpmn2:sequenceFlow id="sequenceFlow_S9LVXg" sourceRef="task_ToMZaQ" targetRef="gateway_uK0c6g">
<bpmn2:documentation id="documentation_Idvd0Q"/>
</bpmn2:sequenceFlow>
<bpmn2:intermediateCatchEvent id="event_Tr0aug" imixs:activityid="30" name="Lock">
<bpmn2:extensionElements>
<imixs:item name="keypublicresult" type="xs:string">
<imixs:value><![CDATA[1]]></imixs:value>
</imixs:item>
<imixs:item name="txtactivityresult" type="xs:string">
<imixs:value><![CDATA[<item name="action">home</item>]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_2XhDKQ"/>
<bpmn2:outgoing>sequenceFlow_1mXO8A</bpmn2:outgoing>
<bpmn2:incoming>sequenceFlow_b0Bj3Q</bpmn2:incoming>
<bpmn2:incoming>sequenceFlow_neSsFQ</bpmn2:incoming>
</bpmn2:intermediateCatchEvent>
<bpmn2:sequenceFlow id="sequenceFlow_1mXO8A" sourceRef="event_Tr0aug" targetRef="task_B8Pi7A">
<bpmn2:documentation id="documentation_JL48mA"/>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="sequenceFlow_rwdWxw" sourceRef="task_B8Pi7A" targetRef="event_1clvlg">
<bpmn2:documentation id="documentation_8xdVSA"/>
</bpmn2:sequenceFlow>
<bpmn2:dataObject id="dataObject_UQIXAQ" name="Form">
<bpmn2:documentation id="documentation_e1in1w"><![CDATA[<imixs-form>
<imixs-subform label="Überblick">
<imixs-form-section columns="4" label="Ausgangsrechnungen">
<item name="analytic.invoices.count.all" type="custom" path="alexander/analyze_plain" label="Alle Rechnungen:" />
<item name="analytic.invoices.count.open" type="custom" path="alexander/analyze_plain" label="Offene Rechnungen:" />
<item name="analytic.invoices.count.due" type="custom" path="alexander/analyze_plain" label="Fällige Rechnungen:" />
<item name="analytic.invoices.count.dunning" type="custom" path="alexander/analyze_plain" label="Rechnungen in Mahnung:" />
</imixs-form-section>
<imixs-form-section label="" path="alexander/section_bp_invoices_out" readonly="true" />
<imixs-form-section columns="2" label="Zahlungsmoral">
<item name="analytic.payment.avg.due" type="custom" path="alexander/analyze_plain" label="Average term of credit:" />
<item name="analytic.payment.avg.days" type="custom" path="alexander/analyze_plain" label="Average payment duration:" />
</imixs-form-section>
<imixs-form-section columns="1" label="">
<item name="analytic.invoices.trend" type="custom" path="alexander/analyze_chart"
label="Trend der letzten 6 Monate:" />
</imixs-form-section>
<imixs-form-section label="Eingangsrechnungen" path="alexander/section_bp_invoices_in" readonly="true" />
</imixs-subform>
<imixs-subform label="Stammdaten">
<imixs-form-section columns="1" label="">
<item name="partner.name" type="text" span="6" readonly="true" label="Name:"/>
<item name="partner.id" type="text" span="6" readonly="true" label="Partner ID:"/>
</imixs-form-section>
<imixs-form-section columns="1" label="" readonly="true">
<item name="partner.address" type="text" span="6" required="false" label="Adresse:" />
<item name="partner.address.sub1" type="text" span="6" required="false" label="Zusatz" />
<item name="partner.zip" type="text" span="1" required="false" label="PLZ:" />
<item name="partner.city" type="text" span="5" required="false" label="Stadt:" />
<item name="partner.phone" type="text" readonly="false" span="6" label="Tel.:" />
<item name="partner.country" type="custom" path="country" span="6" readonly="false" label="Land:" />
<item name="partner.language" type="text" readonly="false" span="6" label="Sprache:" />
</imixs-form-section>
<imixs-form-section columns="1" label="Rechnungseingang ">
<item name="cdtr.number" type="text" span="2" readonly="true" label="Kreditoren Nr.:" />
<item name="cdtr.creditperiod" type="text" span="4" readonly="false" label="Zahlungsziel (Tage):" />
<item name="cdtr.email" type="textlist" span="6" readonly="false" label="E-Mail:" />
</imixs-form-section>
<imixs-form-section columns="1" label="Rechnungsausgang ">
<item name="dbtr.number" type="text" span="2" readonly="true" label="Debitoren Nr.:" />
<item name="dbtr.creditperiod" type="text" span="4" readonly="false" label="Zahlungsziel (Tage):" />
<item name="dbtr.email" type="textlist" span="6" readonly="false" label="E-Mail SOA/Mahnungen:" />
</imixs-form-section>
<imixs-form-section label="Banken" path="alexander/section_businesspartner_banklist" />
</imixs-subform>
</imixs-form>]]></bpmn2:documentation>
</bpmn2:dataObject>
<bpmn2:intermediateCatchEvent id="event_vwH0cA" imixs:activityid="10" name="Save">
<bpmn2:documentation id="documentation_WOUVTQ"/>
<bpmn2:outgoing>sequenceFlow_avahXg</bpmn2:outgoing>
</bpmn2:intermediateCatchEvent>
<bpmn2:sequenceFlow id="sequenceFlow_avahXg" sourceRef="event_vwH0cA" targetRef="task_Q80A1Q">
<bpmn2:documentation id="documentation_lhR6Lw"/>
</bpmn2:sequenceFlow>
<bpmn2:association id="association_uvV8uA" sourceRef="participant_oxb3dg" targetRef="task_Q80A1Q"/>
<bpmn2:association id="association_mpLQEQ" sourceRef="dataObject_UQIXAQ" targetRef="task_Q80A1Q">
<bpmn2:documentation id="documentation_oVnaJw"/>
</bpmn2:association>
<bpmn2:textAnnotation id="textAnnotation_LyGTCw" textFormat="">
<bpmn2:text id="text_M05BXg"><![CDATA[Neuanalge erfolgt über den Cargosoft CSV Import im BusinessPartnerImportService]]></bpmn2:text>
<bpmn2:documentation id="documentation_wdTBKA"/>
</bpmn2:textAnnotation>
<bpmn2:association id="association_83sZPw" sourceRef="participant_oxb3dg" targetRef="textAnnotation_LyGTCw">
<bpmn2:documentation id="documentation_FKcIcg"/>
</bpmn2:association>
<bpmn2:intermediateCatchEvent id="event_TuyuwQ" imixs:activityid="200" name="[update]">
<bpmn2:extensionElements>
<imixs:item name="keypublicresult" type="xs:string">
<imixs:value><![CDATA[0]]></imixs:value>
</imixs:item>
<imixs:item name="txtactivityresult" type="xs:string">
<imixs:value><![CDATA[Business Partner aktualisiert aus Cargosoft ]]></imixs:value>
</imixs:item>
<imixs:item name="rtfresultlog" type="xs:string">
<imixs:value><![CDATA[Business Partner aus Cargosoft aktualisiert]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_wy3OSA"/>
<bpmn2:outgoing>sequenceFlow_9atLFA</bpmn2:outgoing>
</bpmn2:intermediateCatchEvent>
<bpmn2:sequenceFlow id="sequenceFlow_9atLFA" sourceRef="event_TuyuwQ" targetRef="task_Q80A1Q">
<bpmn2:documentation id="documentation_LHM8vg"/>
</bpmn2:sequenceFlow>
<bpmn2:intermediateCatchEvent id="event_h0Kk5g" imixs:activityid="201" name="[update]">
<bpmn2:extensionElements>
<imixs:item name="keypublicresult" type="xs:string">
<imixs:value><![CDATA[0]]></imixs:value>
</imixs:item>
<imixs:item name="txtactivityresult" type="xs:string">
<imixs:value><![CDATA[Business Partner aktualisiert aus Cargosoft ]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_AnqGdA"/>
<bpmn2:outgoing>sequenceFlow_gC0I9w</bpmn2:outgoing>
</bpmn2:intermediateCatchEvent>
<bpmn2:sequenceFlow id="sequenceFlow_gC0I9w" sourceRef="event_h0Kk5g" targetRef="task_B8Pi7A">
<bpmn2:documentation id="documentation_gfdNnQ"/>
</bpmn2:sequenceFlow>
<bpmn2:task id="task_delO7Q" imixs:processid="1300" name="Fraud Detection">
<bpmn2:extensionElements>
<imixs:item name="txtimageurl" type="xs:string">
<imixs:value><![CDATA[typcn-user|typcn-tick,imixs-success]]></imixs:value>
</imixs:item>
<imixs:item name="txtworkflowsummary" type="xs:string">
<imixs:value><![CDATA[<itemvalue>partner.name</itemvalue> (<itemvalue>partner.id</itemvalue>)]]></imixs:value>
</imixs:item>
<imixs:item name="txtworkflowabstract" type="xs:string">
<imixs:value><![CDATA[<strong>Adresse:</strong><br />
<itemvalue>partner.name</itemvalue><br />
<itemvalue>partner.address</itemvalue><br />
<itemvalue>partner.zip</itemvalue> <itemvalue>partner.city</itemvalue><br />
<br />
<strong>BPID:</strong> <itemvalue>partner.id</itemvalue><br />
<strong>Kreditoren-Nr.:</strong> <itemvalue>cdtr.number</itemvalue><br />
<strong>Debitoren-Nr.:</strong> <itemvalue>dbtr.number</itemvalue><br />
]]></imixs:value>
</imixs:item>
<imixs:item name="keyupdateacl" type="xs:string">
<imixs:value><![CDATA[true]]></imixs:value>
</imixs:item>
<imixs:item name="keyownershipfields" type="xs:string">
<imixs:value><![CDATA[process.team]]></imixs:value>
</imixs:item>
<imixs:item name="namownershipnames" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="namaddreadaccess" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="namaddwriteaccess" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="keyaddwritefields" type="xs:string">
<imixs:value><![CDATA[process.team]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_1TuLXg"/>
<bpmn2:outgoing>sequenceFlow_NNbMhg</bpmn2:outgoing>
<bpmn2:incoming>sequenceFlow_avahXg</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_pvIENQ</bpmn2:outgoing>
<bpmn2:incoming>sequenceFlow_BWOtzA</bpmn2:incoming>
<bpmn2:incoming>sequenceFlow_ofWWuw</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_b0Bj3Q</bpmn2:outgoing>
<bpmn2:incoming>sequenceFlow_BHsRkQ</bpmn2:incoming>
</bpmn2:task>
<bpmn2:intermediateCatchEvent id="event_1OVorA" imixs:activityid="300" name="[iban/bic error]">
<bpmn2:extensionElements>
<imixs:item name="keypublicresult" type="xs:string">
<imixs:value><![CDATA[0]]></imixs:value>
</imixs:item>
<imixs:item name="rtfresultlog" type="xs:string">
<imixs:value><![CDATA[IBAN/BIC fraud detection]]></imixs:value>
</imixs:item>
<imixs:item name="txtactivityresult" type="xs:string">
<imixs:value><![CDATA[<item name="process">Partnermanagement</item>]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_J8iRLQ"/>
<bpmn2:outgoing>sequenceFlow_BHsRkQ</bpmn2:outgoing>
<bpmn2:incoming>sequenceFlow_5pT4Tw</bpmn2:incoming>
</bpmn2:intermediateCatchEvent>
<bpmn2:intermediateCatchEvent id="event_baWU4w" imixs:activityid="20" name="Update">
<bpmn2:extensionElements>
<imixs:item name="rtfresultlog" type="xs:string">
<imixs:value><![CDATA[Data updated]]></imixs:value>
</imixs:item>
<imixs:item name="keypublicresult" type="xs:string">
<imixs:value><![CDATA[1]]></imixs:value>
</imixs:item>
<imixs:item name="txtactivityresult" type="xs:string">
<imixs:value><![CDATA[<item name="action">home</item>]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_q730KA"/>
<bpmn2:incoming>sequenceFlow_pvIENQ</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_b4BcVA</bpmn2:outgoing>
</bpmn2:intermediateCatchEvent>
<bpmn2:sequenceFlow id="sequenceFlow_pvIENQ" sourceRef="task_delO7Q" targetRef="event_baWU4w">
<bpmn2:documentation id="documentation_BYJ08A"/>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="sequenceFlow_b4BcVA" sourceRef="event_baWU4w" targetRef="task_Q80A1Q">
<bpmn2:documentation id="documentation_5wWdHA"/>
</bpmn2:sequenceFlow>
<bpmn2:intermediateCatchEvent id="event_ofjVfg" imixs:activityid="200" name="[update]">
<bpmn2:extensionElements>
<imixs:item name="keypublicresult" type="xs:string">
<imixs:value><![CDATA[0]]></imixs:value>
</imixs:item>
<imixs:item name="rtfresultlog" type="xs:string">
<imixs:value><![CDATA[Business Partner aus Cargosoft aktualisiert]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_A42bcQ"/>
<bpmn2:outgoing>sequenceFlow_BWOtzA</bpmn2:outgoing>
</bpmn2:intermediateCatchEvent>
<bpmn2:sequenceFlow id="sequenceFlow_BWOtzA" sourceRef="event_ofjVfg" targetRef="task_delO7Q">
<bpmn2:documentation id="documentation_QW3slw"/>
</bpmn2:sequenceFlow>
<bpmn2:intermediateCatchEvent id="event_i7yQVw" imixs:activityid="10" name="Save">
<bpmn2:extensionElements>
<imixs:item name="txtactivityresult" type="xs:string">
<imixs:value><![CDATA[<item name="process">Partnermanagement</item>]]></imixs:value>
</imixs:item>
<imixs:item name="keypublicresult" type="xs:string">
<imixs:value><![CDATA[1]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_Il4Shw"/>
<bpmn2:outgoing>sequenceFlow_ofWWuw</bpmn2:outgoing>
</bpmn2:intermediateCatchEvent>
<bpmn2:sequenceFlow id="sequenceFlow_ofWWuw" sourceRef="event_i7yQVw" targetRef="task_delO7Q">
<bpmn2:documentation id="documentation_fXWIBw"/>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="sequenceFlow_b0Bj3Q" sourceRef="task_delO7Q" targetRef="event_Tr0aug">
<bpmn2:documentation id="documentation_pVJeTQ"/>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="sequenceFlow_T5L9aQ" sourceRef="task_Q80A1Q" targetRef="event_VM0fEQ">
<bpmn2:documentation id="documentation_0GJKgQ"/>
</bpmn2:sequenceFlow>
<bpmn2:exclusiveGateway gatewayDirection="Diverging" id="gateway_uK0c6g" name="">
<bpmn2:documentation id="documentation_V9jDYA"/>
<bpmn2:incoming>sequenceFlow_S9LVXg</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_jAU3VA</bpmn2:outgoing>
<bpmn2:outgoing>sequenceFlow_8BTEtw</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="sequenceFlow_jAU3VA" sourceRef="gateway_uK0c6g" targetRef="event_aSdkwg">
<bpmn2:documentation id="documentation_ZNk0Bw"/>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="sequenceFlow_BHsRkQ" sourceRef="event_1OVorA" targetRef="task_delO7Q">
<bpmn2:documentation id="documentation_SvoFWw"/>
</bpmn2:sequenceFlow>
<bpmn2:association id="association_WgLbbg" sourceRef="dataObject_UQIXAQ" targetRef="task_delO7Q">
<bpmn2:documentation id="documentation_YhKaqg"/>
</bpmn2:association>
<bpmn2:sequenceFlow id="sequenceFlow_8O1EtQ" sourceRef="event_aSdkwg" targetRef="task_Q80A1Q">
<bpmn2:documentation id="documentation_VTvROw"/>
</bpmn2:sequenceFlow>
<bpmn2:intermediateCatchEvent id="event_6250fg" imixs:activityid="10" name="Save">
<bpmn2:documentation id="documentation_fcuXRg"/>
<bpmn2:outgoing>sequenceFlow_kS2fMA</bpmn2:outgoing>
</bpmn2:intermediateCatchEvent>
<bpmn2:sequenceFlow id="sequenceFlow_kS2fMA" sourceRef="event_6250fg" targetRef="task_B8Pi7A">
<bpmn2:documentation id="documentation_t0kr1g"/>
</bpmn2:sequenceFlow>
<bpmn2:intermediateCatchEvent id="event_VM0fEQ" imixs:activityid="900" name="[Stats]">
<bpmn2:extensionElements>
<imixs:item name="txtactivityresult" type="xs:string">
<imixs:value><![CDATA[<item name="process">Partnermanagement</item>]]></imixs:value>
</imixs:item>
<imixs:item name="keypublicresult" type="xs:string">
<imixs:value><![CDATA[0]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_ZmJD9A"/>
<bpmn2:incoming>sequenceFlow_T5L9aQ</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_MPlPww</bpmn2:outgoing>
<bpmn2:timerEventDefinition id="timerEventDefinition_faqzJg"/>
<bpmn2:incoming>sequenceFlow_rNZ3tw</bpmn2:incoming>
</bpmn2:intermediateCatchEvent>
<bpmn2:exclusiveGateway default="sequenceFlow_aH0vCQ" gatewayDirection="Diverging" id="gateway_s0J53g" name="">
<bpmn2:documentation id="documentation_2Qn8Ng"/>
<bpmn2:incoming>sequenceFlow_MPlPww</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_aH0vCQ</bpmn2:outgoing>
<bpmn2:outgoing>sequenceFlow_ShuXXA</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:task id="task_NohgrQ" imixs:processid="1700" name="Inactiv">
<bpmn2:extensionElements>
<imixs:item name="txtimageurl" type="xs:string">
<imixs:value><![CDATA[typcn-user|typcn-tick,imixs-success]]></imixs:value>
</imixs:item>
<imixs:item name="txtworkflowsummary" type="xs:string">
<imixs:value><![CDATA[<itemvalue>partner.name</itemvalue> (<itemvalue>partner.id</itemvalue>)]]></imixs:value>
</imixs:item>
<imixs:item name="txtworkflowabstract" type="xs:string">
<imixs:value><![CDATA[<strong>Adresse:</strong><br />
<itemvalue>partner.name</itemvalue><br />
<itemvalue>partner.address</itemvalue><br />
<itemvalue>partner.zip</itemvalue> <itemvalue>partner.city</itemvalue><br />
<br />
<strong>BPID:</strong> <itemvalue>partner.id</itemvalue><br />
<strong>Kreditoren-Nr.:</strong> <itemvalue>cdtr.number</itemvalue><br />
<strong>Debitoren-Nr.:</strong> <itemvalue>dbtr.number</itemvalue><br />
]]></imixs:value>
</imixs:item>
<imixs:item name="keyupdateacl" type="xs:string">
<imixs:value><![CDATA[true]]></imixs:value>
</imixs:item>
<imixs:item name="namownershipnames" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="namaddreadaccess" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="namaddwriteaccess" type="xs:string">
<imixs:value/>
</imixs:item>
<imixs:item name="keyaddwritefields" type="xs:string">
<imixs:value><![CDATA[process.team]]></imixs:value>
</imixs:item>
<imixs:item name="txttype" type="xs:string">
<imixs:value><![CDATA[workitemarchive]]></imixs:value>
</imixs:item>
</bpmn2:extensionElements>
<bpmn2:documentation id="documentation_RI2Ing"/>
<bpmn2:incoming>sequenceFlow_9atLFA</bpmn2:incoming>
<bpmn2:incoming>sequenceFlow_8O1EtQ</bpmn2:incoming>
<bpmn2:incoming>sequenceFlow_aH0vCQ</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_neSsFQ</bpmn2:outgoing>
<bpmn2:outgoing>sequenceFlow_zd6RWA</bpmn2:outgoing>
<bpmn2:outgoing>sequenceFlow_rNZ3tw</bpmn2:outgoing>
</bpmn2:task>
<bpmn2:sequenceFlow id="sequenceFlow_MPlPww" sourceRef="event_VM0fEQ" targetRef="gateway_s0J53g">
<bpmn2:documentation id="documentation_saZUSw"/>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="sequenceFlow_aH0vCQ" sourceRef="gateway_s0J53g" targetRef="task_NohgrQ">
<bpmn2:documentation id="documentation_0fUXrw"/>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="sequenceFlow_ShuXXA" sourceRef="gateway_s0J53g" targetRef="task_Q80A1Q">
<bpmn2:documentation id="documentation_mhSkeg"/>
<bpmn2:conditionExpression id="formalExpression_oEZj1g" xsi:type="bpmn2:tFormalExpression"><![CDATA[true;]]></bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="sequenceFlow_neSsFQ" sourceRef="task_NohgrQ" targetRef="event_Tr0aug">
<bpmn2:documentation id="documentation_X3IiUg"/>
</bpmn2:sequenceFlow>
<bpmn2:intermediateCatchEvent id="event_AMs05g" name="[IBAN-ERROR]">
<bpmn2:documentation id="documentation_CKfjew"/>
<bpmn2:linkEventDefinition id="linkEventDefinition_QoDe7w"/>
<bpmn2:outgoing>sequenceFlow_5pT4Tw</bpmn2:outgoing>
</bpmn2:intermediateCatchEvent>
<bpmn2:intermediateThrowEvent id="event_feLZFQ" name="[IBAN-ERROR]">
<bpmn2:documentation id="documentation_ns8CKQ"/>
<bpmn2:linkEventDefinition id="linkEventDefinition_ici5wA"/>
<bpmn2:incoming>sequenceFlow_8BTEtw</bpmn2:incoming>
</bpmn2:intermediateThrowEvent>
<bpmn2:sequenceFlow id="sequenceFlow_5pT4Tw" sourceRef="event_AMs05g" targetRef="event_1OVorA">
<bpmn2:documentation id="documentation_mQfndA"/>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="sequenceFlow_8BTEtw" sourceRef="gateway_uK0c6g" targetRef="event_feLZFQ">
<bpmn2:documentation id="documentation_aB5qHw"/>
</bpmn2:sequenceFlow>
<bpmn2:intermediateThrowEvent id="event_GTXFMw" name="[IBAN-ERROR]">
<bpmn2:documentation id="documentation_a8NiKA"/>
<bpmn2:linkEventDefinition id="linkEventDefinition_YUfA0A"/>
<bpmn2:incoming>sequenceFlow_03b60w</bpmn2:incoming>
</bpmn2:intermediateThrowEvent>
<bpmn2:sequenceFlow id="sequenceFlow_03b60w" sourceRef="task_Q80A1Q" targetRef="event_GTXFMw">
<bpmn2:documentation id="documentation_i02Wsw"/>
</bpmn2:sequenceFlow>
<bpmn2:intermediateThrowEvent id="event_u8OBFg" name="[IBAN-ERROR]">
<bpmn2:documentation id="documentation_0rGDzg"/>
<bpmn2:linkEventDefinition id="linkEventDefinition_8g6AoA"/>
<bpmn2:incoming>sequenceFlow_zd6RWA</bpmn2:incoming>
</bpmn2:intermediateThrowEvent>
<bpmn2:sequenceFlow id="sequenceFlow_zd6RWA" sourceRef="task_NohgrQ" targetRef="event_u8OBFg">
<bpmn2:documentation id="documentation_ez15rg"/>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="sequenceFlow_rNZ3tw" sourceRef="task_NohgrQ" targetRef="event_VM0fEQ">
<bpmn2:documentation id="documentation_AHyEzA"/>
</bpmn2:sequenceFlow>
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1" name="OpenBPMN Diagram">
<bpmndi:BPMNPlane bpmnElement="collaboration_1" id="BPMNPlane_1">
<bpmndi:BPMNShape bpmnElement="event_WmkBvw" id="BPMNShape_heefeQ">
<dc:Bounds height="36.0" width="36.0" x="57.0" y="277.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_6qfN6w">
<dc:Bounds height="20.0" width="100.0" x="25.0" y="316.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="event_1clvlg" id="BPMNShape_85k4rg">
<dc:Bounds height="36.0" width="36.0" x="1657.0" y="277.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_F7t2Ng">
<dc:Bounds height="20.0" width="100.0" x="1625.0" y="259.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="task_ToMZaQ" id="BPMNShape_CUpR6w">
<dc:Bounds height="50.0" width="110.0" x="170.0" y="270.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="participant_oxb3dg" id="BPMNShape_mqlDUA">
<dc:Bounds height="820.0" width="1820.0" x="-30.0" y="40.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="lane_Zxa2TA" id="BPMNShape_Lane_1cDN5A">
<dc:Bounds height="820.0" width="1790.0" x="0.0" y="40.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_25otUg" id="BPMNEdge_NHnc3g" sourceElement="BPMNShape_heefeQ" targetElement="BPMNShape_CUpR6w">
<di:waypoint x="93.0" y="295.0"/>
<di:waypoint x="170.0" y="295.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="task_Q80A1Q" id="BPMNShape_7JA0nw">
<dc:Bounds height="50.0" width="110.0" x="660.0" y="270.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="task_B8Pi7A" id="BPMNShape_ghujeA">
<dc:Bounds height="50.0" width="110.0" x="1360.0" y="270.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="event_aSdkwg" id="BPMNShape_uesqgg">
<dc:Bounds height="36.0" width="36.0" x="437.0" y="277.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_AxKRKQ">
<dc:Bounds height="20.0" width="100.0" x="405.0" y="316.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_S9LVXg" id="BPMNEdge_xoCSiA" sourceElement="BPMNShape_CUpR6w" targetElement="BPMNShape_6IZk7Q">
<di:waypoint x="280.0" y="295.0"/>
<di:waypoint x="340.0" y="295.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="event_Tr0aug" id="BPMNShape_NEaJjA">
<dc:Bounds height="36.0" width="36.0" x="1227.0" y="277.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_GkrCTA">
<dc:Bounds height="20.0" width="100.0" x="1195.0" y="316.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_1mXO8A" id="BPMNEdge_7CorJA" sourceElement="BPMNShape_NEaJjA" targetElement="BPMNShape_ghujeA">
<di:waypoint x="1263.0" y="295.0"/>
<di:waypoint x="1360.0" y="295.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_rwdWxw" id="BPMNEdge_t88dUw" sourceElement="BPMNShape_ghujeA" targetElement="BPMNShape_85k4rg">
<di:waypoint x="1470.0" y="295.0"/>
<di:waypoint x="1657.0" y="295.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="dataObject_UQIXAQ" id="BPMNShape_Mnop9Q">
<dc:Bounds height="50.0" width="35.0" x="560.0" y="380.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_h0UuFw">
<dc:Bounds height="20.0" width="100.0" x="527.5" y="435.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="event_vwH0cA" id="BPMNShape_OXGg8Q">
<dc:Bounds height="36.0" width="36.0" x="747.0" y="177.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_KaHfJQ">
<dc:Bounds height="20.0" width="100.0" x="715.0" y="216.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_avahXg" id="BPMNEdge_odMtvQ" sourceElement="BPMNShape_OXGg8Q" targetElement="BPMNShape_7JA0nw">
<di:waypoint x="747.0" y="196.0"/>
<di:waypoint x="725.0" y="196.0"/>
<di:waypoint x="725.0" y="270.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="association_uvV8uA" id="BPMNEdge_3Mtxfw" sourceElement="BPMNShape_mqlDUA" targetElement="BPMNShape_7JA0nw"/>
<bpmndi:BPMNEdge bpmnElement="association_mpLQEQ" id="BPMNEdge_qkEiPA" sourceElement="BPMNShape_Mnop9Q" targetElement="BPMNShape_7JA0nw">
<di:waypoint x="588.0" y="380.0"/>
<di:waypoint x="588.0" y="357.0"/>
<di:waypoint x="680.0" y="357.0"/>
<di:waypoint x="680.0" y="320.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="textAnnotation_LyGTCw" id="BPMNShape_SbxnGw">
<dc:Bounds height="85.0" width="228.0" x="80.0" y="80.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="association_83sZPw" id="BPMNEdge_p0XWxg" sourceElement="BPMNShape_mqlDUA" targetElement="BPMNShape_SbxnGw"/>
<bpmndi:BPMNShape bpmnElement="event_TuyuwQ" id="BPMNShape_fTy7mg">
<dc:Bounds height="36.0" width="36.0" x="617.0" y="177.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_033n1A">
<dc:Bounds height="20.0" width="100.0" x="585.0" y="216.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_9atLFA" id="BPMNEdge_BC4pwQ" sourceElement="BPMNShape_fTy7mg" targetElement="BPMNShape_7JA0nw">
<di:waypoint x="653.0" y="196.0"/>
<di:waypoint x="700.0" y="196.0"/>
<di:waypoint x="700.0" y="270.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="event_h0Kk5g" id="BPMNShape_T4k09g">
<dc:Bounds height="36.0" width="36.0" x="1327.0" y="177.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_lX0KlA">
<dc:Bounds height="20.0" width="100.0" x="1295.0" y="216.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_gC0I9w" id="BPMNEdge_0nCVvg" sourceElement="BPMNShape_T4k09g" targetElement="BPMNShape_ghujeA">
<di:waypoint x="1360.0" y="185.0"/>
<di:waypoint x="1390.0" y="185.0"/>
<di:waypoint x="1390.0" y="270.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="task_delO7Q" id="BPMNShape_rEOMWA">
<dc:Bounds height="50.0" width="110.0" x="660.0" y="510.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="event_1OVorA" id="BPMNShape_mG4csA">
<dc:Bounds height="36.0" width="36.0" x="557.0" y="517.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_djIb8w">
<dc:Bounds height="20.0" width="100.0" x="528.0" y="559.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="event_baWU4w" id="BPMNShape_755HGg">
<dc:Bounds height="36.0" width="36.0" x="697.0" y="407.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_oN6XKQ">
<dc:Bounds height="20.0" width="100.0" x="665.0" y="446.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_pvIENQ" id="BPMNEdge_wXz5yA" sourceElement="BPMNShape_rEOMWA" targetElement="BPMNShape_755HGg">
<di:waypoint x="715.0" y="510.0"/>
<di:waypoint x="715.0" y="443.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_b4BcVA" id="BPMNEdge_1gQ5Nw" sourceElement="BPMNShape_755HGg" targetElement="BPMNShape_7JA0nw">
<di:waypoint x="715.0" y="407.0"/>
<di:waypoint x="715.0" y="320.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="event_ofjVfg" id="BPMNShape_hDVKtg">
<dc:Bounds height="36.0" width="36.0" x="617.0" y="607.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_HpXNFQ">
<dc:Bounds height="20.0" width="100.0" x="585.0" y="646.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_BWOtzA" id="BPMNEdge_KW5PhQ" sourceElement="BPMNShape_hDVKtg" targetElement="BPMNShape_rEOMWA">
<di:waypoint x="651.0" y="616.0"/>
<di:waypoint x="685.0" y="616.0"/>
<di:waypoint x="685.0" y="560.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="event_i7yQVw" id="BPMNShape_KxnRjA">
<dc:Bounds height="36.0" width="36.0" x="747.0" y="607.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_sJqwmA">
<dc:Bounds height="20.0" width="100.0" x="715.0" y="646.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_ofWWuw" id="BPMNEdge_zlNOzQ" sourceElement="BPMNShape_KxnRjA" targetElement="BPMNShape_rEOMWA">
<di:waypoint x="749.0" y="616.0"/>
<di:waypoint x="735.0" y="616.0"/>
<di:waypoint x="735.0" y="560.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_b0Bj3Q" id="BPMNEdge_CDn41A" sourceElement="BPMNShape_rEOMWA" targetElement="BPMNShape_NEaJjA">
<di:waypoint x="770.0" y="540.0"/>
<di:waypoint x="1240.0" y="540.0"/>
<di:waypoint x="1240.0" y="312.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_T5L9aQ" id="BPMNEdge_dAQH1Q" sourceElement="BPMNShape_7JA0nw" targetElement="BPMNShape_FKXJtw">
<di:waypoint x="770.0" y="295.0"/>
<di:waypoint x="837.0" y="295.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="gateway_uK0c6g" id="BPMNShape_6IZk7Q">
<dc:Bounds height="50.0" width="50.0" x="340.0" y="270.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_JvBA0w">
<dc:Bounds height="20.0" width="100.0" x="315.0" y="323.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_jAU3VA" id="BPMNEdge_2qLI2g" sourceElement="BPMNShape_6IZk7Q" targetElement="BPMNShape_uesqgg">
<di:waypoint x="390.0" y="295.0"/>
<di:waypoint x="437.0" y="295.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_BHsRkQ" id="BPMNEdge_1LX47g" sourceElement="BPMNShape_mG4csA" targetElement="BPMNShape_rEOMWA">
<di:waypoint x="593.0" y="535.0"/>
<di:waypoint x="660.0" y="535.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="association_WgLbbg" id="BPMNEdge_Btz9CA" sourceElement="BPMNShape_Mnop9Q" targetElement="BPMNShape_rEOMWA">
<di:waypoint x="575.0" y="430.0"/>
<di:waypoint x="575.0" y="471.0"/>
<di:waypoint x="675.0" y="471.0"/>
<di:waypoint x="675.0" y="510.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_8O1EtQ" id="BPMNEdge_JxSLfA" sourceElement="BPMNShape_uesqgg" targetElement="BPMNShape_7JA0nw">
<di:waypoint x="473.0" y="295.0"/>
<di:waypoint x="660.0" y="295.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="event_6250fg" id="BPMNShape_v5Xzvg">
<dc:Bounds height="36.0" width="36.0" x="1457.0" y="177.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_IBnUDA">
<dc:Bounds height="20.0" width="100.0" x="1425.0" y="216.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_kS2fMA" id="BPMNEdge_i80XvQ" sourceElement="BPMNShape_v5Xzvg" targetElement="BPMNShape_ghujeA">
<di:waypoint x="1458.0" y="190.0"/>
<di:waypoint x="1435.0" y="190.0"/>
<di:waypoint x="1435.0" y="270.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="event_VM0fEQ" id="BPMNShape_FKXJtw">
<dc:Bounds height="36.0" width="36.0" x="837.0" y="277.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_CAiT2Q">
<dc:Bounds height="20.0" width="100.0" x="805.0" y="316.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="gateway_s0J53g" id="BPMNShape_gklxXQ">
<dc:Bounds height="50.0" width="50.0" x="930.0" y="270.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_I4jC0g">
<dc:Bounds height="20.0" width="100.0" x="905.0" y="323.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="task_NohgrQ" id="BPMNShape_VljsIw">
<dc:Bounds height="50.0" width="110.0" x="1060.0" y="270.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_MPlPww" id="BPMNEdge_rt2H3g" sourceElement="BPMNShape_FKXJtw" targetElement="BPMNShape_gklxXQ">
<di:waypoint x="873.0" y="295.0"/>
<di:waypoint x="930.0" y="295.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_aH0vCQ" id="BPMNEdge_YMnxQQ" sourceElement="BPMNShape_gklxXQ" targetElement="BPMNShape_VljsIw">
<di:waypoint x="980.0" y="295.0"/>
<di:waypoint x="1060.0" y="295.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_ShuXXA" id="BPMNEdge_DY4O8Q" sourceElement="BPMNShape_gklxXQ" targetElement="BPMNShape_7JA0nw">
<di:waypoint x="956.0" y="319.0"/>
<di:waypoint x="956.0" y="364.0"/>
<di:waypoint x="746.0" y="364.0"/>
<di:waypoint x="746.0" y="320.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_neSsFQ" id="BPMNEdge_b7C0wQ" sourceElement="BPMNShape_VljsIw" targetElement="BPMNShape_NEaJjA">
<di:waypoint x="1170.0" y="295.0"/>
<di:waypoint x="1227.0" y="295.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="event_AMs05g" id="BPMNShape_rS0hsw">
<dc:Bounds height="36.0" width="36.0" x="477.0" y="517.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_XcN2Og">
<dc:Bounds height="20.0" width="100.0" x="445.0" y="556.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="event_feLZFQ" id="BPMNShape_uklBfA">
<dc:Bounds height="36.0" width="36.0" x="347.0" y="367.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_OengAQ">
<dc:Bounds height="20.0" width="100.0" x="315.0" y="406.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_5pT4Tw" id="BPMNEdge_LZMXxg" sourceElement="BPMNShape_rS0hsw" targetElement="BPMNShape_mG4csA">
<di:waypoint x="513.0" y="535.0"/>
<di:waypoint x="557.0" y="535.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_8BTEtw" id="BPMNEdge_Os7FVw" sourceElement="BPMNShape_6IZk7Q" targetElement="BPMNShape_uklBfA">
<di:waypoint x="365.0" y="320.0"/>
<di:waypoint x="365.0" y="367.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="event_GTXFMw" id="BPMNShape_5I6iNA">
<dc:Bounds height="36.0" width="36.0" x="847.0" y="177.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_80daBg">
<dc:Bounds height="20.0" width="100.0" x="815.0" y="216.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_03b60w" id="BPMNEdge_ehQBOQ" sourceElement="BPMNShape_7JA0nw" targetElement="BPMNShape_5I6iNA">
<di:waypoint x="770.0" y="294.0"/>
<di:waypoint x="809.0" y="294.0"/>
<di:waypoint x="809.0" y="195.0"/>
<di:waypoint x="847.0" y="195.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="event_u8OBFg" id="BPMNShape_Kp730Q">
<dc:Bounds height="36.0" width="36.0" x="1137.0" y="177.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_iUQ02A">
<dc:Bounds height="20.0" width="100.0" x="1105.0" y="216.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_zd6RWA" id="BPMNEdge_6qKC4A" sourceElement="BPMNShape_VljsIw" targetElement="BPMNShape_Kp730Q">
<di:waypoint x="1114.0" y="270.0"/>
<di:waypoint x="1114.0" y="195.0"/>
<di:waypoint x="1137.0" y="195.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_rNZ3tw" id="BPMNEdge_zrajzA" sourceElement="BPMNShape_VljsIw" targetElement="BPMNShape_FKXJtw">
<di:waypoint x="1080.0" y="270.0"/>
<di:waypoint x="1080.0" y="237.0"/>
<di:waypoint x="855.0" y="237.0"/>
<di:waypoint x="855.0" y="277.0"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>