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

* 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; } }