From 52bd55c569e4d0455631a897e809dc792d26c6d6 Mon Sep 17 00:00:00 2001 From: Nicolas Malin Date: Tue, 27 Jan 2026 14:34:58 +0100 Subject: [PATCH] Improved: Add condition-date on entity-condition element (OFBIZ-13349) On screen or form actions, I propose to add a condition-date element on to directly add historic filter. Currently element entity-condition can have a boolean attribute filter-by-date that filter your entity list with fromDate/thruDate on now date only. If you need to navigate on history or use some other date fields, you can't use it and need to add a complex condition by hand With condition-date you can select the date value to use and date fields for the filter. example : Complex : Simple : Default : --- .../entity/finder/ByConditionFinder.java | 19 ++-- .../ofbiz/entity/finder/EntityFinderUtil.java | 90 +++++++++++++++++++ framework/widget/dtd/widget-common.xsd | 31 +++++++ .../widget/model/AbstractModelAction.java | 37 +++----- .../ofbiz/widget/model/ModelFormAction.java | 15 ++-- 5 files changed, 154 insertions(+), 38 deletions(-) diff --git a/framework/entity/src/main/java/org/apache/ofbiz/entity/finder/ByConditionFinder.java b/framework/entity/src/main/java/org/apache/ofbiz/entity/finder/ByConditionFinder.java index dcc37d37541..eab01404be2 100644 --- a/framework/entity/src/main/java/org/apache/ofbiz/entity/finder/ByConditionFinder.java +++ b/framework/entity/src/main/java/org/apache/ofbiz/entity/finder/ByConditionFinder.java @@ -23,6 +23,7 @@ import org.apache.ofbiz.base.util.UtilXml; import org.apache.ofbiz.entity.condition.EntityCondition; import org.apache.ofbiz.entity.finder.EntityFinderUtil.Condition; +import org.apache.ofbiz.entity.finder.EntityFinderUtil.ConditionDate; import org.apache.ofbiz.entity.finder.EntityFinderUtil.ConditionExpr; import org.apache.ofbiz.entity.finder.EntityFinderUtil.ConditionList; import org.apache.ofbiz.entity.finder.EntityFinderUtil.ConditionObject; @@ -47,15 +48,15 @@ public ByConditionFinder(Element element) { // NOTE: the whereCondition can be null, ie (condition-expr | condition-list) is optional; if left out, means find all, // or with no condition in essense // process condition-expr | condition-list - Element conditionExprElement = UtilXml.firstChildElement(element, "condition-expr"); - Element conditionListElement = UtilXml.firstChildElement(element, "condition-list"); - Element conditionObjectElement = UtilXml.firstChildElement(element, "condition-object"); - if (conditionExprElement != null) { - this.whereCondition = new ConditionExpr(conditionExprElement); - } else if (conditionListElement != null) { - this.whereCondition = new ConditionList(conditionListElement); - } else if (conditionObjectElement != null) { - this.whereCondition = new ConditionObject(conditionObjectElement); + Element conditionElement = UtilXml.firstChildElement(element); + if (conditionElement != null) { + this.whereCondition = switch (UtilXml.getTagNameIgnorePrefix(conditionElement)) { + case "condition-expr" -> new ConditionExpr(conditionElement); + case "condition-date" -> new ConditionDate(conditionElement); + case "condition-list" -> new ConditionList(conditionElement); + case "condition-object" -> new ConditionObject(conditionElement); + default -> null; + }; } Element havingConditionListElement = UtilXml.firstChildElement(element, "having-condition-list"); diff --git a/framework/entity/src/main/java/org/apache/ofbiz/entity/finder/EntityFinderUtil.java b/framework/entity/src/main/java/org/apache/ofbiz/entity/finder/EntityFinderUtil.java index 121ad09e1a0..5ece4f7638f 100644 --- a/framework/entity/src/main/java/org/apache/ofbiz/entity/finder/EntityFinderUtil.java +++ b/framework/entity/src/main/java/org/apache/ofbiz/entity/finder/EntityFinderUtil.java @@ -18,6 +18,10 @@ *******************************************************************************/ package org.apache.ofbiz.entity.finder; +import java.sql.Timestamp; +import java.util.Locale; +import org.apache.ofbiz.base.util.GeneralException; +import org.apache.ofbiz.base.util.UtilDateTime; import static org.apache.ofbiz.base.util.UtilGenerics.cast; import java.io.Serializable; @@ -35,10 +39,12 @@ import org.apache.ofbiz.base.util.ObjectType; import org.apache.ofbiz.base.util.StringUtil; import org.apache.ofbiz.base.util.UtilGenerics; +import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.base.util.UtilXml; import org.apache.ofbiz.base.util.collections.FlexibleMapAccessor; import org.apache.ofbiz.base.util.string.FlexibleStringExpander; +import org.apache.ofbiz.entity.GenericEntity; import org.apache.ofbiz.entity.GenericEntityException; import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entity.condition.EntityComparisonOperator; @@ -47,6 +53,7 @@ import org.apache.ofbiz.entity.condition.EntityJoinOperator; import org.apache.ofbiz.entity.condition.EntityOperator; import org.apache.ofbiz.entity.model.ModelEntity; +import org.apache.ofbiz.entity.model.ModelField; import org.apache.ofbiz.entity.model.ModelFieldTypeReader; import org.apache.ofbiz.entity.util.EntityListIterator; import org.w3c.dom.Element; @@ -286,6 +293,9 @@ public ConditionList(Element conditionListElement) { case "condition-expr": conditionList.add(new ConditionExpr(subElement)); break; + case "condition-date": + conditionList.add(new ConditionDate(subElement)); + break; case "condition-list": conditionList.add(new ConditionList(subElement)); break; @@ -341,6 +351,86 @@ public EntityCondition createCondition(Map context, Mo } } + @SuppressWarnings("serial") + public static final class ConditionDate implements Condition { + private final FlexibleMapAccessor dateField; + private final List compareDateFields; + private final FlexibleStringExpander ignoreExdr; + private final boolean ignoreIfNull; + private final boolean ignoreIfEmpty; + + public ConditionDate(Element conditionDateElement) { + String fieldDateName = conditionDateElement.getAttribute("from-field"); + this.dateField = !fieldDateName.isEmpty() + ? FlexibleMapAccessor.getInstance(fieldDateName) + : null; + List collectedCompareDateFields = UtilXml.childElementList(conditionDateElement).stream() + .map(e -> FlexibleStringExpander.getInstance(e.getAttribute("field-name"))) + .toList(); + compareDateFields = !(collectedCompareDateFields.isEmpty() || collectedCompareDateFields.size() % 2 != 0) + ? collectedCompareDateFields + : List.of(FlexibleStringExpander.getInstance("fromDate"), + FlexibleStringExpander.getInstance("thruDate")); + this.ignoreIfNull = "true".equals(conditionDateElement.getAttribute("ignore-if-null")); + this.ignoreIfEmpty = "true".equals(conditionDateElement.getAttribute("ignore-if-empty")); + this.ignoreExdr = FlexibleStringExpander.getInstance(conditionDateElement.getAttribute("ignore")); + } + + @Override + public EntityCondition createCondition(Map context, ModelEntity modelEntity, ModelFieldTypeReader + modelFieldTypeReader) { + if ("true".equals(this.ignoreExdr.expandString(context))) { + return null; + } + Timestamp dateFieldValue = null; + ModelField dateFieldFromEntity = null; + if (this.dateField != null) { + dateFieldFromEntity = modelEntity.getField(dateField.getOriginalName()); + if (dateFieldFromEntity == null) { + Object valueFound = dateField.get(context); + if (valueFound != null) { + try { + dateFieldValue = (Timestamp) ObjectType.simpleTypeOrObjectConvert( + valueFound, "java.sql.Timestamp", "", (Locale) context.get("locale")); + } catch (GeneralException e) { + Debug.logWarning("Failed to convert value " + valueFound, MODULE); + } + } + } + } + if (this.ignoreIfNull && dateFieldValue == null) { + return null; + } + if (this.ignoreIfEmpty && ObjectType.isEmpty(dateFieldValue)) { + return null; + } + if (UtilValidate.isEmpty(dateFieldValue)) { + dateFieldValue = UtilDateTime.nowTimestamp(); + } + List conditionDates = UtilMisc.toList(); + for (int i = 0; i < compareDateFields.size() / 2; i++) { + String fromDateField = compareDateFields.get(i).expandString(context); + String thruDateField = compareDateFields.get(i + 1).expandString(context); + if (dateFieldFromEntity != null) { + ModelField fromDateModelField = modelEntity.getField(fromDateField); + ModelField thruDateModelField = modelEntity.getField(thruDateField); + conditionDates.add(EntityCondition.makeConditionWhere( + fromDateModelField.getColName() + " <= " + dateFieldFromEntity.getColName())); + conditionDates.add(EntityCondition.makeCondition(EntityOperator.OR, + EntityCondition.makeConditionWhere(thruDateModelField.getColName() + " >= " + dateFieldFromEntity.getColName()), + EntityCondition.makeCondition(thruDateField, EntityOperator.EQUALS, GenericEntity.NULL_FIELD))); + } else { + conditionDates.add(EntityCondition.makeCondition(fromDateField, EntityOperator.LESS_THAN_EQUAL_TO, dateFieldValue)); + conditionDates.add(EntityCondition.makeCondition(EntityOperator.OR, + EntityCondition.makeCondition(thruDateField, EntityOperator.GREATER_THAN_EQUAL_TO, dateFieldValue), + EntityCondition.makeCondition(thruDateField, EntityOperator.EQUALS, GenericEntity.NULL_FIELD))); + } + } + + return EntityCondition.makeCondition(conditionDates); + } + } + public interface OutputHandler extends Serializable { void handleOutput(EntityListIterator eli, Map context, FlexibleMapAccessor listAcsr); void handleOutput(List results, Map context, FlexibleMapAccessor listAcsr); diff --git a/framework/widget/dtd/widget-common.xsd b/framework/widget/dtd/widget-common.xsd index 6b524290406..b3331a94d2f 100644 --- a/framework/widget/dtd/widget-common.xsd +++ b/framework/widget/dtd/widget-common.xsd @@ -358,6 +358,7 @@ under the License. + @@ -375,6 +376,7 @@ under the License. + @@ -422,6 +424,35 @@ under the License. + + + + + + + + + field in context that contains the date to use for filter + + + + + + + + + Ignore the condition if flag is true. + Defaults to false. + + + + + + + + + + diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/AbstractModelAction.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/AbstractModelAction.java index c4e951623c5..1e9c4534732 100644 --- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/AbstractModelAction.java +++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/AbstractModelAction.java @@ -86,30 +86,19 @@ public abstract class AbstractModelAction implements Serializable, ModelAction { * @return A new ModelAction instance */ public static ModelAction newInstance(ModelWidget modelWidget, Element actionElement) { - String nodeName = UtilXml.getNodeNameIgnorePrefix(actionElement); - if ("set".equals(nodeName)) { - return new SetField(modelWidget, actionElement); - } else if ("property-map".equals(nodeName)) { - return new PropertyMap(modelWidget, actionElement); - } else if ("property-to-field".equals(nodeName)) { - return new PropertyToField(modelWidget, actionElement); - } else if ("script".equals(nodeName)) { - return new Script(modelWidget, actionElement); - } else if ("service".equals(nodeName)) { - return new Service(modelWidget, actionElement); - } else if ("entity-one".equals(nodeName)) { - return new EntityOne(modelWidget, actionElement); - } else if ("entity-and".equals(nodeName)) { - return new EntityAnd(modelWidget, actionElement); - } else if ("entity-condition".equals(nodeName)) { - return new EntityCondition(modelWidget, actionElement); - } else if ("get-related-one".equals(nodeName)) { - return new GetRelatedOne(modelWidget, actionElement); - } else if ("get-related".equals(nodeName)) { - return new GetRelated(modelWidget, actionElement); - } else { - throw new IllegalArgumentException("Action element not supported with name: " + actionElement.getNodeName()); - } + return switch (UtilXml.getNodeNameIgnorePrefix(actionElement)) { + case "set" -> new SetField(modelWidget, actionElement); + case "property-map" -> new PropertyMap(modelWidget, actionElement); + case "property-to-field" -> new PropertyToField(modelWidget, actionElement); + case "script" -> new Script(modelWidget, actionElement); + case "service" -> new Service(modelWidget, actionElement); + case "entity-one" -> new EntityOne(modelWidget, actionElement); + case "entity-and" -> new EntityAnd(modelWidget, actionElement); + case "entity-condition" -> new EntityCondition(modelWidget, actionElement); + case "get-related-one" -> new GetRelatedOne(modelWidget, actionElement); + case "get-related" -> new GetRelated(modelWidget, actionElement); + default -> throw new IllegalArgumentException("Action element not supported with name: " + actionElement.getNodeName()); + }; } public static List readSubActions(ModelWidget modelWidget, Element parentElement) { diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormAction.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormAction.java index cf7781063ee..1558b83d1a3 100644 --- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormAction.java +++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormAction.java @@ -49,10 +49,13 @@ public static List readSubActions(ModelForm modelForm, Element pare List actions = new ArrayList<>(actionElementList.size()); for (Element actionElement : UtilXml.childElementList(parentElement)) { String nodeName = actionElement.getLocalName(); - if ("service".equals(nodeName)) { + switch (nodeName) { + case "service": actions.add(new Service(modelForm, actionElement)); - } else if ("entity-and".equals(nodeName) || "entity-condition".equals(nodeName) - || "get-related".equals(nodeName)) { + break; + case "entity-and": + case "entity-condition": + case "get-related": if (!actionElement.hasAttribute("list")) { String listName = modelForm.getListName(); if (UtilValidate.isEmpty(listName)) { @@ -61,9 +64,11 @@ public static List readSubActions(ModelForm modelForm, Element pare actionElement.setAttribute("list", listName); } actions.add(AbstractModelAction.newInstance(modelForm, actionElement)); - } else if ("call-parent-actions".equals(nodeName)) { + break; + case "call-parent-actions": actions.add(new CallParentActions(modelForm, actionElement)); - } else { + break; + default: actions.add(AbstractModelAction.newInstance(modelForm, actionElement)); } }