From 6cfea7439751d0138b1d6b1838d7386049d3b4bf Mon Sep 17 00:00:00 2001 From: Ralph Soika Date: Mon, 12 May 2025 14:24:13 +0200 Subject: [PATCH] dataview - excel export --- .../office/dataview/DataViewController.java | 148 ++++++-- .../office/dataview/DataViewPOIHelper.java | 356 ++++++++++++++++++ .../pages/admin/dataViewDefinition.xhtml | 97 ++++- .../main/webapp/pages/dataviews/data.xhtml | 69 ++-- .../offene_rechnungen_template.xlsx} | Bin 50072 -> 52851 bytes 5 files changed, 593 insertions(+), 77 deletions(-) create mode 100644 office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/dataview/DataViewPOIHelper.java rename templates/{ausgangsrechnungen-export_template.xlsx => dataviews/offene_rechnungen_template.xlsx} (66%) diff --git a/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/dataview/DataViewController.java b/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/dataview/DataViewController.java index a0b44fa..14df9d3 100644 --- a/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/dataview/DataViewController.java +++ b/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/dataview/DataViewController.java @@ -23,22 +23,30 @@ *******************************************************************************/ package org.imixs.workflow.office.dataview; +import java.io.IOException; import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; +import org.imixs.archive.core.SnapshotService; +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.ModelException; +import org.imixs.workflow.exceptions.PluginException; import org.imixs.workflow.exceptions.QueryException; import org.imixs.workflow.faces.data.ViewController; import org.imixs.workflow.faces.data.ViewHandler; import org.imixs.workflow.office.forms.CustomFormController; import org.imixs.workflow.office.forms.CustomFormSection; +import com.alexanderlogistics.mahnlauf.InkassoExportAdapter; + import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.Conversation; import jakarta.enterprise.context.ConversationScoped; @@ -69,6 +77,10 @@ import jakarta.servlet.http.HttpServletRequest; public class DataViewController extends ViewController { private static final long serialVersionUID = 1L; + + public static final String ERROR_CONFIG = "CONFIG_ERROR"; + public static final int MAX_ROWS = 3000; + private List sections = null; private List viewItemDefinitions = null; protected ItemCollection dataDefinition = null; @@ -85,6 +97,12 @@ public class DataViewController extends ViewController { @Inject private DocumentService documentService; + @Inject + private WorkflowService workflowService; + + @Inject + SnapshotService snapshotService; + @Inject CustomFormController customFormController; @@ -111,9 +129,7 @@ public class DataViewController extends ViewController { * This method loads the custom form sections */ public void onLoad() { - String cacheid = null; - logger.info("> onload..."); // Important: start a new conversation beause of the usage of the // CustomFormController! @@ -134,7 +150,7 @@ public class DataViewController extends ViewController { dataDefinition = documentService.load(uniqueid); } - logger.info("> cacheid=" + cacheid); + logger.finest("> cacheid=" + cacheid); if (cacheid != null && !cacheid.isEmpty()) { filter = dataViewCache.get(cacheid); } else { @@ -145,13 +161,21 @@ public class DataViewController extends ViewController { // Init new Filter.... if (dataDefinition != null) { filter.setItemValue("txtWorkflowEditorCustomForm", dataDefinition.getItemValue("form")); + filter.setItemValue("name", dataDefinition.getItemValueString("name")); + filter.setItemValue("description", dataDefinition.getItemValueString("description")); viewItemDefinitions = DataViewDefinitionController .computeDataViewItemDefinitions(dataDefinition); customFormController.computeFieldDefinition(filter); sections = customFormController.getSections(); - logger.info(" < cached page index=" + filter.getItemValueInteger("pageIndex")); + // Update View Handler settings + String sortBy = dataDefinition.getItemValueString("sort.by"); + if (sortBy.isEmpty()) { + sortBy = "$modified"; + } + this.setSortBy(sortBy); + this.setSortReverse(dataDefinition.getItemValueBoolean("sort.reverse")); this.setPageIndex(filter.getItemValueInteger("pageIndex")); if (!filter.getItemValueString("query").isEmpty()) { query = filter.getItemValueString("query"); @@ -229,7 +253,7 @@ public class DataViewController extends ViewController { // Replace all occurrences in the query case-insensitive. query = query.replaceAll("(?i)\\{" + Pattern.quote(itemName) + "\\}", itemValue); } - logger.info("query=" + query); + logger.finest("query=" + query); filter.setItemValue("query", query); // Prefetch data to update total count and page count @@ -246,29 +270,6 @@ public class DataViewController extends ViewController { return query; } - // /** - // * Returns the current workItem. If no workitem is defined the method - // * Instantiates a empty ItemCollection. - // * - // * @return - current workItem or null if not set - // */ - // public ItemCollection getData() { - // // do initialize an empty workItem here if null - // if (data == null) { - // reset(); - // } - // return data; - // } - - // /** - // * Set the current workItem - // * - // * @param workitem - new reference or null to clear the current workItem. - // */ - // public void setData(ItemCollection document) { - // this.data = document; - // } - /** * This method navigates back in the page index and caches the current page * index @@ -285,7 +286,6 @@ public class DataViewController extends ViewController { public void forward() { viewHandler.forward(this); filter.setItemValue("pageIndex", this.getPageIndex()); - logger.info("-> forward to page " + this.getPageIndex()); } /** @@ -297,9 +297,97 @@ public class DataViewController extends ViewController { ((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest()) .getSession().getMaxInactiveInterval() * 1000); conversation.begin(); - logger.log(Level.INFO, "......start new conversation, id={0}", + logger.log(Level.FINEST, "......start new conversation, id={0}", conversation.getId()); + } } + /** + * Exports data into a excel template processed by apache-poi + * + * @throws PluginException + * @throws QueryException + */ + public String export() throws PluginException, QueryException { + + // build query and prepare dataset + run(); + + SimpleDateFormat dateformat = new SimpleDateFormat("yyyyMMddHHmm"); + String targetFileName = dataDefinition.getItemValueString("poi.targetFilename"); + if (targetFileName.isEmpty()) { + throw new PluginException(InkassoExportAdapter.class.getSimpleName(), ERROR_CONFIG, + "Missing Excel Export definition - check configuration!"); + } + logger.info("start export : " + targetFileName + "..."); + logger.fine(query); + + // load template + FileData fileData = loadTemplate(); + + if (fileData == null) { + // we did not found the template! + throw new PluginException(InkassoExportAdapter.class.getSimpleName(), ERROR_CONFIG, + "Missing Excel Export Template - check DataView definition!"); + } + targetFileName = targetFileName + "_" + dateformat.format(new Date()) + ".xlsx"; + try { + // test if query exceeds max count + int totalCount = documentService.count(query); + if (totalCount > MAX_ROWS) { + throw new PluginException(InkassoExportAdapter.class.getSimpleName(), ERROR_CONFIG, + "Data can not be exported into Excel because dataset exceeds " + MAX_ROWS + " rows!"); + } + String sortBy = dataDefinition.getItemValueString("sort.by"); + if (sortBy.isEmpty()) { + sortBy = "$modified"; // default + } + List invoices = documentService.find(query, MAX_ROWS, 0, sortBy, + dataDefinition.getItemValueBoolean("sort.reverse")); + if (invoices.size() > 0) { + String referenceCell = dataDefinition.getItemValueString("poi.referenceCell"); + DataViewPOIHelper.insertDataRows(invoices, referenceCell, viewItemDefinitions, fileData); + } + + // create a temp event + ItemCollection event = new ItemCollection().setItemValue("txtActivityResult", + dataDefinition.getItemValue("poi.update")); + ItemCollection poiConfig = workflowService.evalWorkflowResult(event, "poi-update", dataDefinition, + false); + DataViewPOIHelper.poiUpdate(filter, fileData, poiConfig, workflowService); + + fileData.setName(targetFileName); + + // See: + // https://stackoverflow.com/questions/9391838/how-to-provide-a-file-download-from-a-jsf-backing-bean + DataViewPOIHelper.downloadExcelFile(fileData); + } catch (IOException | QueryException e) { + throw new PluginException(InkassoExportAdapter.class.getSimpleName(), ERROR_CONFIG, + "Failed to generate Excel Export: " + e.getMessage()); + } + + // return "/pages/admin/excel_export_rechnungsausgang.jsf?faces-redirect=true"; + return ""; + } + + /** + * This method returns the first excel poi template from the Data Definition + * + * @param name in attribute txtname + * + * + */ + private FileData loadTemplate() { + + // first filename + List fileDataList = dataDefinition.getFileData(); + if (fileDataList != null && fileDataList.size() > 0) { + String fileName = fileDataList.get(0).getName(); + return snapshotService.getWorkItemFile(dataDefinition.getUniqueID(), fileName); + } + // no file data available! + return null; + } + } diff --git a/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/dataview/DataViewPOIHelper.java b/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/dataview/DataViewPOIHelper.java new file mode 100644 index 0000000..ad3c09b --- /dev/null +++ b/office-alexander-logistics-app/src/main/java/org/imixs/workflow/office/dataview/DataViewPOIHelper.java @@ -0,0 +1,356 @@ +/******************************************************************************* + * Imixs Workflow Technology + * Copyright (C) 2003, 2008 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 + * + * Contributors: + * Imixs Software Solutions GmbH - initial API and implementation + * Ralph Soika + * + *******************************************************************************/ +package org.imixs.workflow.office.dataview; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Date; +import java.util.List; +import java.util.logging.Logger; + +import org.apache.poi.ss.usermodel.CellCopyPolicy; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.util.CellReference; +import org.apache.poi.xssf.usermodel.XSSFCell; +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.WorkflowService; +import org.imixs.workflow.exceptions.PluginException; +import org.imixs.workflow.util.XMLParser; + +import jakarta.faces.context.ExternalContext; +import jakarta.faces.context.FacesContext; + +/** + * The DataViewPOIHelper provides methods to update a excel template with the + * help of the Apache POI framework. + * + * + * @author rsoika + * @version 1.0 + */ + +public class DataViewPOIHelper { + private static Logger logger = Logger.getLogger(DataViewPOIHelper.class.getName()); + public static final String ERROR_CONFIG = "CONFIG_ERROR"; + + /** + * Helper method to initialize a file download + * + * @throws IOException + */ + public static void downloadExcelFile(FileData fileData) throws IOException { + + FacesContext facesContext = FacesContext.getCurrentInstance(); + ExternalContext externalContext = facesContext.getExternalContext(); + + externalContext.responseReset(); + externalContext.setResponseContentType("application/vnd.ms-excel"); + externalContext.setResponseContentLength(fileData.getContent().length); + externalContext.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileData.getName() + "\""); + + OutputStream output = externalContext.getResponseOutputStream(); + + // Now you can write the InputStream of the file to the above OutputStream the + // usual way. + output.write(fileData.getContent()); + + facesContext.responseComplete(); // Important! Otherwise Faces will attempt to render the response which + // obviously will fail since it's already written with a file and closed. + } + + /** + * This helper method inserts a row for each invoice + * + * @throws PluginException + */ + public static void insertDataRows(List dataset, String referenceCell, + List viewItemDefinitions, + FileData fileData) throws PluginException { + // load XSSFWorkbook + try (InputStream imputStream = new ByteArrayInputStream(fileData.getContent())) { + XSSFWorkbook doc = new XSSFWorkbook(imputStream); + // NOTE: we only take the first sheet ! + XSSFSheet sheet = doc.getSheetAt(0); + + CellReference cr = new CellReference(referenceCell); + XSSFRow referenceRow = sheet.getRow(cr.getRow()); + int referenceRowPos = referenceRow.getRowNum() + 1; + int rowPos = referenceRowPos; + // int lastRow = sheet.getLastRowNum(); + int lastRow = 999; + logger.finest("Last rownum=" + lastRow); + sheet.shiftRows(rowPos, lastRow, dataset.size(), true, true); + + for (ItemCollection workitem : dataset) { + logger.finest("......copy row..."); + + // now create a new line.. + XSSFRow row = sheet.createRow(rowPos); + row.copyRowFrom(referenceRow, new CellCopyPolicy()); + // insert values + int cellNum = 0; + for (ItemCollection itemDef : viewItemDefinitions) { + String type = itemDef.getItemValueString("item.type"); + String name = itemDef.getItemValueString("item.name"); + switch (type) { + case "xs:double": + row.getCell(cellNum).setCellValue(workitem.getItemValueDouble(name)); + break; + case "xs:float": + row.getCell(cellNum).setCellValue(workitem.getItemValueFloat(name)); + break; + case "xs:int": + row.getCell(cellNum).setCellValue(workitem.getItemValueInteger(name)); + break; + case "xs:date": + row.getCell(cellNum).setCellValue(workitem.getItemValueDate(name)); + break; + + default: + row.getCell(cellNum).setCellValue(workitem.getItemValueString(name)); + } + cellNum++; + } + + rowPos++; + } + // delete reference row + sheet.shiftRows(referenceRowPos, lastRow + dataset.size(), -1, true, true); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + // write back the file + doc.write(byteArrayOutputStream); + doc.close(); + byte[] newContent = byteArrayOutputStream.toByteArray(); + fileData.setContent(newContent); + + } catch (IOException e) { + throw new PluginException(DataViewPOIHelper.class.getSimpleName(), ERROR_CONFIG, + "failed to update excel export: " + e.getMessage()); + } + } + + /** + * This helper method applies the POI update defintiions a row for each invoice + * + * @throws PluginException + */ + public static void poiUpdate(ItemCollection dataDefinition, FileData fileData, + ItemCollection poiConfig, WorkflowService workflowService) throws PluginException { + + // update $modified for Now function + dataDefinition.setItemValue("$modified", new Date()); + + if (poiConfig == null || !poiConfig.hasItem("findreplace")) { + // no config found + return; + } + List replaceDevList = poiConfig.getItemValue("findreplace"); + String eval = poiConfig.getItemValueString("eval"); + + // load XSSFWorkbook + try (InputStream imputStream = new ByteArrayInputStream(fileData.getContent())) { + XSSFWorkbook workbook = new XSSFWorkbook(imputStream); + // NOTE: we only take the first sheet ! + XSSFSheet sheet = workbook.getSheetAt(0); + + updateXSSFWorkbook(workbook, dataDefinition, replaceDevList, workflowService); + + // Update Eval list + if (eval != null && !eval.isEmpty()) { + // iterate over all cells to be evaluated + String[] cellPositions = eval.split(";"); + for (String cellPos : cellPositions) { + evalXSSFSheet(workbook, sheet, cellPos); + } + logger.fine("formula evaluation completed"); + } + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + // write back the file + workbook.write(byteArrayOutputStream); + workbook.close(); + byte[] newContent = byteArrayOutputStream.toByteArray(); + fileData.setContent(newContent); + + } catch (IOException e) { + throw new PluginException(DataViewPOIHelper.class.getSimpleName(), ERROR_CONFIG, + "failed to update excel export: " + e.getMessage()); + } + } + + /** + * Returns a Cell by name or an optional absolute cell postion + *

+ * Examples for refs are 'A1', 'AB3', 'MyCell' where 'MyCell' is a named cell. + *

+ * + */ + public static XSSFCell getCellByRef(XSSFWorkbook doc, XSSFSheet sheet, String cellReference) { + XSSFCell cell = null; + + // first we test if the cellName is a named cell + Name aNamedCell = doc.getName(cellReference); + if (aNamedCell != null) { + // yes its a named cell so we need to get the referrer Formula + logger.finest("...resolving named cell = " + aNamedCell.getNameName()); + cellReference = aNamedCell.getRefersToFormula(); + // now we can find the cell by its ref + } + + CellReference cr = new CellReference(cellReference); + XSSFRow row = sheet.getRow(cr.getRow()); + if (row == null) { + logger.severe("Unable to resolve cell ref '" + cellReference + "'!"); + return null; + } + cell = row.getCell(cr.getCol()); + return cell; + } + + /** + * This method updates the XSSFWorkbook document. The method can be overwritten + * by subclasses to add additional logic + * + * @param workbook + * @param workitem + * @param replaceDevList + * @throws PluginException + */ + public static void updateXSSFWorkbook(XSSFWorkbook workbook, ItemCollection workitem, List replaceDevList, + WorkflowService workflowService) + throws PluginException { + + logger.fine("XSSFWorkbook loaded"); + // NOTE: we only take the first sheet ! + XSSFSheet sheet = workbook.getSheetAt(0); + + for (String entityDev : replaceDevList) { + ItemCollection entityData = XMLParser.parseItemStructure(entityDev); + + if (entityData != null) { + String find = entityData.getItemValueString("find"); + String replace = entityData.getItemValueString("replace"); + replace = workflowService.adaptText(replace, workitem); + // optional itename + String itemname = entityData.getItemValueString("itemname"); + + // replace with item value? + if (!itemname.isEmpty()) { + List valueList = workitem.getItemValue(itemname); + if (valueList.size() > 0) { + // provide the first value only + replaceXSSFSheetItemValue(workbook, sheet, find, valueList.get(0)); + } + } else { + replaceXSSFSheetStringValue(workbook, sheet, find, replace); + } + + } + } + } + + /** + * Helper method replaces a given cell of a XSSFSheet with a typed item value + * + * @throws PluginException + */ + public static void replaceXSSFSheetItemValue(XSSFWorkbook doc, XSSFSheet sheet, String find, Object itemValue) + throws PluginException { + logger.finest("update cell " + find); + XSSFCell cell = getCellByRef(doc, sheet, find); + if (cell == null) { + logger.warning("Cell " + find + " not found."); + return; + } + if (itemValue instanceof Date) { + cell.setCellValue((Date) itemValue); + } else if (itemValue instanceof Double) { + cell.setCellValue((Double) itemValue); + } else { + // default to text + cell.setCellValue(itemValue.toString()); + } + } + + /** + * Helper method replaces a given cell of a XSSFSheet with a string value + * + * @throws PluginException + */ + private static void replaceXSSFSheetStringValue(XSSFWorkbook doc, XSSFSheet sheet, String find, String replace) + throws PluginException { + logger.finest("update cell " + find); + XSSFCell cell = getCellByRef(doc, sheet, find); + if (cell == null) { + logger.warning("Cell " + find + " not found."); + return; + } + try { + // we try to set first as float value if possible + float f = Float.parseFloat(replace); + cell.setCellValue(f); + } catch (NumberFormatException e) { + // set value as string + cell.setCellValue(replace); + } + } + + /** + * Evaluates a given list of cells in a given XSWorkbook + * + * @param doc + * @param sheet + * @param cell + * @throws PluginException + */ + public static void evalXSSFSheet(XSSFWorkbook doc, XSSFSheet sheet, String cell) throws PluginException { + FormulaEvaluator evaluator = doc.getCreationHelper().createFormulaEvaluator(); + XSSFCell c = getCellByRef(doc, sheet, cell); + if (c == null) { + logger.warning("Cell " + cell + " not found."); + return; + } + if (c.getCellType() == CellType.FORMULA) { + logger.finest("...eval cell " + cell); + try { + CellType evalResult = evaluator.evaluateFormulaCell(c); + if (evalResult == CellType.ERROR) { + logger.warning("...unable to evaluate cell " + cell); + } + } catch (Exception poie) { + logger.warning("...failed to evaluate cell " + cell + " : " + poie.getMessage()); + } + } + } +} diff --git a/office-alexander-logistics-app/src/main/webapp/pages/admin/dataViewDefinition.xhtml b/office-alexander-logistics-app/src/main/webapp/pages/admin/dataViewDefinition.xhtml index 8e003be..71c0333 100644 --- a/office-alexander-logistics-app/src/main/webapp/pages/admin/dataViewDefinition.xhtml +++ b/office-alexander-logistics-app/src/main/webapp/pages/admin/dataViewDefinition.xhtml @@ -10,10 +10,12 @@ + + - +

@@ -55,11 +57,8 @@

- Name: * - - + Name: *
-
@@ -88,6 +87,28 @@
+
+
+
+ Sort By: +
+
+ +
+
+
+
+ Reverse Order: +
+
+ + + +
+
+
@@ -102,7 +123,8 @@ values. Each item of the attribute list can define an optional label, xs datatype and format.
- Convert - xml datatypes: xs:string (default), xs:decimal, xs:date, + Convert - xml datatypes: xs:string (default), xs:int, xs:double, xs:float, + xs:date, xs:dateTime, xs:anyURI @@ -212,7 +234,7 @@
-
+

Template Test View

-
-
-
- POI Update: -
-
- -
-
+

+ + The 'Target Name' defines the name of the file download. The 'Reference Cell + marks the row to insert the dataset. POI update Definitions are optional. -

+

+
+
+ Target Name: +
+
+ +
+
+
+
+ Reference Cell: +
+
+ +
+
+
+
+
+
+ POI Definition: +
+
+ +
+
+ + +
+
+ Source File : +
+
-
+ +
+ + +
@@ -277,6 +333,7 @@ } + /*]]>*/ diff --git a/office-alexander-logistics-app/src/main/webapp/pages/dataviews/data.xhtml b/office-alexander-logistics-app/src/main/webapp/pages/dataviews/data.xhtml index 7ede73d..be34bf4 100644 --- a/office-alexander-logistics-app/src/main/webapp/pages/dataviews/data.xhtml +++ b/office-alexander-logistics-app/src/main/webapp/pages/dataviews/data.xhtml @@ -9,14 +9,39 @@ + + + + - - - - +

@@ -50,14 +75,16 @@ -
+
- + + +
@@ -133,10 +160,17 @@ #{columnDef.item['item.label']} + pattern="#{message.datePatternShort}" /> + + + #{columnDef.item['item.label']} + + + @@ -146,30 +180,11 @@
+
- - - - - diff --git a/templates/ausgangsrechnungen-export_template.xlsx b/templates/dataviews/offene_rechnungen_template.xlsx similarity index 66% rename from templates/ausgangsrechnungen-export_template.xlsx rename to templates/dataviews/offene_rechnungen_template.xlsx index bb61fd352cdb0bd6171c30686804f5193da593c6..e4c0c00176310b3660a763aaf1ef7a190ec592b6 100644 GIT binary patch delta 11943 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