office-alexander-logistics/office-alexander-logistics-app/src/main/java/com/alexanderlogistics/api/CargosoftMigrationRestService.java

698 lines
26 KiB
Java

/*******************************************************************************
* Imixs Workflow
* Copyright (C) 2001, 2011 Imixs Software Solutions GmbH,
* http://www.imixs.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You can receive a copy of the GNU General Public
* License at http://www.gnu.org/licenses/gpl.html
*
* Project:
* http://www.imixs.org
* http://java.net/projects/imixs-workflow
*
* Contributors:
* Imixs Software Solutions GmbH - initial API and implementation
* Ralph Soika - Software Developer
*******************************************************************************/
package com.alexanderlogistics.api;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.imixs.marty.team.TeamService;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.DocumentService;
import org.imixs.workflow.engine.WorkflowService;
import org.imixs.workflow.engine.index.UpdateService;
import org.imixs.workflow.exceptions.AccessDeniedException;
import org.imixs.workflow.exceptions.ModelException;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.ProcessingErrorException;
import org.imixs.workflow.exceptions.QueryException;
import org.imixs.workflow.xml.XMLDataCollectionAdapter;
import com.alexanderlogistics.BusinessPartnerService;
import com.alexanderlogistics.InvoiceUtil;
import com.alexanderlogistics.mahnlauf.MahnlaufService;
import com.alexanderlogistics.xml.BusinessPartnerImportService;
import jakarta.ejb.TransactionAttribute;
import jakarta.ejb.TransactionAttributeType;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.xml.bind.JAXBException;
/**
** Dieser Service korrigiert falsch importierte Cargosoft daten.
* Ausgangsrechnungen die ein falsches Jahr haben.
*
* Oder DAten die über die XML Schnittstelle falsch reinkamen...
*
* @author rsoika
* @version 1.1
*/
@ApplicationScoped
@Produces({ MediaType.TEXT_HTML, MediaType.APPLICATION_XHTML_XML, MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON,
MediaType.TEXT_XML })
@Path("/cargosoft")
public class CargosoftMigrationRestService implements Serializable {
private static final long serialVersionUID = 1L;
String log = "";
int errors = 0;
int count = 0;
boolean isRunning = false;
@Inject
DocumentService documentService;
@Inject
WorkflowService workflowService;
@Inject
MahnlaufService mahnlaufService;
@Inject
UpdateService updateService;
@Inject
TeamService teamService;
@Inject
BusinessPartnerService businessPartnerService;
@Inject
BusinessPartnerImportService businessPartnerImportService;
private static Logger logger = Logger.getLogger(CargosoftMigrationRestService.class.getName());
public CargosoftMigrationRestService() {
super();
}
/**
* Dieser agent speichert einfach alle Cargosoft Krediotren/Debitoren Daten ,
* was dann zur automatischen Neuanlage bzw. aktualisierung des businesspartner
* workflows führt.
*
*
* curl -H "Cookie:
* JSESSIONID=ShAegfNiXmqLFLSFwcUZ7hrLLQDKZGKiu4oj_xeX.imixs-office-workflow-8586d7d58d-h6k4q"
* https://alexander-logistics-dwc.office-workflow.de/api/cargosoft/bp-sync?maxcount=1000
*
* @return
* @throws QueryException
* @throws AccessDeniedException
* @throws ProcessingErrorException
* @throws PluginException
* @throws ModelException
*/
// @GET
// @Path("/bp-sync")
// @Produces({ MediaType.TEXT_PLAIN })
public String syncBusinessPartner(@QueryParam("maxcount") int maxcount)
throws QueryException, AccessDeniedException, ProcessingErrorException, PluginException, ModelException {
StringBuffer messageBuffer = new StringBuffer();
if (maxcount <= 0) {
maxcount = 100;
}
String query = "(type:cargosoftkreditor)";
int syncs = 0;
long l = System.currentTimeMillis();
int batchSize = 100;
int totalObjects = 0;
log("├── sync business partner objects....", messageBuffer);
if (isRunning) {
log("├── sync process already running!", messageBuffer);
return messageBuffer.toString();
}
isRunning = true;
int totalCount = documentService.count(query);
log("│   ├── found " + totalCount + " cargosoft entries", 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 (businessPartnerImportService.syncCargosoftBusinessPartner(cargosoftItemCol) == true) {
updateService.updateIndex();
syncs++;
if (syncs >= maxcount)
break;
}
}
// Fortschritt loggen
log("│ ├── Processed page " + (pageIndex + 1) + " of " + totalPages + " (" + totalObjects
+ " objects read, " + syncs + " total syncs)", messageBuffer);
// Optional: Kurze Pause nach jedem 5. Batch
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (syncs >= maxcount)
break;
}
long duration = System.currentTimeMillis() - l;
double objectsPerSecond = totalObjects / (duration / 1000.0);
log("├── Successfully " + syncs + " business partner objects synced in " + duration + "ms ("
+ String.format("%.1f", objectsPerSecond) + " objects/sec)", messageBuffer);
isRunning = false;
return messageBuffer.toString();
}
/**
* Migriert die Rechnungen mit den neuen cdtr. Nummern
*
*
*/
// @GET
// @Path("/cargosoft-renew_cdtr_number")
// @Produces({ MediaType.TEXT_PLAIN })
public String migrationAdresswandlungOpenInvoices(@QueryParam("maxcount") int maxcount,
@QueryParam("type") String type)
throws QueryException, AccessDeniedException, ProcessingErrorException,
PluginException, ModelException {
StringBuffer messageBuffer = new StringBuffer();
if (maxcount <= 0) {
maxcount = 10;
}
if (type == null || type.isEmpty()) {
type = "workitem";
}
if (isRunning) {
log("├── sync process already running!", messageBuffer);
return messageBuffer.toString();
}
isRunning = true;
log("├── read cargosoft migration file....", messageBuffer);
Map<String, String> mapping = readAdressMapping();
log("├── found " + mapping.size() + " mappings", messageBuffer);
String query = "(type:" + type + ") AND ($modelversion:rechnungseingang*)";
// int syncs = 0;
long l = System.currentTimeMillis();
int batchSize = 100;
int totalUpdates = 0;
log("├── migration cargosoft addresses....", messageBuffer);
log("│ ├── query=" + query, messageBuffer);
int totalCount = documentService.count(query);
log("│ ├── found " + totalCount + " 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++) {
log("│ ├── read page " + pageIndex, messageBuffer);
List<ItemCollection> invoiceList = documentService.find(query, batchSize,
pageIndex);
long l1 = System.currentTimeMillis();
log("│ ├── ...verifying " + invoiceList.size() + " invoices...",
messageBuffer);
int updates = migratateInvioceBPAddress(invoiceList, mapping);
totalUpdates = totalUpdates + updates;
log("│ ├── " + updates + " invoices migrated in " +
(System.currentTimeMillis() - l1) + "ms ", messageBuffer);
// break;
if (totalUpdates >= maxcount) {
break;
}
}
long duration = System.currentTimeMillis() - l;
double objectsPerSecond = totalUpdates / (duration / 1000.0);
log("├── Successfully " + totalUpdates + " invoices migrated in " + duration
+ "ms ("
+ String.format("%.1f", objectsPerSecond) + " objects/sec)", messageBuffer);
isRunning = false;
return messageBuffer.toString();
}
@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
private int migratateInvioceBPAddress(List<ItemCollection> invoices,
Map<String, String> map) {
int count = 0;
for (ItemCollection invoice : invoices) {
String cdtrNumber = invoice.getItemValueString("cdtr.number");
if (cdtrNumber.isBlank()) {
logger.warning(" ..Invoice " + invoice.getUniqueID() + " has no cdtr.number!");
continue;
}
// logger.info("==> check cdtr.number: " + cdtrNumber);
// K überspringen
String newNumber = map.get(cdtrNumber.substring(1));
if (newNumber != null) {
// migrate number
invoice.setItemValue("cdtr.number.old", cdtrNumber);
invoice.setItemValue("cdtr.number", "K" + newNumber);
logger.info("│ ├── replace " + cdtrNumber + " -> K" + newNumber + " : " +
invoice.getUniqueID());
documentService.save(invoice);
// logger.info("-- kein update! disabled save!!");
count++;
}
}
return count;
}
/**
* Migriert die Rechnungen mit den neuen cdtr. Nummern
*
*
*/
// @GET
// @Path("/fix-separun")
// @Produces({ MediaType.TEXT_PLAIN })
public String fixSepaRun(@QueryParam("maxcount") int maxcount)
throws QueryException, AccessDeniedException, ProcessingErrorException,
PluginException, ModelException {
StringBuffer messageBuffer = new StringBuffer();
int totalUpdates = 0;
if (maxcount <= 0) {
maxcount = 10;
if (isRunning) {
log("├── sync process already running!", messageBuffer);
return messageBuffer.toString();
}
isRunning = true;
String query = "(type:workitem) AND ($modelversion:rechnungseingang*) AND ($taskid:5500)";
// int syncs = 0;
long l = System.currentTimeMillis();
log("├── migration sepa run....", messageBuffer);
log("│ ├── query=" + query, messageBuffer);
int totalCount = documentService.count(query);
log("│ ├── found " + totalCount + " invoices", messageBuffer);
List<ItemCollection> invoiceList = documentService.find(query, 999, 0);
long l1 = System.currentTimeMillis();
log("│ ├── ...verifying " + invoiceList.size() + " invoices...",
messageBuffer);
for (ItemCollection invoice : invoiceList) {
String cdtrNumber = invoice.getItemValueString("cdtr.number");
String cdtrNumberOld = invoice.getItemValueString("cdtr.number.old");
String bpPartnerID = invoice.getItemValueString("partner.id");
if (bpPartnerID.isEmpty()) {
continue;
}
if (cdtrNumberOld.isEmpty()) {
continue;
}
// lookup bp ....
ItemCollection bpPartner = businessPartnerService.getBusinessPartnerByID(InvoiceUtil.getBPID(invoice));
String fixPartnerName = bpPartner.getItemValueString("partner.name");
String fixPartnerId = bpPartner.getItemValueString("partner.id");
if (!bpPartnerID.equals(fixPartnerId)) {
logger.info("Problem found with invoice: " + invoice.getUniqueID() + " wrong partnerID="
+ bpPartnerID + " correct= " + fixPartnerId);
logger.info(" correct name= " + fixPartnerName);
invoice.setItemValue("partner.id", fixPartnerId);
invoice.setItemValue("cdtr.name", fixPartnerName);
invoice.setItemValue("partner.name", fixPartnerName);
documentService.save(invoice);
// workflowService.processWorkItem(invoice.event(10));
totalUpdates++;
}
}
log("│ ├── " + totalUpdates + " invoices migrated in " +
(System.currentTimeMillis() - l1) + "ms ", messageBuffer);
}
log("├── Successfully " + totalUpdates + " invoices migrated",
messageBuffer);
isRunning = false;
return messageBuffer.toString();
}
/**
* Diese hilfsmethode liest die Cargosoft Datei mit den Mapping - alt->neu ein
*
* @return
*/
private Map<String, String> readAdressMapping() {
Map<String, String> result = new HashMap<>();
try (InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream("agl_wandlung_20250822.csv");
BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String line;
boolean firstLine = true;
while ((line = reader.readLine()) != null) {
if (firstLine) {
firstLine = false;
continue; // Header überspringen
}
String[] parts = line.split(";");
if (parts.length == 2) {
String oldID = parts[0].trim();
String newID = parts[1].trim();
result.put(oldID, newID);
}
}
} catch (IOException e) {
// Logging framework verwenden
System.err.println("Fehler beim Laden der CSV: " + e.getMessage());
}
return result;
}
/**
* Hilfsmethode speichert eine cargoosft kreditor object...
*
* @param bpID
* @param messageBuffer
* @return
*/
@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
public boolean syncBusinessPartner(ItemCollection cargosoftItemCol) {
String bpip = InvoiceUtil.buildBPID(cargosoftItemCol.getItemValueString("name"));
if (businessPartnerService.getBusinessPartnerByID(bpip) == null) {
// not available so we save just the cargosoft entry once...
documentService.save(cargosoftItemCol);
return true;
}
return false;
}
/**
* Dieser agent löscht einfach alle Businesspartner objekte. Wird eigentlich
* nicht mehr benötigt.
*
* curl -H "Cookie:
* JSESSIONID=ShAegfNiXmqLFLSFwcUZ7hrLLQDKZGKiu4oj_xeX.imixs-office-workflow-8586d7d58d-h6k4q"
* https://alexander-logistics-dwc.office-workflow.de/api/cargosoft/bp-delete?maxcount=10000
*
* @return
* @throws QueryException
* @throws AccessDeniedException
* @throws ProcessingErrorException
* @throws PluginException
* @throws ModelException
*/
// @GET
// @Path("/bp-delete")
// @Produces({ MediaType.TEXT_PLAIN })
public String deleteBusinessPartner(@QueryParam("maxcount") int maxcount)
throws QueryException, AccessDeniedException, ProcessingErrorException, PluginException, ModelException {
StringBuffer messageBuffer = new StringBuffer();
String query = "(type:workitem*) AND ($modelversion: businesspartner*)";
int deletions = 0;
long l = System.currentTimeMillis();
int batchSize = 100;
int totalObjects = 0;
log("├── delete old business partner objects....", messageBuffer);
if (isRunning) {
log("├── sync process already running!", messageBuffer);
return messageBuffer.toString();
}
isRunning = true;
log("│   ├── maxcount= " + maxcount, messageBuffer);
int totalCount = documentService.count(query);
log("│   ├── found " + totalCount + " entities ", messageBuffer);
// Berechne Anzahl der benötigten Pages
int totalPages = (int) Math.ceil((double) totalCount / batchSize);
// Verarbeite Page für Page
int iterations = 0;
while (deletions < maxcount) {
List<ItemCollection> cargosoftDataList = documentService.find(query, batchSize, 0);
if (cargosoftDataList.size() == 0) {
break;
}
for (ItemCollection cargosoftItemCol : cargosoftDataList) {
totalObjects++;
if (deleteBusinessPartner(cargosoftItemCol, messageBuffer)) {
deletions++;
}
if (deletions >= maxcount)
break;
}
// Explicit flush the lucene search event log
updateService.updateIndex();
// Fortschritt loggen
iterations++;
log("│ ├── Processed page " + (iterations) + " of " + totalPages + " (" + deletions + " total deletions)",
messageBuffer);
// Optional: Kurze Pause nach jedem 5. Batch
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (deletions >= maxcount)
break;
}
long duration = System.currentTimeMillis() - l;
double objectsPerSecond = totalObjects / (duration / 1000.0);
log("├── Successfully " + deletions + " business partner objects deleted in " + duration + "ms ("
+ String.format("%.1f", objectsPerSecond) + " objects/sec)", messageBuffer);
isRunning = false;
return messageBuffer.toString();
}
/**
* Entfernt doppelte Eintrage - sind irgnedwie entstandnen :(((
*
* @return
* @throws QueryException
* @throws AccessDeniedException
* @throws ProcessingErrorException
* @throws PluginException
* @throws ModelException
*/
// @GET
// @Path("/remove-dubletten")
// @Produces({ MediaType.TEXT_PLAIN })
public String deleteCargosoftDupplicates(@QueryParam("maxcount") int maxcount)
throws QueryException, AccessDeniedException, ProcessingErrorException, PluginException, ModelException {
StringBuffer messageBuffer = new StringBuffer();
String query = "(type:cargosoftkreditor)";
int deletions = 0;
long l = System.currentTimeMillis();
int batchSize = 100;
int totalObjects = 0;
log("├── delete cargosoft dupplicaates....", messageBuffer);
if (isRunning) {
log("├── sync process already running!", messageBuffer);
return messageBuffer.toString();
}
isRunning = true;
log("│   ├── maxcount= " + maxcount, messageBuffer);
int totalCount = documentService.count(query);
log("│   ├── found " + totalCount + " cargosoft entries", 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);
if (cargosoftDataList.size() == 0) {
break;
}
for (ItemCollection cargosoftItemCol : cargosoftDataList) {
totalObjects++;
String name = cargosoftItemCol.getItemValueString("name");
String subQuery = "(type:cargosoftkreditor) AND (name:" + name + ")";
List<ItemCollection> dupplicateList = documentService.find(subQuery, 100, 0, "$modified", true);
if (dupplicateList.size() > 1) {
// alle bis auf den ersten löschen
log("│   ├── found duplicate: " + name, messageBuffer);
boolean first = true;
for (ItemCollection dubel : dupplicateList) {
if (first) {
first = false;
continue;
}
// löschen!
documentService.remove(dubel);
deletions++;
}
}
if (deletions >= maxcount)
break;
}
// Explicit flush the lucene search event log
updateService.updateIndex();
// Fortschritt loggen
// Fortschritt loggen
log("│ ├── Processed page " + (pageIndex + 1) + " of " + totalPages + " (" + deletions
+ " total deletions)", messageBuffer);
// Optional: Kurze Pause nach jedem 5. Batch
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (deletions >= maxcount)
break;
}
long duration = System.currentTimeMillis() - l;
double objectsPerSecond = totalObjects / (duration / 1000.0);
log("├── Successfully " + deletions + " duplicated cargosoft objects deleted in " + duration + "ms ("
+ String.format("%.1f", objectsPerSecond) + " objects/sec)", messageBuffer);
isRunning = false;
return messageBuffer.toString();
}
/**
* Hilfsmethode - löscht ein cargoosoft object...
*
* @param bpID
* @param messageBuffer
* @return
*/
@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
public boolean deleteBusinessPartner(ItemCollection cargosoftItemCol, StringBuffer messageBuffer) {
documentService.remove(cargosoftItemCol);
return true;
}
/**
* Importiert vom Server Filesystem eine XML Datei
*
* Dies dient zum testen in dem wir unter /transfer/ xml Daten aus der
* Produktion ablegen die wir so importiern können
*
*
*/
@GET
@Path("/import")
@Produces({ MediaType.TEXT_PLAIN })
public String importXMLFile(@QueryParam("file") String filePath)
throws QueryException, AccessDeniedException, ProcessingErrorException,
PluginException, ModelException {
StringBuffer messageBuffer = new StringBuffer();
if (filePath != null && filePath.endsWith(".xml")) {
log("├── import xml file: " + filePath, messageBuffer);
List<ItemCollection> col = null;
try (InputStream inputStream = new FileInputStream(filePath)) {
col = XMLDataCollectionAdapter.readCollectionFromInputStream(inputStream);
if (col != null) {
log("├── import ok - " + col.size() + " documents found", messageBuffer);
// Mahnwesen Process = 227af2bc-22ad-4332-8781-98ff853ca3fd
// documentService.find(filePath, totalUpdates, totalUpdates)
for (ItemCollection workitem : col) {
workitem.setItemValue("process.ref", "227af2bc-22ad-4332-8781-98ff853ca3fd");
workitem.removeItem("$immutable");
workitem.removeItem("$snapshotid");
workitem.setType("workitem");
// fix id
String id = workitem.getUniqueID();
id = id.substring(0, id.lastIndexOf("-"));
workitem.setItemValue("$uniqueid", id);
log("├── import : " + id, messageBuffer);
documentService.save(workitem);
}
} else {
log("├── Import Failed: no data found!", messageBuffer);
}
} catch (JAXBException | IOException e) {
log("├── Import Failed: " + e.getMessage(), messageBuffer);
}
log("├── Import finished", messageBuffer);
}
return messageBuffer.toString();
}
private void log(String message, StringBuffer messageLog) {
logger.info(message);
messageLog.append(message + "\n");
}
}