debitor analyse update

This commit is contained in:
Ralph Soika 2024-10-09 13:52:59 +02:00
parent ec2ee8a8e9
commit b09846efbe
3 changed files with 356 additions and 28 deletions

View file

@ -0,0 +1,315 @@
package com.alexanderlogistics;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.apache.poi.ss.usermodel.CellCopyPolicy;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.imixs.workflow.FileData;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.DocumentService;
import org.imixs.workflow.engine.WorkflowService;
import org.imixs.workflow.exceptions.AdapterException;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.QueryException;
import org.imixs.workflow.office.config.ConfigService;
import org.imixs.workflow.poi.POIFindReplaceAdapter;
import org.imixs.workflow.util.XMLParser;
import jakarta.inject.Inject;
/**
* Der AGLAnalyticExcelAdapterDebitor exportiert eine Excel Datei aus einem
* Textblock und fügt die offenen Recnungen ein
*
* Der Adapter wird im Modell wie folgt konfiguriert
*
* <pre>
* {@code
<soa name="excel-export">
<textblock>textblock-ref</textblock>
<template>filename</template>
<targetname>filename</targetname>
</soa>
}
* </pre>
*
* <p>
* Der Adapter erweitert den POIAdapter somit können Felder aktualisiert werden
* (siehe POIFineReplaceAdapter).
*
* <p>
* Der Adapter kopiert die ausgangsrechnungen in neue Zeilen, welche ab
* Zeilennummer
* 16 eingefügt werden.
* <p>
*
* Abschliessend wird die Zelle 'TOTAL' noch aktualisiert.
*
*
* @version 1.0
* @author rsoika
*/
public class AGLAnalyticExcelAdapterDebitor extends POIFindReplaceAdapter {
private static Logger logger = Logger.getLogger(AGLAnalyticExcelAdapter.class.getName());
@Inject
WorkflowService workflowService;
@Inject
OPListExportService opListExportService;
@Inject
DocumentService documentService;
@Inject
ConfigService configService;
/**
* This method
*
* @throws PluginException
*/
@SuppressWarnings("unchecked")
@Override
public ItemCollection execute(ItemCollection document, ItemCollection event)
throws AdapterException, PluginException {
// ermittle die Hauptwährungen
ItemCollection configItemCollection = configService.loadConfiguration("AGL_CONFIGURATION");
List<String> currencyList = configItemCollection.getItemValue("currency.out");
if (currencyList == null || currencyList.size() < 2) {
throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(),
OPListExportService.ERROR_CONFIG,
"missing Currency configuration - please check parameters");
}
// read the Steuerbescheide options
ItemCollection ausgangsrechnungConfig = workflowService.evalWorkflowResult(event, "soa",
document,
false);
if (ausgangsrechnungConfig == null || !ausgangsrechnungConfig.hasItem("excel-export")) {
throw new PluginException(AGLAnalyticExcelAdapterDebitor.class.getSimpleName(), CONFIG_ERROR,
"missing soa configuration in model event - please check model configuration");
}
List<String> excelDefList = ausgangsrechnungConfig.getItemValue("excel-export");
ItemCollection excelDefCollection = XMLParser.parseItemStructure(excelDefList.get(0));
String textblock = excelDefCollection.getItemValueString("textblock");
String template = excelDefCollection.getItemValueString("template");
String targetName = excelDefCollection.getItemValueString("target-name");
// adapt text....
targetName = workflowService.adaptText(targetName, document);
try {
appendExcelTemplate(document, textblock, template, targetName);
insertInvoiceRows(document, targetName);
logger.info("... loading poi configuration..");
// Process POI instructions
// read the config
ItemCollection poiConfig = workflowService.evalWorkflowResult(event, "poi-update", document,
false);
if (poiConfig == null || !poiConfig.hasItem("findreplace")) {
throw new PluginException(POIFindReplaceAdapter.class.getSimpleName(), CONFIG_ERROR,
"missing poi configuration");
}
List<String> replaceDevList = poiConfig.getItemValue("findreplace");
String eval = poiConfig.getItemValueString("eval");
logger.info("... update template with normal poi information..");
this.updateFileData(document.getFileData(targetName), document, replaceDevList, eval);
} catch (PluginException | IOException | QueryException e) {
throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(),
OPListExportService.ERROR_CONFIG,
"failed to update steuerbescheide: " + e.getMessage());
}
logger.info("... completed!");
return document;
}
/**
* Hilfsmethode die das fuehrende K/D aus der Debitorennummer entfernt
*
* @return
*/
private String getDbtNr(ItemCollection workitem) {
String dbtNr = workitem.getItemValueString("dbtr.number");
if (dbtNr.startsWith("D") || dbtNr.startsWith("K")) {
dbtNr = dbtNr.substring(1);
}
return dbtNr;
}
/**
* This helper method inserts a row for each invoice at row 16
* into the excel template file
* <p>
* The method copies the Row A16 as a reference row
* <p>
* The named cell 'TOTAL' should contain the summary formula. It will be
* evaluated at the end.
*
* @throws PluginException
* @throws QueryException
*/
private void insertInvoiceRows(ItemCollection document, String fileName)
throws PluginException, QueryException {
// load dummy rechnungen
List<ItemCollection> invoices = loadInvoices(getDbtNr(document));
FileData fileData = document.getFileData(fileName);
// load XSSFWorkbook
XSSFWorkbook doc = null;
try (InputStream imputStream = new ByteArrayInputStream(fileData.getContent())) {
doc = new XSSFWorkbook(imputStream);
// NOTE: we only take the first sheet !
XSSFSheet sheet = doc.getSheetAt(0);
CellReference invoiceRefCell = new CellReference("A11");
XSSFRow referenceRowInvoice = sheet.getRow(invoiceRefCell.getRow());
int referenceRowPos = 10;
int rowPos = referenceRowPos;
int lastRow = 2999;
logger.finest("Last rownum=" + lastRow);
// jetzt füge alle Rechnungen an.
int totalRowCount = invoices.size();
sheet.shiftRows(referenceRowPos, lastRow, totalRowCount, true, true);
for (ItemCollection invoice : invoices) {
logger.fine("......add invoice " + invoice.getUniqueID());
// now create a new line..
XSSFRow row = sheet.createRow(rowPos);
row.copyRowFrom(referenceRowInvoice, new CellCopyPolicy());
// insert values
row.getCell(0)
.setCellValue(invoice.getItemValueDate("$created"));
row.getCell(1, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueString("invoice.number"));
row.getCell(2, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueDate("invoice.date"));
row.getCell(3, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueDate("invoice.duedate"));
row.getCell(4, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueString("space.name"));
row.getCell(5, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueString("invoice.text"));
// Waehrung?
if (invoice.getItemValueDouble("invoice.rate") == 0) {
row.getCell(6, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueDouble("invoice.total"));
row.getCell(7, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueDouble("invoice.saldo"));
} else {
row.getCell(8, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueDouble("invoice.total"));
row.getCell(9, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueDouble("invoice.saldo"));
}
row.getCell(10, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueString("$workflowstatus"));
rowPos++;
}
// Leerzeile
// categoryRow = sheet.createRow(rowPos);
rowPos++;
// write back the file
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
doc.write(byteArrayOutputStream);
byte[] newContent = byteArrayOutputStream.toByteArray();
FileData fileDataNew = new FileData(fileData.getName(), newContent, fileData.getContentType(),
null);
// update the fileData
document.addFileData(fileDataNew);
logger.finest("......new document added");
} catch (IOException e) {
throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(),
OPListExportService.ERROR_CONFIG,
"failed to update opliste: " + e.getMessage());
} finally {
if (doc != null) {
try {
doc.close();
} catch (IOException e) {
logger.severe("Failed to close workbook: " + e.getMessage());
e.printStackTrace();
}
}
}
}
/**
* This method loads a text-block for a specified ref and appends the named
* fileData object of this document.
*
* @param document
* @throws PluginException
*/
private void appendExcelTemplate(ItemCollection document, String textblockRef, String template,
String targetName)
throws PluginException {
if ((template == null || template.isEmpty()) || (textblockRef == null || textblockRef.isEmpty())) {
throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(),
OPListExportService.ERROR_CONFIG,
"invalid SOA configuration in model event - textblock/template reference not defined!");
}
// load the text block
FileData fileData = opListExportService.loadTextBlockFileData(textblockRef, template);
// do we found the document?
if (fileData == null) {
throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(),
OPListExportService.ERROR_CONFIG,
"invalid SOA configuration in model event - template=" + template
+ " not found!");
}
fileData.setName(targetName);
// append document
logger.info("...append new SOA Template: " + targetName);
document.addFileData(fileData);
}
/**
* Diese Methode läd alle offenen Steuerbescheide
*
* @return
*/
private List<ItemCollection> loadInvoices(String dbtrNumber) {
List<ItemCollection> result = new ArrayList<>();
String query = "(type:workitem) AND dbtr.number:" + dbtrNumber
+ " AND $modelversion:rechnungsausgang-*";
try {
result = documentService.find(
query, 9999, 0,
"invoice.number", false);
} catch (QueryException e) {
e.printStackTrace();
}
return result;
}
}

BIN
templates/soa_template.xlsx Normal file

Binary file not shown.

View file

@ -29,7 +29,7 @@
</imixs:item> </imixs:item>
<open-bpmn:auto-align>true</open-bpmn:auto-align> <open-bpmn:auto-align>true</open-bpmn:auto-align>
</bpmn2:extensionElements> </bpmn2:extensionElements>
<bpmn2:signal id="Signal_1" name="com.alexanderlogistics.AGLAnalyticExcelAdapter"/> <bpmn2:signal id="Signal_1" name="com.alexanderlogistics.AGLAnalyticExcelAdapterDebitor"/>
<bpmn2:message id="Message_2" name="Html-Header"> <bpmn2:message id="Message_2" name="Html-Header">
<bpmn2:documentation id="Documentation_22"><![CDATA[<html> <bpmn2:documentation id="Documentation_22"><![CDATA[<html>
<head> <head>
@ -123,7 +123,6 @@ th { font-weight: bold;}
<bpmn2:documentation id="Documentation_4"><![CDATA[<textblock><itemvalue>$workflowgroup</itemvalue> - <itemvalue>$workflowstatus</itemvalue></textblock>]]></bpmn2:documentation> <bpmn2:documentation id="Documentation_4"><![CDATA[<textblock><itemvalue>$workflowgroup</itemvalue> - <itemvalue>$workflowstatus</itemvalue></textblock>]]></bpmn2:documentation>
<bpmn2:outgoing>SequenceFlow_10</bpmn2:outgoing> <bpmn2:outgoing>SequenceFlow_10</bpmn2:outgoing>
<bpmn2:incoming>sequenceFlow_OL83kQ</bpmn2:incoming> <bpmn2:incoming>sequenceFlow_OL83kQ</bpmn2:incoming>
<bpmn2:incoming>sequenceFlow_P5vJKg</bpmn2:incoming>
</bpmn2:task> </bpmn2:task>
<bpmn2:sequenceFlow id="SequenceFlow_10" sourceRef="Task_2" targetRef="EndEvent_3"> <bpmn2:sequenceFlow id="SequenceFlow_10" sourceRef="Task_2" targetRef="EndEvent_3">
<bpmn2:documentation id="documentation_WzPj2A"/> <bpmn2:documentation id="documentation_WzPj2A"/>
@ -164,7 +163,7 @@ th { font-weight: bold;}
<bpmn2:documentation id="Documentation_3"><![CDATA[<textblock><itemvalue>$workflowgroup</itemvalue> - <itemvalue>$workflowstatus</itemvalue></textblock>]]></bpmn2:documentation> <bpmn2:documentation id="Documentation_3"><![CDATA[<textblock><itemvalue>$workflowgroup</itemvalue> - <itemvalue>$workflowstatus</itemvalue></textblock>]]></bpmn2:documentation>
<bpmn2:incoming>SequenceFlow_4</bpmn2:incoming> <bpmn2:incoming>SequenceFlow_4</bpmn2:incoming>
<bpmn2:outgoing>sequenceFlow_17AQjg</bpmn2:outgoing> <bpmn2:outgoing>sequenceFlow_17AQjg</bpmn2:outgoing>
<bpmn2:outgoing>sequenceFlow_nZPhew</bpmn2:outgoing> <bpmn2:incoming>sequenceFlow_1K0l0g</bpmn2:incoming>
</bpmn2:task> </bpmn2:task>
<bpmn2:sequenceFlow id="SequenceFlow_4" sourceRef="StartEvent_1" targetRef="Task_1"> <bpmn2:sequenceFlow id="SequenceFlow_4" sourceRef="StartEvent_1" targetRef="Task_1">
<bpmn2:documentation id="documentation_p6trwA"/> <bpmn2:documentation id="documentation_p6trwA"/>
@ -308,24 +307,47 @@ th { font-weight: bold;}
<imixs:value><![CDATA[0]]></imixs:value> <imixs:value><![CDATA[0]]></imixs:value>
</imixs:item> </imixs:item>
<imixs:item name="rtfresultlog" type="xs:string"> <imixs:item name="rtfresultlog" type="xs:string">
<imixs:value><![CDATA[Analysedaten archiviert]]></imixs:value> <imixs:value><![CDATA[Analysedaten Debitor archiviert]]></imixs:value>
</imixs:item> </imixs:item>
<imixs:item name="keyupdateacl" type="xs:boolean"> <imixs:item name="keyupdateacl" type="xs:boolean">
<imixs:value>false</imixs:value> <imixs:value>false</imixs:value>
</imixs:item> </imixs:item>
<imixs:item name="txtactivityresult" type="xs:string"> <imixs:item name="txtactivityresult" type="xs:string">
<imixs:value><![CDATA[<item name="process">Mahnwesen</item> <imixs:value><![CDATA[<item name="process">Mahnwesen</item>
<steuerbescheide name="excel-export"> <soa name="excel-export">
<textblock>Steuerbescheide Template</textblock> <textblock>SOA Template</textblock>
<template>steuerbescheid-liste_template.xlsx</template> <template>soa_template.xlsx</template>
<target-name>steuerbescheide_<itemvalue format="yyyy-MM-dd">$lasteventdate</itemvalue>.xlsx</target-name> <target-name>soa_<itemvalue>dbtr.number</itemvalue>_<itemvalue format="yyyy-MM-dd">$lasteventdate</itemvalue>.xlsx</target-name>
</steuerbescheide> </soa>
<poi-update name="findreplace"> <poi-update name="findreplace">
<find>K6</find> <find>I6</find>
<replace><itemvalue format="dd.MM.yyyy">$lasteventdate</itemvalue></replace> <replace><itemvalue format="dd.MM.yyyy">$lasteventdate</itemvalue></replace>
<type>date</type> <type>date</type>
</poi-update> </poi-update>
<poi-update name="findreplace">
<find>D6</find>
<replace><itemvalue>dbtr.name</itemvalue></replace>
<type>date</type>
</poi-update>
<poi-update name="findreplace">
<find>G10</find>
<replace>Total AED</replace>
</poi-update>
<poi-update name="findreplace">
<find>H10</find>
<replace>Saldo AED</replace>
</poi-update>
<poi-update name="findreplace">
<find>I10</find>
<replace>Total USD</replace>
</poi-update>
<poi-update name="findreplace">
<find>J10</find>
<replace>Saldo USD</replace>
</poi-update>
<poi-update name="eval">TOTAL1;TOTAL2;TOTAL3;TOTAL4</poi-update> <poi-update name="eval">TOTAL1;TOTAL2;TOTAL3;TOTAL4</poi-update>
]]></imixs:value> ]]></imixs:value>
@ -333,15 +355,8 @@ th { font-weight: bold;}
</bpmn2:extensionElements> </bpmn2:extensionElements>
<bpmn2:documentation id="documentation_pCTOtQ"><![CDATA[Excel Datei erzeugen]]></bpmn2:documentation> <bpmn2:documentation id="documentation_pCTOtQ"><![CDATA[Excel Datei erzeugen]]></bpmn2:documentation>
<bpmn2:signalEventDefinition id="signalEventDefinition_007muw" signalRef="Signal_1"/> <bpmn2:signalEventDefinition id="signalEventDefinition_007muw" signalRef="Signal_1"/>
<bpmn2:incoming>sequenceFlow_nZPhew</bpmn2:incoming> <bpmn2:outgoing>sequenceFlow_1K0l0g</bpmn2:outgoing>
<bpmn2:outgoing>sequenceFlow_P5vJKg</bpmn2:outgoing>
</bpmn2:intermediateCatchEvent> </bpmn2:intermediateCatchEvent>
<bpmn2:sequenceFlow id="sequenceFlow_nZPhew" sourceRef="Task_1" targetRef="event_HrHS9A">
<bpmn2:documentation id="documentation_IN9m7w"/>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="sequenceFlow_P5vJKg" sourceRef="event_HrHS9A" targetRef="Task_2">
<bpmn2:documentation id="documentation_gV4coQ"/>
</bpmn2:sequenceFlow>
<bpmn2:dataObject id="dataObject_00mX6w" name="Form"> <bpmn2:dataObject id="dataObject_00mX6w" name="Form">
<bpmn2:documentation id="documentation_lUG0qw"><![CDATA[<imixs-form> <bpmn2:documentation id="documentation_lUG0qw"><![CDATA[<imixs-form>
<imixs-form-section columns="2"> <imixs-form-section columns="2">
@ -355,7 +370,7 @@ th { font-weight: bold;}
</imixs-form-section> </imixs-form-section>
<imixs-form-section columns="1" label="Trend Zahlungsmoral"> <imixs-form-section columns="1" label="Trend Zahlungsmoral">
<item name="analytic.invoices.trend" type="custom" path="alexander/analyze_chart" label="Zahlungen / Monat:" /> <item name="analytic.invoices.trend" type="custom" path="alexander/analyze_chart" label="Zahlungen innerhalb der letzten 6 Monate:" />
</imixs-form-section> </imixs-form-section>
@ -365,6 +380,9 @@ th { font-weight: bold;}
<bpmn2:association id="association_hwRAUQ" sourceRef="dataObject_00mX6w" targetRef="Task_2"> <bpmn2:association id="association_hwRAUQ" sourceRef="dataObject_00mX6w" targetRef="Task_2">
<bpmn2:documentation id="documentation_rNw1ng"/> <bpmn2:documentation id="documentation_rNw1ng"/>
</bpmn2:association> </bpmn2:association>
<bpmn2:sequenceFlow id="sequenceFlow_1K0l0g" sourceRef="event_HrHS9A" targetRef="Task_1">
<bpmn2:documentation id="documentation_CVv7XQ"/>
</bpmn2:sequenceFlow>
</bpmn2:process> </bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1" name="Default Process Diagram"> <bpmndi:BPMNDiagram id="BPMNDiagram_1" name="Default Process Diagram">
<bpmndi:BPMNPlane bpmnElement="Collaboration_1" id="BPMNPlane_1"> <bpmndi:BPMNPlane bpmnElement="Collaboration_1" id="BPMNPlane_1">
@ -446,15 +464,6 @@ th { font-weight: bold;}
<dc:Bounds height="20.0" width="100.0" x="295.0" y="426.0"/> <dc:Bounds height="20.0" width="100.0" x="295.0" y="426.0"/>
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_nZPhew" id="BPMNEdge_5PzaQQ" sourceElement="BPMNShape_Task_1" targetElement="BPMNShape_hrK7fA">
<di:waypoint x="345.0" y="340.0"/>
<di:waypoint x="345.0" y="387.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_P5vJKg" id="BPMNEdge_aN5clQ" sourceElement="BPMNShape_hrK7fA" targetElement="BPMNShape_Task_2">
<di:waypoint x="363.0" y="405.0"/>
<di:waypoint x="623.0" y="405.0"/>
<di:waypoint x="623.0" y="340.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="dataObject_00mX6w" id="BPMNShape_UEsYmw"> <bpmndi:BPMNShape bpmnElement="dataObject_00mX6w" id="BPMNShape_UEsYmw">
<dc:Bounds height="50.0" width="35.0" x="560.0" y="190.0"/> <dc:Bounds height="50.0" width="35.0" x="560.0" y="190.0"/>
<bpmndi:BPMNLabel id="BPMNLabel_ekwD3A"> <bpmndi:BPMNLabel id="BPMNLabel_ekwD3A">
@ -467,6 +476,10 @@ th { font-weight: bold;}
<di:waypoint x="625.0" y="265.0"/> <di:waypoint x="625.0" y="265.0"/>
<di:waypoint x="625.0" y="290.0"/> <di:waypoint x="625.0" y="290.0"/>
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_1K0l0g" id="BPMNEdge_1KePCQ" sourceElement="BPMNShape_hrK7fA" targetElement="BPMNShape_Task_1">
<di:waypoint x="345.0" y="387.0"/>
<di:waypoint x="345.0" y="340.0"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane> </bpmndi:BPMNPlane>
<bpmndi:BPMNLabelStyle id="BPMNLabelStyle_1"> <bpmndi:BPMNLabelStyle id="BPMNLabelStyle_1">
<dc:Font name="arial" size="9.0"/> <dc:Font name="arial" size="9.0"/>