This is an automated email from the ASF dual-hosted git repository.
nmalin pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
The following commit(s) were added to refs/heads/trunk by this push:
new fcd2c0c Improved: Estimated shipping cost resolution with breaks on
price and quantity (OFBIZ-6988)
fcd2c0c is described below
commit fcd2c0cec8d78dad16cebe349b7d8b573894028a
Author: Nicolas Malin <[email protected]>
AuthorDate: Wed Sep 15 11:45:04 2021 +0200
Improved: Estimated shipping cost resolution with breaks on price and
quantity (OFBIZ-6988)
On the service calcShipmentCostEstimate, each estimated shipment cost is
analysed to resolve which ones should be applied on the order.
During the breakQtys block analysis, OFBiz checks if the estimate match the
quantity with their breaks and validates if one is good.
If a rule contains multiple break type (ex: weight and price) this will be
ignored or use as partial, only one break would be analysed.
To solve it we extend the analyse on the break type combinaison.
After a global code review, we reformat the function to down less code
quantity for help the reader on:
* use an Util function to resolve BigDecimal values
* dedicate function to resolve the shipping address
* dedicate function to validate a break type
* move multiple loop to stream
Thanks to Gil Portenseigne and Jacques Leroux for their look
---
.../apache/ofbiz/shipment/ShipmentCostTests.groovy | 378 +++++++++++++++++++++
.../ofbiz/shipment/shipment/ShipmentServices.java | 366 ++++++++------------
applications/product/testdef/FacilityTest.xml | 5 +
.../product/testdef/data/ShipmentCostTestData.xml | 108 ++++++
.../org/apache/ofbiz/base/util/UtilNumber.java | 24 ++
5 files changed, 651 insertions(+), 230 deletions(-)
diff --git
a/applications/product/src/main/groovy/org/apache/ofbiz/shipment/ShipmentCostTests.groovy
b/applications/product/src/main/groovy/org/apache/ofbiz/shipment/ShipmentCostTests.groovy
new file mode 100644
index 0000000..66cb97f
--- /dev/null
+++
b/applications/product/src/main/groovy/org/apache/ofbiz/shipment/ShipmentCostTests.groovy
@@ -0,0 +1,378 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+
*******************************************************************************/
+package org.apache.ofbiz.shipment
+
+import org.apache.ofbiz.service.ServiceUtil
+import org.apache.ofbiz.service.testtools.OFBizTestCase
+
+class ShipmentCostTests extends OFBizTestCase {
+ ShipmentCostTests(String name) {
+ super(name)
+ }
+
+ /**
+ * ShipmentCostEstimates from ShipmentCostTestData.xml
+ *
+ | Method | wghBr | qtyBr | prBr | Party | Carrier | flat | itemFlat
| percent |
+ | ------ | ----- | ----- | ---- | -------- | --------- | ---- | --------
| ------- |
+ | ROAD | | | | | | 10 | 0
| 0 |
+ | AIR | | | | | | 0 | 0
| 4 |
+ | LOCAL | | | | | | 10 | 0
| 0 |
+ | ROAD | W1 | | | | UPS_BREAK | 9 | 0
| 0 |
+ | ROAD | W2 | | | | UPS_BREAK | 10 | 0
| 0 |
+ | ROAD | W3 | | | | UPS_BREAK | 11 | 0
| 0 |
+ | AIR | | Q1 | | | UPS_BREAK | 12 | 0
| 0 |
+ | AIR | | Q2 | | | UPS_BREAK | 13 | 0
| 0 |
+ | AIR | | Q3 | | | UPS_BREAK | 14 | 0
| 0 |
+ | LOCAL | | | P1 | | UPS_BREAK | 15 | 0
| 0 |
+ | LOCAL | | | P2 | | UPS_BREAK | 16 | 0
| 0 |
+ | LOCAL | | | P3 | | UPS_BREAK | 17 | 0
| 0 |
+ | LOCAL | W2 | | P2 | | UPS_BREAK | 18 | 0
| 0 |
+ | LOCAL | W2 | | P3 | | UPS_BREAK | 19 | 0
| 0 |
+ | LOCAL | W2 | | P3 | | UPS_BREAK | 19 | 0
| 0 |
+ | LOCAL | W3 | | P2 | | UPS_BREAK | 20 | 0
| 0 |
+ | ROAD | | | | RECEIVER | UPS_MULTI | 1 | 0
| 0 |
+ | ROAD | | | P2 | RECEIVER | UPS_MULTI | 2 | 0
| 0 |
+ | ROAD | | | | | UPS_MULTI | 3 | 0
| 0 |
+
+ With break :
+
+ | id | BreakType | from | to |
+ | --- | --------- | ---- | ----- |
+ | W1 | Weight | 0 | 99 |
+ | W2 | Weight | 100 | 999 |
+ | W3 | Weight | 1000 | 0 |
+ | P1 | Price | 0 | 99 |
+ | P2 | Price | 100 | 999 |
+ | P3 | Price | 1000 | 99999 |
+ | Q1 | Quantity | 0 | 100 |
+ | Q2 | Quantity | 100 | 1000 |
+ | Q3 | Quantity | 1000 | 0 |
+
+ */
+
+ void testCalculateSimpleShipmentCostFlatValue() {
+ Map serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'ROAD',
+ carrierPartyId : 'UPS_SIMPLE',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 10 as BigDecimal,
+ shippableTotal : 10 as BigDecimal,
+ userLogin : userLogin
+ ]
+ Map resultMap = dispatcher.runSync('calcShipmentCostEstimate',
serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 10d, resultMap.shippingEstimateAmount as Double
+ }
+
+ void testCalculateSimpleShipmentCostPercentValue() {
+ Map serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'AIR',
+ carrierPartyId : 'UPS_SIMPLE',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 10 as BigDecimal,
+ shippableTotal : 10 as BigDecimal,
+ userLogin : userLogin
+ ]
+ Map resultMap = dispatcher.runSync('calcShipmentCostEstimate',
serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 0.4d, resultMap.shippingEstimateAmount as Double
+ }
+
+ void testCalculateWeightBreakShipmentCostFlatValue() {
+ Map serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'ROAD',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 10 as BigDecimal,
+ shippableTotal : 10 as BigDecimal,
+ userLogin : userLogin
+ ]
+
+ Map resultMap = dispatcher.runSync('calcShipmentCostEstimate',
serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 9d, resultMap.shippingEstimateAmount as Double
+
+ serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'ROAD',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 100 as BigDecimal,
+ shippableTotal : 10 as BigDecimal,
+ userLogin : userLogin
+ ]
+
+ resultMap = dispatcher.runSync('calcShipmentCostEstimate', serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 10d, resultMap.shippingEstimateAmount as Double
+
+ serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'ROAD',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 1000 as BigDecimal,
+ shippableTotal : 10 as BigDecimal,
+ userLogin : userLogin
+ ]
+ resultMap = dispatcher.runSync('calcShipmentCostEstimate', serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 11d, resultMap.shippingEstimateAmount as Double
+ }
+
+ void testCalculateQuantityBreakShipmentCostFlatValue() {
+ Map serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'AIR',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 10 as BigDecimal,
+ shippableTotal : 10 as BigDecimal,
+ userLogin : userLogin
+ ]
+ Map resultMap = dispatcher.runSync('calcShipmentCostEstimate',
serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 12d, resultMap.shippingEstimateAmount as Double
+
+ serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'AIR',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 100 as BigDecimal,
+ shippableWeight : 10 as BigDecimal,
+ shippableTotal : 10 as BigDecimal,
+ userLogin : userLogin
+ ]
+ resultMap = dispatcher.runSync('calcShipmentCostEstimate', serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 13d, resultMap.shippingEstimateAmount as Double
+
+ serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'AIR',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 1000 as BigDecimal,
+ shippableWeight : 10 as BigDecimal,
+ shippableTotal : 10 as BigDecimal,
+ userLogin : userLogin
+ ]
+ resultMap = dispatcher.runSync('calcShipmentCostEstimate', serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 14d, resultMap.shippingEstimateAmount as Double
+ }
+
+ void testCalculatePriceBreakShipmentCostFlatValue() {
+ Map serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'LOCAL_DELIVERY',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 10 as BigDecimal,
+ shippableTotal : 10 as BigDecimal,
+ userLogin : userLogin
+ ]
+ Map resultMap = dispatcher.runSync('calcShipmentCostEstimate',
serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 15d, resultMap.shippingEstimateAmount as Double
+
+ serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'LOCAL_DELIVERY',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 10 as BigDecimal,
+ shippableTotal : 100 as BigDecimal,
+ userLogin : userLogin
+ ]
+ resultMap = dispatcher.runSync('calcShipmentCostEstimate', serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 16d, resultMap.shippingEstimateAmount as Double
+
+ serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'LOCAL_DELIVERY',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 10 as BigDecimal,
+ shippableTotal : 1000 as BigDecimal,
+ userLogin : userLogin
+ ]
+ resultMap = dispatcher.runSync('calcShipmentCostEstimate', serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 17d, resultMap.shippingEstimateAmount as Double
+ }
+
+ void testCalculatePriceAndWeightBreakShipmentCostFlatValue() {
+ Map serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'LOCAL_DELIVERY',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 100 as BigDecimal,
+ shippableTotal : 100 as BigDecimal,
+ userLogin : userLogin
+ ]
+ Map resultMap = dispatcher.runSync('calcShipmentCostEstimate',
serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 18d, resultMap.shippingEstimateAmount as Double
+
+ serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'LOCAL_DELIVERY',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 100 as BigDecimal,
+ shippableTotal : 1000 as BigDecimal,
+ userLogin : userLogin
+ ]
+ resultMap = dispatcher.runSync('calcShipmentCostEstimate', serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 19d, resultMap.shippingEstimateAmount as Double
+
+ serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'LOCAL_DELIVERY',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 1000 as BigDecimal,
+ shippableTotal : 100 as BigDecimal,
+ userLogin : userLogin
+ ]
+ resultMap = dispatcher.runSync('calcShipmentCostEstimate', serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 20d, resultMap.shippingEstimateAmount as Double
+
+ serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'LOCAL_DELIVERY',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 1000 as BigDecimal,
+ shippableTotal : 1000 as BigDecimal,
+ userLogin : userLogin
+ ]
+ resultMap = dispatcher.runSync('calcShipmentCostEstimate', serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 21d, resultMap.shippingEstimateAmount as Double
+ }
+
+ void testPriceBreakOverRangeAndFailedShipmentCost() {
+ Map serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'LOCAL_DELIVERY',
+ carrierPartyId : 'UPS_BREAK',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 100 as BigDecimal,
+ shippableTotal : 100000 as BigDecimal,
+ userLogin : userLogin
+ ]
+ Map resultMap = dispatcher.runSync('calcShipmentCostEstimate',
serviceCtx)
+ assert ServiceUtil.isFailure(resultMap)
+ }
+
+ void testCalculateMultipleWithPartyShipmentCostFlatValue() {
+ Map serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'ROAD',
+ carrierPartyId : 'UPS_MULTI',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ partyId : 'RECEIVER',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 10 as BigDecimal,
+ shippableTotal : 10 as BigDecimal,
+ userLogin : userLogin
+ ]
+ Map resultMap = dispatcher.runSync('calcShipmentCostEstimate',
serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 1d, resultMap.shippingEstimateAmount as Double
+ }
+
+ void testCalculateMultipleWithBreakShipmentCostFlatValue() {
+ Map serviceCtx = [
+ shippableItemInfo : [[:]],
+ shippingCountryCode : 'USA',
+ shipmentMethodTypeId: 'ROAD',
+ carrierPartyId : 'UPS_MULTI',
+ carrierRoleTypeId : 'CARRIER',
+ productStoreId : 'ShipCost',
+ partyId : 'RECEIVER',
+ shippableQuantity : 10 as BigDecimal,
+ shippableWeight : 10 as BigDecimal,
+ shippableTotal : 100 as BigDecimal,
+ userLogin : userLogin
+ ]
+ Map resultMap = dispatcher.runSync('calcShipmentCostEstimate',
serviceCtx)
+ assert ServiceUtil.isSuccess(resultMap)
+ assertEquals 2d, resultMap.shippingEstimateAmount as Double
+ }
+}
diff --git
a/applications/product/src/main/java/org/apache/ofbiz/shipment/shipment/ShipmentServices.java
b/applications/product/src/main/java/org/apache/ofbiz/shipment/shipment/ShipmentServices.java
index cc752ee..c5c0174 100644
---
a/applications/product/src/main/java/org/apache/ofbiz/shipment/shipment/ShipmentServices.java
+++
b/applications/product/src/main/java/org/apache/ofbiz/shipment/shipment/ShipmentServices.java
@@ -20,7 +20,6 @@ package org.apache.ofbiz.shipment.shipment;
import java.math.BigDecimal;
import java.math.RoundingMode;
-import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -29,6 +28,7 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import java.util.stream.Collectors;
import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.UtilGenerics;
import org.apache.ofbiz.base.util.UtilMisc;
@@ -39,8 +39,6 @@ import org.apache.ofbiz.common.geo.GeoWorker;
import org.apache.ofbiz.entity.Delegator;
import org.apache.ofbiz.entity.GenericEntityException;
import org.apache.ofbiz.entity.GenericValue;
-import org.apache.ofbiz.entity.condition.EntityCondition;
-import org.apache.ofbiz.entity.condition.EntityOperator;
import org.apache.ofbiz.entity.util.EntityListIterator;
import org.apache.ofbiz.entity.util.EntityQuery;
import org.apache.ofbiz.entity.util.EntityUtil;
@@ -115,15 +113,15 @@ public class ShipmentServices {
estimate.set("priceUomId", context.get("puom"));
storeAll.add(estimate);
- if (!applyQuantityBreak(context, result, storeAll, delegator,
estimate, "w", "weight", "Weight")) {
+ if (!applyQuantityBreak(context, result, storeAll, delegator,
estimate, "weight")) {
return result;
}
- if (!applyQuantityBreak(context, result, storeAll, delegator,
estimate, "q", "quantity", "Quantity")) {
+ if (!applyQuantityBreak(context, result, storeAll, delegator,
estimate, "quantity")) {
return result;
}
- if (!applyQuantityBreak(context, result, storeAll, delegator,
estimate, "p", "price", "Price")) {
+ if (!applyQuantityBreak(context, result, storeAll, delegator,
estimate, "price")) {
return result;
}
@@ -160,7 +158,8 @@ public class ShipmentServices {
}
private static boolean applyQuantityBreak(Map<String, ? extends Object>
context, Map<String, Object> result, List<GenericValue> storeAll,
- Delegator delegator, GenericValue estimate, String prefix, String
breakType, String breakTypeString) {
+ Delegator delegator, GenericValue estimate, String breakType) {
+ String prefix = breakType.substring(0, 1);
BigDecimal min = (BigDecimal) context.get(prefix + "min");
BigDecimal max = (BigDecimal) context.get(prefix + "max");
if (min != null || max != null) {
@@ -168,29 +167,29 @@ public class ShipmentServices {
if (min.compareTo(max) <= 0 || max.compareTo(BigDecimal.ZERO)
== 0) {
try {
String newSeqId =
delegator.getNextSeqId("QuantityBreak");
- GenericValue weightBreak =
delegator.makeValue("QuantityBreak");
- weightBreak.set("quantityBreakId", newSeqId);
- weightBreak.set("quantityBreakTypeId", "SHIP_" +
breakType.toUpperCase(Locale.getDefault()));
- weightBreak.set("fromQuantity", min);
- weightBreak.set("thruQuantity", max);
+ GenericValue quantityBreak =
delegator.makeValue("QuantityBreak",
+ "quantityBreakId", newSeqId,
+ "quantityBreakTypeId", "SHIP_" +
breakType.toUpperCase(Locale.getDefault()),
+ "fromQuantity", min,
+ "thruQuantity", max);
estimate.set(breakType + "BreakId", newSeqId);
estimate.set(breakType + "UnitPrice",
context.get(prefix + "price"));
if (context.containsKey(prefix + "uom")) {
estimate.set(breakType + "UomId",
context.get(prefix + "uom"));
}
- storeAll.add(0, weightBreak);
+ storeAll.add(0, quantityBreak);
} catch (Exception e) {
Debug.logError(e, MODULE);
}
} else {
result.put(ModelService.RESPONSE_MESSAGE,
ModelService.RESPOND_ERROR);
- result.put(ModelService.ERROR_MESSAGE, "Max " +
breakTypeString
- + " must not be less than Min " + breakTypeString
+ ".");
+ result.put(ModelService.ERROR_MESSAGE, "Max " + breakType
+ + " must not be less than Min " + breakType + ".");
return false;
}
} else {
result.put(ModelService.RESPONSE_MESSAGE,
ModelService.RESPOND_ERROR);
- result.put(ModelService.ERROR_MESSAGE, breakTypeString + "
Span Requires BOTH Fields.");
+ result.put(ModelService.ERROR_MESSAGE, breakType + " Span
Requires BOTH Fields.");
return false;
}
}
@@ -198,8 +197,9 @@ public class ShipmentServices {
}
// ShippingEstimate Calc Service
- public static Map<String, Object> calcShipmentCostEstimate(DispatchContext
dctx, Map<String, ? extends Object> context) {
+ public static Map<String, Object> calcShipmentCostEstimate(DispatchContext
dctx, Map<String, Object> context) {
Delegator delegator = dctx.getDelegator();
+ Locale locale = (Locale) context.get("locale");
// prepare the data
String productStoreShipMethId = (String)
context.get("productStoreShipMethId");
@@ -212,52 +212,37 @@ public class ShipmentServices {
String shippingCountryCode = (String)
context.get("shippingCountryCode");
List<Map<String, Object>> shippableItemInfo =
UtilGenerics.cast(context.get("shippableItemInfo"));
- BigDecimal shippableTotal = (BigDecimal) context.get("shippableTotal");
- BigDecimal shippableQuantity = (BigDecimal)
context.get("shippableQuantity");
- BigDecimal shippableWeight = (BigDecimal)
context.get("shippableWeight");
- BigDecimal initialEstimateAmt = (BigDecimal)
context.get("initialEstimateAmt");
- Locale locale = (Locale) context.get("locale");
-
- if (shippableTotal == null) {
- shippableTotal = BigDecimal.ZERO;
- }
- if (shippableQuantity == null) {
- shippableQuantity = BigDecimal.ZERO;
- }
- if (shippableWeight == null) {
- shippableWeight = BigDecimal.ZERO;
- }
- if (initialEstimateAmt == null) {
- initialEstimateAmt = BigDecimal.ZERO;
- }
+ final BigDecimal shippableTotal = UtilNumber.getBigDecimal(context,
"shippableTotal", BigDecimal.ZERO);
+ final BigDecimal shippableQuantity = UtilNumber.getBigDecimal(context,
"shippableQuantity", BigDecimal.ZERO);
+ final BigDecimal shippableWeight = UtilNumber.getBigDecimal(context,
"shippableWeight", BigDecimal.ZERO);
+ final BigDecimal initialEstimateAmt =
UtilNumber.getBigDecimal(context, "initialEstimateAmt", BigDecimal.ZERO);
// get the ShipmentCostEstimate(s)
- Map<String, String> estFields = UtilMisc.toMap("productStoreId",
productStoreId, "shipmentMethodTypeId", shipmentMethodTypeId,
- "carrierPartyId", carrierPartyId, "carrierRoleTypeId",
carrierRoleTypeId);
- EntityCondition estFieldsCond =
EntityCondition.makeCondition(estFields, EntityOperator.AND);
+ Map<String, String> estFields = UtilMisc.toMap("productStoreId",
productStoreId,
+ "shipmentMethodTypeId", shipmentMethodTypeId,
+ "carrierPartyId", carrierPartyId,
+ "carrierRoleTypeId", carrierRoleTypeId);
if (UtilValidate.isNotEmpty(productStoreShipMethId)) {
// if the productStoreShipMethId field is passed, then also get
estimates that have the field set
- List<EntityCondition> condList =
UtilMisc.toList(EntityCondition.makeCondition("productStoreShipMethId",
- EntityOperator.EQUALS, productStoreShipMethId),
estFieldsCond);
- estFieldsCond = EntityCondition.makeCondition(condList,
EntityOperator.AND);
+ estFields.put("productStoreShipMethId", productStoreShipMethId);
}
- Collection<GenericValue> estimates = null;
+ List<GenericValue> estimates;
try {
estimates = EntityQuery.use(delegator).from("ShipmentCostEstimate")
- .where(estFieldsCond)
- .cache(true)
+ .where(estFields)
+ .cache()
.queryList();
} catch (GenericEntityException e) {
Debug.logError(e, MODULE);
return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
"ProductShipmentCostEstimateCannotRetrieve", locale));
}
- if (estimates == null || estimates.size() < 1) {
+ if (estimates.isEmpty()) {
if (initialEstimateAmt.compareTo(BigDecimal.ZERO) == 0) {
Debug.logWarning("No shipping estimates found; the shipping
amount returned is 0! Condition used was: "
- + estFieldsCond + "; Using the passed context: " +
context, MODULE);
+ + estFields + "; Using the passed context: " +
context, MODULE);
}
Map<String, Object> respNow = ServiceUtil.returnSuccess();
@@ -266,105 +251,20 @@ public class ShipmentServices {
}
// Get the PostalAddress
- GenericValue shipAddress = null;
- if (shippingContactMechId != null) {
- try {
- shipAddress =
EntityQuery.use(delegator).from("PostalAddress").where("contactMechId",
shippingContactMechId).queryOne();
- } catch (GenericEntityException e) {
- Debug.logError(e, MODULE);
- return
ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
- "ProductShipmentCostEstimateCannotGetShippingAddress",
locale));
- }
- } else if (shippingPostalCode != null) {
- String countryGeoId = null;
- try {
- GenericValue countryGeo =
EntityQuery.use(delegator).from("Geo")
- .where("geoTypeId", "COUNTRY", "geoCode",
shippingCountryCode)
- .cache(true)
- .queryFirst();
- if (countryGeo != null) {
- countryGeoId = countryGeo.getString("geoId");
- }
- } catch (GenericEntityException e) {
- Debug.logError(e, MODULE);
- }
- shipAddress = delegator.makeValue("PostalAddress");
- shipAddress.set("countryGeoId", countryGeoId);
- shipAddress.set("postalCodeGeoId", shippingPostalCode);
+ final GenericValue shipAddress;
+ try {
+ shipAddress = resolveShippingAddress(delegator,
shippingContactMechId, shippingPostalCode, shippingCountryCode);
+ } catch (GenericEntityException e) {
+ Debug.logError(e, MODULE);
+ return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
+ "ProductShipmentCostEstimateCannotGetShippingAddress",
locale));
}
// Get the possible estimates.
- List<GenericValue> estimateList = new LinkedList<>();
+ List<GenericValue> estimateList = estimates.stream().filter(item ->
+ matchGeoAndBreakQuantity(delegator, shippableTotal,
shippableQuantity, shippableWeight, shipAddress, item))
+ .collect(Collectors.toList());
- for (GenericValue thisEstimate: estimates) {
- try {
- String toGeo = thisEstimate.getString("geoIdTo");
- if (UtilValidate.isNotEmpty(toGeo) && shipAddress == null) {
- // This estimate requires shipping address details. We
don't have it so we cannot use this estimate.
- continue;
- }
- List<GenericValue> toGeoList = GeoWorker.expandGeoGroup(toGeo,
delegator);
- // Make sure we have a valid GEOID.
- if (UtilValidate.isEmpty(toGeoList)
- || GeoWorker.containsGeo(toGeoList,
shipAddress.getString("countryGeoId"), delegator)
- || GeoWorker.containsGeo(toGeoList,
shipAddress.getString("stateProvinceGeoId"), delegator)
- || GeoWorker.containsGeo(toGeoList,
shipAddress.getString("postalCodeGeoId"), delegator)) {
- GenericValue wv =
thisEstimate.getRelatedOne("WeightQuantityBreak", false);
- GenericValue qv =
thisEstimate.getRelatedOne("QuantityQuantityBreak", false);
- GenericValue pv =
thisEstimate.getRelatedOne("PriceQuantityBreak", false);
- if (wv == null && qv == null && pv == null) {
- estimateList.add(thisEstimate);
- } else {
- // Do some testing.
- boolean useWeight = false;
- boolean weightValid = false;
- boolean useQty = false;
- boolean qtyValid = false;
- boolean usePrice = false;
- boolean priceValid = false;
-
- if (wv != null) {
- useWeight = true;
- BigDecimal min = BigDecimal.ONE.movePointLeft(4);
- BigDecimal max = BigDecimal.ONE.movePointLeft(4);
- min = wv.getBigDecimal("fromQuantity");
- max = wv.getBigDecimal("thruQuantity");
- if (shippableWeight.compareTo(min) >= 0 &&
(max.compareTo(BigDecimal.ZERO) == 0 || shippableWeight.compareTo(max) <= 0)) {
- weightValid = true;
- }
- }
- if (qv != null) {
- useQty = true;
- BigDecimal min = BigDecimal.ONE.movePointLeft(4);
- BigDecimal max = BigDecimal.ONE.movePointLeft(4);
- min = qv.getBigDecimal("fromQuantity");
- max = qv.getBigDecimal("thruQuantity");
- if (shippableQuantity.compareTo(min) >= 0 &&
(max.compareTo(BigDecimal.ZERO) == 0
- || shippableQuantity.compareTo(max) <= 0))
{
- qtyValid = true;
- }
- }
- if (pv != null) {
- usePrice = true;
- BigDecimal min = BigDecimal.ONE.movePointLeft(4);
- BigDecimal max = BigDecimal.ONE.movePointLeft(4);
- min = pv.getBigDecimal("fromQuantity");
- max = pv.getBigDecimal("thruQuantity");
- if (shippableTotal.compareTo(min) >= 0 &&
(max.compareTo(BigDecimal.ZERO) == 0 || shippableTotal.compareTo(max) <= 0)) {
- priceValid = true;
- }
- }
- // Now check the tests.
- if ((useWeight && weightValid) || (useQty && qtyValid)
|| (usePrice && priceValid)) {
- estimateList.add(thisEstimate);
- }
- }
- }
- } catch (GenericEntityException e) {
- Debug.logError(e, e.getLocalizedMessage(), MODULE);
- }
- }
-
- if (estimateList.size() < 1) {
+ if (estimateList.isEmpty()) {
return
ServiceUtil.returnFailure(UtilProperties.getMessage(RESOURCE,
"ProductShipmentCostEstimateCannotFoundForCarrier",
UtilMisc.toMap("carrierPartyId", carrierPartyId,
@@ -390,80 +290,44 @@ public class ShipmentServices {
Set<String> featureSet =
UtilGenerics.cast(itemMap.get("featureSet"));
if (UtilValidate.isNotEmpty(featureSet)) {
for (String featureId: featureSet) {
- BigDecimal featureQuantity =
shippableFeatureMap.get(featureId);
- if (featureQuantity == null) {
- featureQuantity = BigDecimal.ZERO;
- }
- featureQuantity = featureQuantity.add(quantity);
- shippableFeatureMap.put(featureId,
featureQuantity);
+ shippableFeatureMap.put(featureId,
UtilNumber.safeAdd(quantity, shippableFeatureMap.get(featureId)));
}
}
}
-
}
}
- // Calculate priority based on available data.
- double priorityParty = 9;
- double priorityRole = 8;
- double priorityGeo = 4;
- double priorityWeight = 1;
- double priorityQty = 1;
- double priorityPrice = 1;
-
- int estimateIndex = 0;
-
+ // Grab the estimate and work with it.
+ GenericValue estimate;
if (estimateList.size() > 1) {
- TreeMap<Integer, GenericValue> estimatePriority = new TreeMap<>();
+ // Calculate priority based on available data.
+ final Map<String, Integer> priorityByField = UtilMisc.toMap(
+ "partyId", 9,
+ "roleTypeId", 8,
+ "geoIdTo", 4,
+ "weightBreakId", 1,
+ "quantityBreakId", 1,
+ "priceBreakId", 1);
+ TreeMap<Integer, GenericValue> estimatePriority = new TreeMap<>();
for (GenericValue currentEstimate: estimateList) {
- int prioritySum = 0;
- if
(UtilValidate.isNotEmpty(currentEstimate.getString("partyId"))) {
- prioritySum += priorityParty;
- }
- if
(UtilValidate.isNotEmpty(currentEstimate.getString("roleTypeId"))) {
- prioritySum += priorityRole;
- }
- if
(UtilValidate.isNotEmpty(currentEstimate.getString("geoIdTo"))) {
- prioritySum += priorityGeo;
- }
- if
(UtilValidate.isNotEmpty(currentEstimate.getString("weightBreakId"))) {
- prioritySum += priorityWeight;
- }
- if
(UtilValidate.isNotEmpty(currentEstimate.getString("quantityBreakId"))) {
- prioritySum += priorityQty;
- }
- if
(UtilValidate.isNotEmpty(currentEstimate.getString("priceBreakId"))) {
- prioritySum += priorityPrice;
- }
-
- // there will be only one of each priority; latest will replace
- estimatePriority.put(prioritySum, currentEstimate);
+ estimatePriority.put(priorityByField.keySet()
+ .stream()
+ .filter(k ->
UtilValidate.isNotEmpty(currentEstimate.get(k)))
+
.mapToInt(priorityByField::get)
+ .sum(), currentEstimate);
}
// locate the highest priority estimate; or the latest entered
- Object[] estimateArray = estimatePriority.values().toArray();
- estimateIndex =
estimateList.indexOf(estimateArray[estimateArray.length - 1]);
+ estimate =
estimatePriority.descendingMap().pollFirstEntry().getValue();
+ } else {
+ estimate = estimateList.get(0);
}
- // Grab the estimate and work with it.
- GenericValue estimate = estimateList.get(estimateIndex);
-
// flat fees
- BigDecimal orderFlat = BigDecimal.ZERO;
- if (estimate.getBigDecimal("orderFlatPrice") != null) {
- orderFlat = estimate.getBigDecimal("orderFlatPrice");
- }
-
- BigDecimal orderItemFlat = BigDecimal.ZERO;
- if (estimate.getBigDecimal("orderItemFlatPrice") != null) {
- orderItemFlat = estimate.getBigDecimal("orderItemFlatPrice");
- }
-
- BigDecimal orderPercent = BigDecimal.ZERO;
- if (estimate.getBigDecimal("orderPricePercent") != null) {
- orderPercent = estimate.getBigDecimal("orderPricePercent");
- }
+ BigDecimal orderFlat = UtilNumber.getBigDecimal(estimate,
"orderFlatPrice", BigDecimal.ZERO);
+ BigDecimal orderItemFlat = UtilNumber.getBigDecimal(estimate,
"orderItemFlatPrice", BigDecimal.ZERO);
+ BigDecimal orderPercent = UtilNumber.getBigDecimal(estimate,
"orderPricePercent", BigDecimal.ZERO);
BigDecimal itemFlatAmount = shippableQuantity.multiply(orderItemFlat);
BigDecimal orderPercentage =
shippableTotal.multiply(orderPercent.movePointLeft(2));
@@ -472,20 +336,9 @@ public class ShipmentServices {
BigDecimal flatTotal =
orderFlat.add(itemFlatAmount).add(orderPercentage);
// spans
- BigDecimal weightUnit = BigDecimal.ZERO;
- if (estimate.getBigDecimal("weightUnitPrice") != null) {
- weightUnit = estimate.getBigDecimal("weightUnitPrice");
- }
-
- BigDecimal qtyUnit = BigDecimal.ZERO;
- if (estimate.getBigDecimal("quantityUnitPrice") != null) {
- qtyUnit = estimate.getBigDecimal("quantityUnitPrice");
- }
-
- BigDecimal priceUnit = BigDecimal.ZERO;
- if (estimate.getBigDecimal("priceUnitPrice") != null) {
- priceUnit = estimate.getBigDecimal("priceUnitPrice");
- }
+ BigDecimal weightUnit = UtilNumber.getBigDecimal(estimate,
"weightUnitPrice", BigDecimal.ZERO);
+ BigDecimal qtyUnit = UtilNumber.getBigDecimal(estimate,
"quantityUnitPrice", BigDecimal.ZERO);
+ BigDecimal priceUnit = UtilNumber.getBigDecimal(estimate,
"priceUnitPrice", BigDecimal.ZERO);
BigDecimal weightAmount = shippableWeight.multiply(weightUnit);
BigDecimal quantityAmount = shippableQuantity.multiply(qtyUnit);
@@ -496,16 +349,10 @@ public class ShipmentServices {
// feature surcharges
BigDecimal featureSurcharge = BigDecimal.ZERO;
- String featureGroupId = estimate.getString("productFeatureGroupId");
- BigDecimal featurePercent = estimate.getBigDecimal("featurePercent");
- BigDecimal featurePrice = estimate.getBigDecimal("featurePrice");
- if (featurePercent == null) {
- featurePercent = BigDecimal.ZERO;
- }
- if (featurePrice == null) {
- featurePrice = BigDecimal.ZERO;
- }
+ BigDecimal featurePercent = UtilNumber.getBigDecimal(estimate,
"featurePercent", BigDecimal.ZERO);
+ BigDecimal featurePrice = UtilNumber.getBigDecimal(estimate,
"featurePrice", BigDecimal.ZERO);
+ String featureGroupId = estimate.getString("productFeatureGroupId");
if (UtilValidate.isNotEmpty(featureGroupId)) {
for (Map.Entry<String, BigDecimal> entry:
shippableFeatureMap.entrySet()) {
String featureId = entry.getKey();
@@ -515,7 +362,7 @@ public class ShipmentServices {
try {
appl =
EntityQuery.use(delegator).from("ProductFeatureGroupAppl")
.where("productFeatureGroupId", featureGroupId,
"productFeatureId", featureId)
- .cache(true)
+ .cache()
.filterByDate()
.queryFirst();
} catch (GenericEntityException e) {
@@ -535,7 +382,7 @@ public class ShipmentServices {
if (sizeUnit != null && sizeUnit.compareTo(BigDecimal.ZERO) > 0) {
for (BigDecimal size : shippableItemSizes) {
if (size != null && size.compareTo(sizeUnit) >= 0) {
- sizeSurcharge = sizeSurcharge.add(sizePrice);
+ sizeSurcharge = UtilNumber.safeAdd(sizeSurcharge,
sizePrice);
}
}
}
@@ -547,10 +394,7 @@ public class ShipmentServices {
BigDecimal subTotal = spanTotal.add(flatTotal).add(surchargeTotal);
// percent add-on
- BigDecimal shippingPricePercent = BigDecimal.ZERO;
- if (estimate.getBigDecimal("shippingPricePercent") != null) {
- shippingPricePercent =
estimate.getBigDecimal("shippingPricePercent");
- }
+ BigDecimal shippingPricePercent = UtilNumber.getBigDecimal(estimate,
"shippingPricePercent", BigDecimal.ZERO);
// shipping total
BigDecimal shippingTotal =
subTotal.add((subTotal.add(initialEstimateAmt)).multiply(shippingPricePercent.movePointLeft(2)));
@@ -561,6 +405,68 @@ public class ShipmentServices {
return responseResult;
}
+ private static GenericValue resolveShippingAddress(Delegator delegator,
String shippingContactMechId,
+ String
shippingPostalCode, String shippingCountryCode)
+ throws GenericEntityException {
+ if (shippingContactMechId != null) {
+ return
EntityQuery.use(delegator).from("PostalAddress").where("contactMechId",
shippingContactMechId).queryOne();
+ } else if (shippingPostalCode != null) {
+ String countryGeoId = null;
+ GenericValue countryGeo = EntityQuery.use(delegator).from("Geo")
+ .where("geoTypeId", "COUNTRY", "geoCode",
shippingCountryCode)
+ .cache()
+ .queryFirst();
+ if (countryGeo != null) {
+ countryGeoId = countryGeo.getString("geoId");
+ }
+ return delegator.makeValue("PostalAddress",
+ UtilMisc.toMap("countryGeoId", countryGeoId,
+ "postalCodeGeoId", shippingPostalCode));
+ }
+ return null;
+ }
+
+ private static boolean matchGeoAndBreakQuantity(Delegator delegator,
BigDecimal shippableTotal,
+ BigDecimal
shippableQuantity, BigDecimal shippableWeight,
+ GenericValue shipAddress,
GenericValue thisEstimate) {
+ try {
+ String toGeo = thisEstimate.getString("geoIdTo");
+ if (UtilValidate.isNotEmpty(toGeo) && shipAddress == null) {
+ // This estimate requires shipping address details. We don't
have it so we cannot use this estimate.
+ return false;
+ }
+
+ List<GenericValue> toGeoList = GeoWorker.expandGeoGroup(toGeo,
delegator);
+ // Make sure we have a valid GEOID.
+ if (UtilValidate.isEmpty(toGeoList)
+ || GeoWorker.containsGeo(toGeoList,
shipAddress.getString("countryGeoId"), delegator)
+ || GeoWorker.containsGeo(toGeoList,
shipAddress.getString("stateProvinceGeoId"), delegator)
+ || GeoWorker.containsGeo(toGeoList,
shipAddress.getString("postalCodeGeoId"), delegator)) {
+
+ // now check if some break quantity are present and valid the
matching value
+ GenericValue wv =
thisEstimate.getRelatedOne("WeightQuantityBreak", true);
+ GenericValue qv =
thisEstimate.getRelatedOne("QuantityQuantityBreak", true);
+ GenericValue pv =
thisEstimate.getRelatedOne("PriceQuantityBreak", true);
+ return (wv == null && qv == null && pv == null) || (
+ isBreakQuantityValid(shippableWeight, wv)
+ && isBreakQuantityValid(shippableQuantity, qv)
+ && isBreakQuantityValid(shippableTotal, pv));
+ }
+ } catch (GenericEntityException e) {
+ Debug.logError(e, e.getLocalizedMessage(), MODULE);
+ }
+ return false;
+ }
+
+ private static boolean isBreakQuantityValid(BigDecimal qty, GenericValue
breakQuantity) {
+ if (breakQuantity == null) {
+ return true;
+ }
+ BigDecimal min = breakQuantity.getBigDecimal("fromQuantity");
+ BigDecimal max = breakQuantity.getBigDecimal("thruQuantity");
+ return qty.compareTo(min) >= 0 && (max.compareTo(BigDecimal.ZERO) == 0
|| qty.compareTo(max) <= 0);
+ }
+
public static Map<String, Object>
fillShipmentStagingTables(DispatchContext dctx, Map<String, ? extends Object>
context) {
Delegator delegator = dctx.getDelegator();
String shipmentId = (String) context.get("shipmentId");
diff --git a/applications/product/testdef/FacilityTest.xml
b/applications/product/testdef/FacilityTest.xml
index 21185d5..d2c36a9 100644
--- a/applications/product/testdef/FacilityTest.xml
+++ b/applications/product/testdef/FacilityTest.xml
@@ -35,6 +35,11 @@ under the License.
<junit-test-suite class-name="org.apache.ofbiz.product.ShipmentTests"/>
</test-case>
+ <test-group case-name="shipmentcost-tests">
+ <entity-xml action="load"
entity-xml-url="component://product/testdef/data/ShipmentCostTestData.xml"/>
+ <junit-test-suite
class-name="org.apache.ofbiz.shipment.ShipmentCostTests"/>
+ </test-group>
+
<test-case case-name="loadIssuanceTestData">
<entity-xml action="load"
entity-xml-url="component://product/testdef/data/IssuanceTestData.xml"/>
</test-case>
diff --git a/applications/product/testdef/data/ShipmentCostTestData.xml
b/applications/product/testdef/data/ShipmentCostTestData.xml
new file mode 100644
index 0000000..0dc3dec
--- /dev/null
+++ b/applications/product/testdef/data/ShipmentCostTestData.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<entity-engine-xml>
+ <create-replace>
+ <ProductStore productStoreId="ShipCost" storeName="OFBiz test"
companyName="Apache OFBiz"
+ title="Test OFBiz store1"/>
+ <ShipmentMethodType description="Air" shipmentMethodTypeId="AIR"/>
+ <ShipmentMethodType description="Road" shipmentMethodTypeId="ROAD"/>
+ <ShipmentMethodType description="Local Delivery"
shipmentMethodTypeId="LOCAL_DELIVERY"/>
+
+ <QuantityBreakType description="Shipping Weight Break"
quantityBreakTypeId="SHIP_WEIGHT"/>
+ <QuantityBreakType description="Shipping Quantity Break"
quantityBreakTypeId="SHIP_QUANTITY"/>
+ <QuantityBreakType description="Shipping Price Break"
quantityBreakTypeId="SHIP_PRICE"/>
+
+ <!--simple case-->
+ <Party partyId="UPS_SIMPLE" partyTypeId="PARTY_GROUP"/>
+ <PartyGroup partyId="UPS_SIMPLE" groupName="UPS_SIMPLE"/>
+ <PartyRole partyId="UPS_SIMPLE" roleTypeId="CARRIER"/>
+
+ <CarrierShipmentMethod partyId="UPS_SIMPLE" roleTypeId="CARRIER"
shipmentMethodTypeId="AIR" sequenceNumber="1" carrierServiceCode="01"/>
+ <CarrierShipmentMethod partyId="UPS_SIMPLE" roleTypeId="CARRIER"
shipmentMethodTypeId="ROAD" sequenceNumber="2" carrierServiceCode="02"/>
+ <CarrierShipmentMethod partyId="UPS_SIMPLE" roleTypeId="CARRIER"
shipmentMethodTypeId="LOCAL_DELIVERY" sequenceNumber="3"
carrierServiceCode="03"/>
+
+ <ProductStoreShipmentMeth productStoreShipMethId="9002"
productStoreId="ShipCost" partyId="UPS_SIMPLE" includeNoChargeItems="N"
allowUspsAddr="N" requireUspsAddr="N" roleTypeId="CARRIER"
shipmentMethodTypeId="ROAD" sequenceNumber="2"/>
+ <ProductStoreShipmentMeth productStoreShipMethId="9001"
productStoreId="ShipCost" partyId="UPS_SIMPLE" includeNoChargeItems="N"
allowUspsAddr="N" requireUspsAddr="N" roleTypeId="CARRIER"
shipmentMethodTypeId="AIR" sequenceNumber="2"/>
+ <ProductStoreShipmentMeth productStoreShipMethId="9003"
productStoreId="ShipCost" partyId="UPS_SIMPLE" includeNoChargeItems="N"
allowUspsAddr="N" requireUspsAddr="N" roleTypeId="CARRIER"
shipmentMethodTypeId="LOCAL_DELIVERY" sequenceNumber="2"/>
+ <ShipmentCostEstimate productStoreShipMethId="9002"
productStoreId="ShipCost" orderFlatPrice="10.0" orderItemFlatPrice="0.0"
orderPricePercent="0.0" shipmentCostEstimateId="9000"
shipmentMethodTypeId="ROAD" carrierPartyId="UPS_SIMPLE"
carrierRoleTypeId="CARRIER"/>
+ <ShipmentCostEstimate productStoreShipMethId="9001"
productStoreId="ShipCost" orderFlatPrice="0.0" orderItemFlatPrice="0.0"
orderPricePercent="4" shipmentCostEstimateId="9001" shipmentMethodTypeId="AIR"
carrierPartyId="UPS_SIMPLE" carrierRoleTypeId="CARRIER"/>
+ <ShipmentCostEstimate productStoreShipMethId="9003"
productStoreId="ShipCost" orderFlatPrice="10.0" orderItemFlatPrice="0.0"
orderPricePercent="0.0" shipmentCostEstimateId="9003"
shipmentMethodTypeId="LOCAL_DELIVERY" carrierPartyId="UPS_SIMPLE"
carrierRoleTypeId="CARRIER"/>
+
+ <!--break case-->
+ <QuantityBreak fromQuantity="0" quantityBreakId="W1"
quantityBreakTypeId="SHIP_WEIGHT" thruQuantity="99"/>
+ <QuantityBreak fromQuantity="100" quantityBreakId="W2"
quantityBreakTypeId="SHIP_WEIGHT" thruQuantity="999"/>
+ <QuantityBreak fromQuantity="1000" quantityBreakId="W3"
quantityBreakTypeId="SHIP_WEIGHT" thruQuantity="0"/>
+
+ <QuantityBreak fromQuantity="0" quantityBreakId="P1"
quantityBreakTypeId="SHIP_PRICE" thruQuantity="99"/>
+ <QuantityBreak fromQuantity="100" quantityBreakId="P2"
quantityBreakTypeId="SHIP_PRICE" thruQuantity="999"/>
+ <QuantityBreak fromQuantity="1000" quantityBreakId="P3"
quantityBreakTypeId="SHIP_PRICE" thruQuantity="99999"/>
+
+ <QuantityBreak fromQuantity="0" quantityBreakId="Q1"
quantityBreakTypeId="SHIP_QUANTITY" thruQuantity="100"/>
+ <QuantityBreak fromQuantity="100" quantityBreakId="Q2"
quantityBreakTypeId="SHIP_QUANTITY" thruQuantity="1000"/>
+ <QuantityBreak fromQuantity="1000" quantityBreakId="Q3"
quantityBreakTypeId="SHIP_QUANTITY" thruQuantity="0"/>
+
+ <Party partyId="UPS_BREAK" partyTypeId="PARTY_GROUP"/>
+ <PartyGroup partyId="UPS_BREAK" groupName="UPS_BREAK"/>
+ <PartyRole partyId="UPS_BREAK" roleTypeId="CARRIER"/>
+
+ <CarrierShipmentMethod partyId="UPS_BREAK" roleTypeId="CARRIER"
shipmentMethodTypeId="AIR" sequenceNumber="1" carrierServiceCode="01"/>
+ <CarrierShipmentMethod partyId="UPS_BREAK" roleTypeId="CARRIER"
shipmentMethodTypeId="ROAD" sequenceNumber="2" carrierServiceCode="02"/>
+ <CarrierShipmentMethod partyId="UPS_BREAK" roleTypeId="CARRIER"
shipmentMethodTypeId="LOCAL_DELIVERY" sequenceNumber="2"
carrierServiceCode="02"/>
+
+ <ProductStoreShipmentMeth productStoreShipMethId="WB9001"
productStoreId="ShipCost" partyId="UPS_BREAK" includeNoChargeItems="N"
allowUspsAddr="N" requireUspsAddr="N" roleTypeId="CARRIER"
shipmentMethodTypeId="ROAD" sequenceNumber="2"/>
+ <ProductStoreShipmentMeth productStoreShipMethId="QB9001"
productStoreId="ShipCost" partyId="UPS_BREAK" includeNoChargeItems="N"
allowUspsAddr="N" requireUspsAddr="N" roleTypeId="CARRIER"
shipmentMethodTypeId="AIR" sequenceNumber="2"/>
+ <ProductStoreShipmentMeth productStoreShipMethId="PB9001"
productStoreId="ShipCost" partyId="UPS_BREAK" includeNoChargeItems="N"
allowUspsAddr="N" requireUspsAddr="N" roleTypeId="CARRIER"
shipmentMethodTypeId="LOCAL_DELIVERY" sequenceNumber="2"/>
+
+ <!-- use by testCalculateWeightBreakShipmentCostFlatValue -->
+ <ShipmentCostEstimate orderFlatPrice="9.0" weightBreakId="W1"
productStoreShipMethId="WB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="WB9000" shipmentMethodTypeId="ROAD"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+ <ShipmentCostEstimate orderFlatPrice="10.0" weightBreakId="W2"
productStoreShipMethId="WB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="WB9001" shipmentMethodTypeId="ROAD"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+ <ShipmentCostEstimate orderFlatPrice="11.0" weightBreakId="W3"
productStoreShipMethId="WB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="WB9003" shipmentMethodTypeId="ROAD"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+
+ <!-- use by testCalculateQuantityBreakShipmentCostFlatValue -->
+ <ShipmentCostEstimate orderFlatPrice="12.0" quantityBreakId="Q1"
productStoreShipMethId="QB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="QB9000" shipmentMethodTypeId="AIR"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+ <ShipmentCostEstimate orderFlatPrice="13.0" quantityBreakId="Q2"
productStoreShipMethId="QB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="QB9001" shipmentMethodTypeId="AIR"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+ <ShipmentCostEstimate orderFlatPrice="14.0" quantityBreakId="Q3"
productStoreShipMethId="QB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="QB9003" shipmentMethodTypeId="AIR"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+
+ <!-- use by testCalculatePriceBreakShipmentCostFlatValue -->
+ <ShipmentCostEstimate orderFlatPrice="15.0" priceBreakId="P1"
productStoreShipMethId="PB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="PB9000" shipmentMethodTypeId="LOCAL_DELIVERY"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+ <ShipmentCostEstimate orderFlatPrice="16.0" priceBreakId="P2"
productStoreShipMethId="PB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="PB9001" shipmentMethodTypeId="LOCAL_DELIVERY"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+ <ShipmentCostEstimate orderFlatPrice="17.0" priceBreakId="P3"
productStoreShipMethId="PB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="PB9003" shipmentMethodTypeId="LOCAL_DELIVERY"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+
+ <!-- use by testCalculatePriceAndWeightBreakShipmentCostFlatValue -->
+ <ShipmentCostEstimate orderFlatPrice="18.0" priceBreakId="P2"
weightBreakId="W2" productStoreShipMethId="PB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="PB9004" shipmentMethodTypeId="LOCAL_DELIVERY"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+ <ShipmentCostEstimate orderFlatPrice="19.0" priceBreakId="P3"
weightBreakId="W2" productStoreShipMethId="PB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="PB9005" shipmentMethodTypeId="LOCAL_DELIVERY"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+ <ShipmentCostEstimate orderFlatPrice="20.0" priceBreakId="P2"
weightBreakId="W3" productStoreShipMethId="PB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="PB9006" shipmentMethodTypeId="LOCAL_DELIVERY"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+ <ShipmentCostEstimate orderFlatPrice="21.0" priceBreakId="P3"
weightBreakId="W3" productStoreShipMethId="PB9001" productStoreId="ShipCost"
orderItemFlatPrice="0.0" orderPricePercent="0.0"
shipmentCostEstimateId="PB9007" shipmentMethodTypeId="LOCAL_DELIVERY"
carrierPartyId="UPS_BREAK" carrierRoleTypeId="CARRIER"/>
+
+ <!--multiple case -->
+ <Party partyId="UPS_MULTI" partyTypeId="PARTY_GROUP"/>
+ <Party partyId="RECEIVER" partyTypeId="PARTY_GROUP"/>
+ <PartyGroup partyId="UPS_MULTI" groupName="UPS_MULTI"/>
+ <PartyRole partyId="UPS_MULTI" roleTypeId="CARRIER"/>
+
+ <CarrierShipmentMethod partyId="UPS_MULTI" roleTypeId="CARRIER"
shipmentMethodTypeId="ROAD" sequenceNumber="2" carrierServiceCode="02"/>
+ <ProductStoreShipmentMeth productStoreShipMethId="M9002"
productStoreId="ShipCost" partyId="UPS_MULTI" includeNoChargeItems="N"
allowUspsAddr="N" requireUspsAddr="N" roleTypeId="CARRIER"
shipmentMethodTypeId="ROAD" sequenceNumber="2"/>
+ <ShipmentCostEstimate productStoreShipMethId="M9002"
productStoreId="ShipCost" orderFlatPrice="1.0" orderItemFlatPrice="0.0"
orderPricePercent="0.0" shipmentCostEstimateId="M9000"
shipmentMethodTypeId="ROAD" carrierPartyId="UPS_MULTI"
carrierRoleTypeId="CARRIER" partyId="RECEIVER"/>
+ <ShipmentCostEstimate productStoreShipMethId="M9002" priceBreakId="P2"
productStoreId="ShipCost" orderFlatPrice="2.0" orderItemFlatPrice="0.0"
orderPricePercent="0" shipmentCostEstimateId="M9001"
shipmentMethodTypeId="ROAD" carrierPartyId="UPS_MULTI"
carrierRoleTypeId="CARRIER" partyId="RECEIVER"/>
+ <ShipmentCostEstimate productStoreShipMethId="M9002"
productStoreId="ShipCost" orderFlatPrice="3.0" orderItemFlatPrice="0.0"
orderPricePercent="0.0" shipmentCostEstimateId="M9003"
shipmentMethodTypeId="ROAD" carrierPartyId="UPS_MULTI"
carrierRoleTypeId="CARRIER"/>
+
+ </create-replace>
+</entity-engine-xml>
diff --git
a/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilNumber.java
b/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilNumber.java
index 66c9ce3..922ce65 100644
--- a/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilNumber.java
+++ b/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilNumber.java
@@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.Locale;
import com.ibm.icu.text.RuleBasedNumberFormat;
+import java.util.Map;
public final class UtilNumber {
@@ -389,4 +390,27 @@ public final class UtilNumber {
public static BigDecimal safeAdd(BigDecimal left, BigDecimal right) {
return right != null ? left.add(right) : left;
}
+
+ /**
+ * Resolve a BigDecimal for a given field present on context map and
return default value if not present
+ * @param context
+ * @param field
+ * @param defaultValue
+ * @return
+ */
+ public static BigDecimal getBigDecimal(Map<String, ?> context, String
field, BigDecimal defaultValue) {
+ if (context != null && field != null && context.get(field) != null) {
+ Object fieldValue = context.get(field);
+ if (fieldValue instanceof BigDecimal) {
+ return (BigDecimal) context.get(field);
+ }
+ if (fieldValue instanceof Double) {
+ return BigDecimal.valueOf((Double) context.get(field));
+ }
+ if (fieldValue instanceof Long) {
+ return BigDecimal.valueOf((Long) context.get(field));
+ }
+ }
+ return defaultValue;
+ }
}