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.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator;
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.poi.POIUtil;
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
*
*
* {@code
textblock-ref
filename
filename
}
*
*
*
* Der Adapter erweitert den POIAdapter somit können Felder aktualisiert werden
* (siehe POIFineReplaceAdapter).
*
*
* Der Adapter kopiert die ausgangsrechnungen in neue Zeilen, welche ab
* Zeilennummer
* 16 eingefügt werden.
*
*
* 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
InvoiceService invoiceService;
@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
List currencyList = invoiceService.getCurrenciesOut();
// read the 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 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 {
FileData fileData = appendExcelTemplate(document, textblock, template, targetName);
// get workbook
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);
// first add the columns for the currency list
addCurrencyColumns(doc, sheet, currencyList);
// add rows
insertInvoiceRows(sheet, document, currencyList);
// write back the updated excel file
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
doc.write(byteArrayOutputStream);
byte[] newContent = byteArrayOutputStream.toByteArray();
FileData fileDataNew = new FileData(fileData.getName(), newContent, fileData.getContentType(),
null);
document.addFileData(fileDataNew);
logger.finest("......new document added");
} catch (IOException | QueryException e) {
throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(),
InvoiceService.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();
}
}
}
// Finally process POI instructions
processPOIUpdate(document, event, targetName);
} catch (PluginException e) {
throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(),
InvoiceService.ERROR_CONFIG,
"failed to update steuerbescheide: " + e.getMessage());
}
logger.info("... completed!");
return document;
}
/**
* A SOA can be created for one or multiple currencies. This helper method adds
* the Total and Saldo Colum for each currency
*
* The template provided only the columns for the first currency.
*
* @param sheet
* @param currencyList
*/
private void addCurrencyColumns(XSSFWorkbook doc, XSSFSheet sheet, List currencyList) {
if (currencyList == null || currencyList.size() <= 1) {
// no extra currencies defined!
return;
}
// zusaetzliche Zellen einfuegen...
int newColumns = (currencyList.size() - 1) * 2;
for (int i = 0; i < newColumns; i++) {
logger.info(".. add currency column...");
POIUtil.insertColumn(sheet, 6, 8 + i);
}
// Beschriftungen eintragen
int col = 6;
XSSFCell cell = null;
XSSFRow labelRow = sheet.getRow(9);
for (String currency : currencyList) {
cell = labelRow.getCell(col);
cell.setCellValue("Total " + currency);
col++;
cell = labelRow.getCell(col);
cell.setCellValue("Balance " + currency);
col++;
}
}
/**
* 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
*
* The method copies the Row A16 as a reference row
*
* The named cell 'TOTAL' should contain the summary formula. It will be
* evaluated at the end.
*
* @throws PluginException
* @throws QueryException
*/
private void insertInvoiceRows(XSSFSheet sheet, ItemCollection document, List currencyList)
throws PluginException, QueryException {
// load dummy rechnungen
List invoices = loadInvoices(getDbtNr(document));
// load XSSFWorkbook
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();
POIUtil.insertRows(sheet, "A11", totalRowCount - 0);
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?
String currency = invoice.getItemValueString("invoice.currency");
int totalCol = findTotalPos(currency, currencyList);
row.getCell(totalCol, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueDouble("invoice.total"));
row.getCell(totalCol + 1, MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueDouble("invoice.saldo"));
// Status
row.getCell(6 + (currencyList.size() * 2), MissingCellPolicy.CREATE_NULL_AS_BLANK)
.setCellValue(invoice.getItemValueString("$workflowstatus"));
rowPos++;
}
// setze Summen Formeln
for (int column = 6; column < currencyList.size() * 2 + 6; column++) {
XSSFCell cell = sheet.getRow(totalRowCount + 11).getCell(column); // G11
String cellRefFrom = POIUtil.getCellReference(column, 10);
String cellRefTo = POIUtil.getCellReference(column, totalRowCount + 10);
String formula = "SUM(" + cellRefFrom + ":" + cellRefTo + ")";
cell.setCellFormula(formula);
}
// Optional: Formel direkt evaluieren
XSSFFormulaEvaluator.evaluateAllFormulaCells(sheet.getWorkbook());
}
/**
* Findet die Total Spalte für eine Währung
*
* @param currency
* @return
*/
private int findTotalPos(String currency, List currencyList) {
int result = 6;
if (currency != null) {
for (String cur : currencyList) {
if (currency.equals(cur)) {
return result;
}
result = result + 2;
}
}
return result;
}
/**
* This method loads a text-block for a specified ref and appends the named
* fileData object of this document.
*
* @param document
* @throws PluginException
*/
private FileData 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(),
InvoiceService.ERROR_CONFIG,
"invalid SOA configuration in model event - textblock/template reference not defined!");
}
// load the text block
FileData fileData = invoiceService.loadTextBlockFileData(textblockRef, template);
// do we found the document?
if (fileData == null) {
throw new PluginException(AGLAnalyticExcelAdapter.class.getSimpleName(),
InvoiceService.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);
return fileData;
}
/**
* Diese Methode läd alle offenen Steuerbescheide
*
* @return
*/
private List loadInvoices(String dbtrNumber) {
List 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;
}
}