neue Ansicht Businesspartner

This commit is contained in:
Ralph Soika 2026-05-04 16:33:57 +02:00
parent a65ae59950
commit c15efc106c
6 changed files with 1664 additions and 0 deletions

View file

@ -0,0 +1,156 @@
/*******************************************************************************
* 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.forms;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.imixs.workflow.dataview.DataViewService;
import org.imixs.workflow.exceptions.QueryException;
import org.imixs.workflow.faces.data.WorkflowController;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.Conversation;
import jakarta.enterprise.context.ConversationScoped;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.servlet.http.HttpServletRequest;
/**
* The DataViewFormSectionController provides methods to display a data view in
* as a
* custom form section
*
* @see pages/workitems/sections/dataview.xhtml
* @author rsoika
* @version 1.0
*/
@Named
@ConversationScoped
public class DataViewSectionController implements Serializable {
private static final long serialVersionUID = 1L;
public static final int MAX_SEARCH_RESULT = 1000;
public static Logger logger = Logger.getLogger(DataViewSectionController.class.getName());
@Inject
WorkflowController workflowController;
@Inject
private Conversation conversation;
@Inject
DataViewService dataViewService;
private Map<String, DataViewSectionDataSet> dataSets = null;
@PostConstruct
public void init() {
startConversation();
dataSets = new HashMap<>();
}
/**
* Starts a new conversation
*/
protected void startConversation() {
if (conversation.isTransient()) {
conversation.setTimeout(
((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest())
.getSession().getMaxInactiveInterval() * 1000);
conversation.begin();
logger.log(Level.FINEST, "......start new conversation, id={0}",
conversation.getId());
}
}
/**
* Returns a single value from a Option key/value list
*
* @param options - options
* @param key - option key
* @return option value
*/
public String getOptionValue(String options, String key) {
// Null checks
if (options == null || key == null || options.trim().isEmpty() || key.trim().isEmpty()) {
return null;
}
// Split options into key/value pairs (separated by semicolon)
String[] pairs = options.split(";");
for (String pair : pairs) {
// Split each pair into key and value (separated by equals sign)
String[] keyValue = pair.split("=", 2); // Limit to 2 in case value contains "="
if (keyValue.length == 2) {
String currentKey = keyValue[0].trim();
String currentValue = keyValue[1].trim();
// Check if the searched key was found
if (key.equals(currentKey)) {
return currentValue;
}
}
}
// Key not found
return null;
}
/**
* Initializes a new DataViewSectionDataSet based on the given options
* (parameter 'name').
*
* The expected format of the options string is:
* <p>
* "name=DATAVIEWNAME"
* <p>
* The method loads the corresponding dataView definition immediately and stores
* the dataSet in a local cache.
*
* @return view result
* @throws QueryException
*/
public DataViewSectionDataSet loadDataSet(String options) throws QueryException {
DataViewSectionDataSet dataSet = dataSets.get(options);
// Extract and set dataViewName from options
if (dataSet == null) {
String _dataViewName = getOptionValue(options, "name");
if (_dataViewName != null) {
logger.info("...build new dataSet by options: " + options);
dataSet = new DataViewSectionDataSet(_dataViewName, workflowController.getWorkitem(), dataViewService);
dataSets.put(options, dataSet);
}
}
return dataSet;
}
}

View file

@ -0,0 +1,260 @@
package org.imixs.workflow.office.forms;
import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import java.util.logging.Logger;
import org.imixs.workflow.FileData;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.dataview.DataViewController;
import org.imixs.workflow.dataview.DataViewExportEvent;
import org.imixs.workflow.dataview.DataViewPOIHelper;
import org.imixs.workflow.dataview.DataViewService;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.QueryException;
public class DataViewSectionDataSet implements Serializable {
private static final long serialVersionUID = 1L;
public static Logger logger = Logger.getLogger(DataViewSectionController.class.getName());
List<ItemCollection> data = null;
private ItemCollection dataViewDefinition;
private String dataViewName;
private List<ItemCollection> viewItemDefinitions = null;
private String query = null;
private boolean endOfList = false;
private long totalCount = 0;
private long totalPages = 0;
private String sortBy = null;
private boolean sortReverse = false;
private int pageSize = 10;
private int pageIndex = 0;
private DataViewService dataViewService;
private ItemCollection workitem;
public DataViewSectionDataSet(String dataViewName, ItemCollection workitem, DataViewService dataViewService) {
this.dataViewName = dataViewName;
this.workitem = workitem;
this.dataViewService = dataViewService;
dataViewDefinition = dataViewService.loadDataViewDefinition(dataViewName);
boolean debug = dataViewDefinition.getItemValueBoolean("debug");
if (debug) {
logger.info("resolve query by dataView '" + dataViewName + "'");
}
// preload the viewItem definitions
viewItemDefinitions = this.dataViewService.computeDataViewItemDefinitions(dataViewDefinition);
// resove query by dataView
query = this.dataViewService.parseQuery(dataViewDefinition, this.workitem);
loadData();
}
public void forward() {
if (!isEndOfList()) {
pageIndex++;
loadData();
}
}
public void back() {
if (pageIndex > 0) {
pageIndex--;
loadData();
}
}
/**
* load current page
*/
private void loadData() {
try {
data = dataViewService.getWorkflowService().getDocumentService().find(query, getPageSize(), getPageIndex(),
getSortBy(), isSortReverse());
// The end of a list is reached when the size is below or equal the
// pageSize. See issue #287
totalCount = dataViewService.getWorkflowService().getDocumentService().count(query);
totalPages = (long) Math.ceil((double) totalCount / pageSize);
if (data.size() < pageSize) {
setEndOfList(true);
} else {
// look ahead if we have more entries...
int iAhead = (getPageSize() * (getPageIndex() + 1)) + 1;
if (totalCount < iAhead) {
// there is no more data
setEndOfList(true);
} else {
setEndOfList(false);
}
}
} catch (QueryException e) {
logger.warning("Failed to load data: " + e.getMessage());
}
}
public List<ItemCollection> getData() {
return data;
}
public ItemCollection getDataViewDefinition() {
return dataViewDefinition;
}
public long getTotalCount() {
return totalCount;
}
public long getTotalPages() {
return totalPages;
}
public int getPageIndex() {
return pageIndex;
}
public void setPageIndex(int pageIndex) {
this.pageIndex = pageIndex;
}
public boolean isEndOfList() {
return endOfList;
}
public void setEndOfList(boolean endOfList) {
this.endOfList = endOfList;
}
public String getDataViewName() {
return dataViewName;
}
public List<ItemCollection> getViewItemDefinitions() {
return viewItemDefinitions;
}
/**
* returns the maximum size of a search result
*
* @return
*/
public int getPageSize() {
return pageSize;
}
/**
* set the maximum size of a search result
*
* @param searchCount
*/
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public String getSortBy() {
return sortBy;
}
public void setSortBy(String sortBy) {
this.sortBy = sortBy;
}
public boolean isSortReverse() {
return sortReverse;
}
public void setSortReverse(boolean sortReverse) {
this.sortReverse = sortReverse;
}
/**
* Exports data into a excel template processed by apache-poi. The method sends
* a DataViewExport event to allow clients to adapt the export process.
*
* @see DataViewExportEvent
*
* @throws PluginException
* @throws QueryException
*/
public String export() throws PluginException, QueryException {
// Build target filename
boolean debug = dataViewDefinition.getItemValueBoolean("debug");
// start export
if (debug) {
logger.info("│ ├── Query: " + query);
}
try {
// test if query exceeds max count
int totalCount = dataViewService.getWorkflowService().getDocumentService().count(query);
// start export
if (debug) {
logger.info("│ ├── Count: " + totalCount);
}
if (totalCount > DataViewService.MAX_ROWS) {
throw new PluginException(DataViewController.class.getSimpleName(), DataViewService.ERROR_CONFIG,
"Data can not be exported into Excel because dataset exceeds " + DataViewService.MAX_ROWS
+ " rows!");
}
String sortBy = dataViewDefinition.getItemValueString("sort.by");
if (sortBy.isEmpty()) {
sortBy = "$modified"; // default
}
List<ItemCollection> workitems = dataViewService.getWorkflowService().getDocumentService().find(query,
DataViewService.MAX_ROWS, 0, sortBy,
dataViewDefinition.getItemValueBoolean("sort.reverse"));
FileData fileDataExport = dataViewService.poiExport(workitems, dataViewDefinition, viewItemDefinitions);
// create a temp event
ItemCollection event = new ItemCollection().setItemValue("txtActivityResult",
dataViewDefinition.getItemValue("poi.update"));
ItemCollection poiConfig = dataViewService.getWorkflowService().evalWorkflowResult(event, "poi-update",
dataViewDefinition,
false);
// merge workitem fields (Workaround because custom forms did hard coded map to
// workflowController instead of workitem
DataViewPOIHelper.poiUpdate(workitem, fileDataExport, poiConfig, dataViewService.getWorkflowService());
if (debug) {
logger.info("├── POI Export completed!");
}
// See:
// https://stackoverflow.com/questions/9391838/how-to-provide-a-file-download-from-a-jsf-backing-bean
DataViewPOIHelper.downloadExcelFile(fileDataExport);
} catch (IOException | QueryException e) {
throw new PluginException(DataViewController.class.getSimpleName(), DataViewService.ERROR_CONFIG,
"Failed to generate Excel Export: " + e.getMessage());
}
// return "/pages/admin/excel_export_rechnungsausgang.jsf?faces-redirect=true";
return "";
}
/**
* Returns true if a poi export is defined
*
* @return
*/
public boolean hasPoiExport() {
if (dataViewDefinition != null && !dataViewDefinition.getItemValueString("poi.targetfilename").isBlank()) {
return true;
}
return false;
}
}

View file

@ -0,0 +1,105 @@
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core" xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:h="http://java.sun.com/jsf/html" xmlns:i="http://java.sun.com/jsf/composite/imixs"
xmlns:marty="http://java.sun.com/jsf/composite/marty">
<!-- preload view options -->
<h:panelGroup styleClass="imixs-view imixs-search" layout="block" id="dataview_result_id">
<ui:param name="dataViewDataSet" value="#{dataViewSectionController.loadDataSet(options)}" />
<ui:param name="searchPage"
value="#{(dataViewDataSet.getPageIndex()+1)}/#{(dataViewDataSet.getTotalPages())}" />
<table class="imixsdatatable imixs-orderitems">
<tr>
<ui:repeat value="#{dataViewDataSet.getViewItemDefinitions()}" var="columnDef">
<th>#{columnDef.item['item.label']}</th>
</ui:repeat>
</tr>
<ui:repeat value="#{dataViewDataSet.data}" var="record">
<tr>
<ui:repeat value="#{dataViewDataSet.getViewItemDefinitions()}" var="columnDef">
<td>
<ui:fragment rendered="#{columnDef.item['item.type'] eq 'xs:anyURI'}">
<h:link outcome="/pages/workitems/workitem.xhtml?faces-redirect=true"
styleClass="imixs-viewentry-main-link">
<h:outputText value="#{record.item[columnDef.item['item.name']]}" />
<f:param name="id" value="#{record.item['$uniqueid']}" />
</h:link>
</ui:fragment>
<ui:fragment rendered="#{columnDef.item['item.type'] eq 'xs:string'}">
<h:outputText value="#{record.item[columnDef.item['item.name']]}" />
</ui:fragment>
<ui:fragment rendered="#{columnDef.item['item.type'] eq 'xs:date'}"
styleClass="align-center">
<h:outputText value="#{record.item[columnDef.item['item.name']]}">
<f:convertDateTime timeZone="#{message.timeZone}" type="both"
pattern="#{message.datePatternShort}" />
</h:outputText>
</ui:fragment>
<ui:fragment rendered="#{columnDef.item['item.type'] eq 'xs:double'}"
styleClass="align-right">
<h:outputText value="#{record.item[columnDef.item['item.name']]}">
<f:convertNumber minFractionDigits="2" locale="de" />
</h:outputText>
</ui:fragment>
<ui:fragment rendered="#{columnDef.item['item.type'] eq 'xs:float'}"
styleClass="align-right">
<h:outputText value="#{record.item[columnDef.item['item.name']]}">
<f:convertNumber minFractionDigits="2" locale="de" />
</h:outputText>
</ui:fragment>
<ui:fragment rendered="#{columnDef.item['item.type'] eq 'xs:dateTime'}">
<f:facet name="header">#{columnDef.item['item.label']}</f:facet>
<h:outputText value="#{record.item[columnDef.item['item.name']]}">
<f:convertDateTime timeZone="#{message.timeZone}" type="both"
pattern="#{message.dateTimePatternShort}" />
</h:outputText>
</ui:fragment>
</td>
</ui:repeat>
</tr>
</ui:repeat>
</table>
<!-- navigation -->
<div
style="background-color: #f9f9f9; font-size:0.85rem; border-bottom: 1px solid var(--input-border);border-left: 1px solid var(--input-border);border-right: 1px solid var(--input-border); height: 30px; display: flex; justify-content: space-between; align-items: center;padding: 5px 10px;">
<span style="display: flex; align-items: center;gap: 10px;">
<h:commandLink title="Excel Export" rendered="false" actionListener="#{dataGroupController.export()}"
onclick="handleSubmit(this)">
<i class="fa-solid fa-file-csv"></i>
</h:commandLink>
<h:commandLink title="Excel Export" rendered="#{dataViewDataSet.hasPoiExport()}"
actionListener="#{dataViewDataSet.export()}" onclick="handleSubmit(this)">
<span class="typcn typcn-vendor-microsoft"></span>
</h:commandLink>
</span>
<span style="display: flex; align-items: center; gap: 10px;">
<h:outputText value=" #{(dataViewDataSet.pageIndex+1)} / #{dataViewDataSet.totalPages}" />
<h:commandLink value="" action="#{dataViewDataSet.back()}">
<f:ajax render="dataview_result_id" />
<i class="fa-solid fa-backward" style=" #{(dataViewDataSet.pageIndex == 0)?'opacity:0.5':''}"></i>
</h:commandLink>
<h:commandLink value="" action="#{dataViewDataSet.forward()}">
<f:ajax render="dataview_result_id" />
<i class="fa-solid fa-forward" style=" #{(dataViewDataSet.endOfList)?'opacity:0.5':''}"></i>
</h:commandLink>
</span>
</div>
</h:panelGroup>
<script type="text/javascript">
/*<![CDATA[*/
function handleSubmit(event) {
// remove ajax loader 1sec after trigger excel export
setTimeout(function () {
document.body.classList.remove('loading');
}, 2000);
}
/*]]>*/
</script>
</ui:composition>

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff