IDEMPIERE-5118 MUOMConversion fix and improvements (#1075)

This commit is contained in:
hengsin 2021-12-23 17:21:26 +08:00 committed by GitHub
parent 93ebe43cbd
commit b88a77ffec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 281 additions and 17 deletions

View File

@ -0,0 +1,23 @@
SET SQLBLANKLINES ON
SET DEFINE OFF
-- IDEMPIERE-5118 MUOMConversion fix and improvements
-- Dec 21, 2021, 3:31:26 PM MYT
UPDATE AD_IndexColumn SET SeqNo=4,Updated=TO_DATE('2021-12-21 15:31:26','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_IndexColumn_ID=200656
;
-- Dec 21, 2021, 3:32:58 PM MYT
INSERT INTO AD_IndexColumn (AD_Client_ID,AD_Org_ID,AD_IndexColumn_ID,AD_IndexColumn_UU,Created,CreatedBy,EntityType,IsActive,Updated,UpdatedBy,AD_Column_ID,AD_TableIndex_ID,SeqNo) VALUES (0,0,201444,'9c133f49-0a9e-42ce-a8da-86ed15f7e40a',TO_DATE('2021-12-21 15:32:57','YYYY-MM-DD HH24:MI:SS'),100,'D','Y',TO_DATE('2021-12-21 15:32:57','YYYY-MM-DD HH24:MI:SS'),100,1003,200562,3)
;
-- Dec 21, 2021, 3:33:18 PM MYT
DROP INDEX c_uom_conversion_product
;
-- Dec 21, 2021, 3:33:19 PM MYT
CREATE UNIQUE INDEX c_uom_conversion_product ON C_UOM_Conversion (C_UOM_ID,C_UOM_To_ID,AD_Client_ID,COALESCE(M_Product_ID,-1))
;
SELECT register_migration_script('202112210900_IDEMPIERE-5118.sql') FROM dual
;

View File

@ -0,0 +1,20 @@
-- IDEMPIERE-5118 MUOMConversion fix and improvements
-- Dec 21, 2021, 3:31:26 PM MYT
UPDATE AD_IndexColumn SET SeqNo=4,Updated=TO_TIMESTAMP('2021-12-21 15:31:26','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_IndexColumn_ID=200656
;
-- Dec 21, 2021, 3:32:58 PM MYT
INSERT INTO AD_IndexColumn (AD_Client_ID,AD_Org_ID,AD_IndexColumn_ID,AD_IndexColumn_UU,Created,CreatedBy,EntityType,IsActive,Updated,UpdatedBy,AD_Column_ID,AD_TableIndex_ID,SeqNo) VALUES (0,0,201444,'9c133f49-0a9e-42ce-a8da-86ed15f7e40a',TO_TIMESTAMP('2021-12-21 15:32:57','YYYY-MM-DD HH24:MI:SS'),100,'D','Y',TO_TIMESTAMP('2021-12-21 15:32:57','YYYY-MM-DD HH24:MI:SS'),100,1003,200562,3)
;
-- Dec 21, 2021, 3:33:18 PM MYT
DROP INDEX c_uom_conversion_product
;
-- Dec 21, 2021, 3:33:19 PM MYT
CREATE UNIQUE INDEX c_uom_conversion_product ON C_UOM_Conversion (C_UOM_ID,C_UOM_To_ID,AD_Client_ID,COALESCE(M_Product_ID,-1))
;
SELECT register_migration_script('202112210900_IDEMPIERE-5118.sql') FROM dual
;

View File

@ -18,7 +18,6 @@ package org.compiere.model;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.util.Properties;
import java.util.logging.Level;
@ -316,7 +315,7 @@ public class CalloutEngine implements Callout
BigDecimal rate2 = Env.ZERO;
if (rate1.signum() != 0.0) // no divide by zero
rate2 = Env.ONE.divide(rate1, 12, RoundingMode.HALF_UP);
rate2 = MUOMConversion.getOppositeRate(rate1);
//
if (mField.getColumnName().equals("MultiplyRate"))
mTab.setValue("DivideRate", rate2);

View File

@ -449,7 +449,6 @@ public class MUOMConversion extends X_C_UOM_Conversion implements ImmutablePOSup
return retValue;
} // convert
/**************************************************************************
* Convert PRICE expressed in entered UoM to equivalent price in product UoM and round. <br/>
* OR Convert QTY in product UOM to qty in entered UoM and round. <br/>
@ -464,7 +463,27 @@ public class MUOMConversion extends X_C_UOM_Conversion implements ImmutablePOSup
* @return Product: Qty/Price (precision rounded)
*/
static public BigDecimal convertProductTo (Properties ctx,
int M_Product_ID, int C_UOM_To_ID, BigDecimal qtyPrice)
int M_Product_ID, int C_UOM_To_ID, BigDecimal qtyPrice)
{
return convertProductTo(ctx, M_Product_ID, C_UOM_To_ID, qtyPrice, -1);
}
/**************************************************************************
* Convert PRICE expressed in entered UoM to equivalent price in product UoM and round. <br/>
* OR Convert QTY in product UOM to qty in entered UoM and round. <br/>
*
* eg: $6/6pk => $1/ea <br/>
* OR 6 X ea => 1 X 6pk
*
* @param ctx context
* @param M_Product_ID product
* @param C_UOM_To_ID entered UOM
* @param qtyPrice quantity or price
* @param precision Rounding precision, -1 to use precision from UOM
* @return Product: Qty/Price (precision rounded)
*/
static public BigDecimal convertProductTo (Properties ctx,
int M_Product_ID, int C_UOM_To_ID, BigDecimal qtyPrice, int precision)
{
if (qtyPrice == null || qtyPrice.signum() == 0
|| M_Product_ID == 0 || C_UOM_To_ID == 0)
@ -475,10 +494,17 @@ public class MUOMConversion extends X_C_UOM_Conversion implements ImmutablePOSup
{
if (Env.ONE.compareTo(retValue) == 0)
return qtyPrice;
MUOM uom = MUOM.get (ctx, C_UOM_To_ID);
if (uom != null)
return uom.round(retValue.multiply(qtyPrice), true);
return retValue.multiply(qtyPrice);
if (precision >= 0)
{
return retValue.multiply(qtyPrice).setScale(precision, RoundingMode.HALF_UP);
}
else
{
MUOM uom = MUOM.get (ctx, C_UOM_To_ID);
if (uom != null)
return uom.round(retValue.multiply(qtyPrice), true);
return retValue.multiply(qtyPrice);
}
}
return null;
} // convertProductTo
@ -496,6 +522,8 @@ public class MUOMConversion extends X_C_UOM_Conversion implements ImmutablePOSup
{
if (M_Product_ID == 0)
return null;
//first check product specific conversion
MUOMConversion[] rates = getProductConversions(ctx, M_Product_ID);
for (int i = 0; i < rates.length; i++)
@ -505,8 +533,10 @@ public class MUOMConversion extends X_C_UOM_Conversion implements ImmutablePOSup
return rate.getMultiplyRate();
}
List<MUOMConversion> conversions = new Query(ctx, Table_Name, "C_UOM_ID=? AND C_UOM_TO_ID=?", null)
.setParameters(MProduct.get(ctx, M_Product_ID).getC_UOM_ID(), C_UOM_To_ID)
//fall back to generic conversion
List<MUOMConversion> conversions = new Query(ctx, Table_Name, "C_UOM_ID=? AND C_UOM_TO_ID=? AND M_Product_ID IS NULL AND AD_Client_ID IN (0, ?)", null)
.setParameters(MProduct.get(ctx, M_Product_ID).getC_UOM_ID(), C_UOM_To_ID, Env.getAD_Client_ID(ctx))
.setOrderBy("AD_Client_ID Desc")
.setOnlyActiveRecords(true)
.list();
for (int i = 0; i < conversions.size(); i++)
@ -532,7 +562,27 @@ public class MUOMConversion extends X_C_UOM_Conversion implements ImmutablePOSup
* @return Product: Qty/Price (precision rounded)
*/
static public BigDecimal convertProductFrom (Properties ctx,
int M_Product_ID, int C_UOM_To_ID, BigDecimal qtyPrice)
int M_Product_ID, int C_UOM_To_ID, BigDecimal qtyPrice)
{
return convertProductFrom(ctx, M_Product_ID, C_UOM_To_ID, qtyPrice, -1);
}
/**************************************************************************
* Convert PRICE expressed in product UoM to equivalent price in entered UoM and round. <br/>
* OR Convert QTY in entered UOM to qty in product UoM and round. <br/>
*
* eg: $1/ea => $6/6pk <br/>
* OR 1 X 6pk => 6 X ea
*
* @param ctx context
* @param M_Product_ID product
* @param C_UOM_To_ID entered UOM
* @param qtyPrice quantity or price
* @param precision Rounding precision, -1 to use precision from UOM
* @return Product: Qty/Price (precision rounded)
*/
static public BigDecimal convertProductFrom (Properties ctx,
int M_Product_ID, int C_UOM_To_ID, BigDecimal qtyPrice, int precision)
{
// No conversion
if (qtyPrice == null || qtyPrice.compareTo(Env.ZERO)==0
@ -547,10 +597,17 @@ public class MUOMConversion extends X_C_UOM_Conversion implements ImmutablePOSup
{
if (Env.ONE.compareTo(retValue) == 0)
return qtyPrice;
MUOM uom = MUOM.get (ctx, C_UOM_To_ID);
if (uom != null)
return uom.round(retValue.multiply(qtyPrice), true);
return retValue.multiply(qtyPrice);
if (precision >= 0)
{
return retValue.multiply(qtyPrice).setScale(precision, RoundingMode.HALF_UP);
}
else
{
MUOM uom = MUOM.get (ctx, C_UOM_To_ID);
if (uom != null)
return uom.round(retValue.multiply(qtyPrice), true);
return retValue.multiply(qtyPrice);
}
}
if (s_log.isLoggable(Level.FINE)) s_log.fine("No Rate M_Product_ID=" + M_Product_ID);
return null;
@ -567,6 +624,10 @@ public class MUOMConversion extends X_C_UOM_Conversion implements ImmutablePOSup
static public BigDecimal getProductRateFrom (Properties ctx,
int M_Product_ID, int C_UOM_To_ID)
{
if (M_Product_ID == 0)
return null;
//first, check product specific conversion
MUOMConversion[] rates = getProductConversions(ctx, M_Product_ID);
for (int i = 0; i < rates.length; i++)
@ -576,8 +637,10 @@ public class MUOMConversion extends X_C_UOM_Conversion implements ImmutablePOSup
return rate.getDivideRate();
}
List<MUOMConversion> conversions = new Query(ctx, Table_Name, "C_UOM_ID=? AND C_UOM_TO_ID=?", null)
.setParameters(MProduct.get(ctx, M_Product_ID).getC_UOM_ID(), C_UOM_To_ID)
//fall back to generic conversion
List<MUOMConversion> conversions = new Query(ctx, Table_Name, "C_UOM_ID=? AND C_UOM_TO_ID=? AND M_Product_ID IS NULL AND AD_Client_ID IN (0, ?)", null)
.setParameters(MProduct.get(ctx, M_Product_ID).getC_UOM_ID(), C_UOM_To_ID, Env.getAD_Client_ID(ctx))
.setOrderBy("AD_Client_ID Desc")
.setOnlyActiveRecords(true)
.list();
for (int i = 0; i < conversions.size(); i++)
@ -746,6 +809,18 @@ public class MUOMConversion extends X_C_UOM_Conversion implements ImmutablePOSup
log.saveError("Error", Msg.parseTranslation(getCtx(), "@C_UOM_ID@ = @C_UOM_To_ID@"));
return false;
}
if (getMultiplyRate() != null && getMultiplyRate().signum() != 0)
{
if (getDivideRate() == null || getDivideRate().signum() == 0)
setDivideRate(getOppositeRate(getMultiplyRate()));
}
else if (getDivideRate() != null && getDivideRate().signum() != 0)
{
if (getMultiplyRate() == null || getMultiplyRate().signum() == 0)
setMultiplyRate(getOppositeRate(getDivideRate()));
}
// Nothing to convert
if (getMultiplyRate().compareTo(Env.ZERO) <= 0)
{
@ -807,4 +882,12 @@ public class MUOMConversion extends X_C_UOM_Conversion implements ImmutablePOSup
return this;
}
/**
* Calculate opposite conversion rate, i.e calculate divide rate for multiply rate and vice versa.
* @param rate
* @return {@link BigDecimal}
*/
public static BigDecimal getOppositeRate(BigDecimal rate) {
return Env.ONE.divide(rate, 12, RoundingMode.HALF_UP);
}
} // UOMConversion

View File

@ -0,0 +1,139 @@
/***********************************************************************
* This file is part of iDempiere ERP Open Source *
* http://www.idempiere.org *
* *
* Copyright (C) Contributors *
* *
* 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 should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301, USA. *
* *
* Contributors: *
* - hengsin *
**********************************************************************/
package org.idempiere.test.model;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.compiere.model.MUOM;
import org.compiere.model.MUOMConversion;
import org.compiere.model.PO;
import org.compiere.util.CacheMgt;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.idempiere.test.AbstractTestCase;
import org.junit.jupiter.api.Test;
/**
*
* @author hengsin
*
*/
public class MUOMConversionTest extends AbstractTestCase {
private final static int EACH_ID = 100;
private final static int HOUR_ID = 101;
private static final int PRODUCT_OAK_TREE = 123;
public MUOMConversionTest() {
}
@Test
public void testConversion() {
MUOM each = new MUOM(Env.getCtx(), EACH_ID, getTrxName());
MUOM hour = new MUOM(Env.getCtx(), HOUR_ID, getTrxName());
//conversion1 at system level
MUOMConversion conv1 = new MUOMConversion(each);
conv1.set_TrxName(null);
conv1.setC_UOM_To_ID(HOUR_ID);
conv1.setMultiplyRate(new BigDecimal("1.15"));
conv1.setDivideRate(BigDecimal.ZERO);
try {
PO.setCrossTenantSafe();
conv1.saveEx();
} finally {
PO.clearCrossTenantSafe();
}
MUOMConversion conv2 = null;
MUOMConversion conv3 = null;
try {
BigDecimal converted = MUOMConversion.convertProductTo(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"));
assertEquals(new BigDecimal("1.15"), converted);
converted = MUOMConversion.convertProductTo(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"), -1);
assertEquals(new BigDecimal("1.15"), converted);
converted = MUOMConversion.convertProductTo(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"), 1);
assertEquals(new BigDecimal("1.2"), converted);
//conversion2 at tenant level
conv2 = new MUOMConversion(Env.getCtx(), 0, null);
conv2.setC_UOM_ID(EACH_ID);
conv2.setC_UOM_To_ID(HOUR_ID);
conv2.setMultiplyRate(new BigDecimal("1.35"));
conv2.saveEx();
converted = MUOMConversion.convertProductTo(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"));
assertEquals(new BigDecimal("1.35"), converted);
converted = MUOMConversion.convertProductTo(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"), -1);
assertEquals(new BigDecimal("1.35"), converted);
converted = MUOMConversion.convertProductTo(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"), 1);
assertEquals(new BigDecimal("1.4"), converted);
//conversion3 at tenant and product level
conv3 = new MUOMConversion(Env.getCtx(), 0, null);
conv3.setM_Product_ID(PRODUCT_OAK_TREE);
conv3.setC_UOM_ID(EACH_ID);
conv3.setC_UOM_To_ID(HOUR_ID);
conv3.setMultiplyRate(new BigDecimal("0.75"));
conv3.saveEx();
CacheMgt.get().reset();
converted = MUOMConversion.convertProductTo(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"));
assertEquals(new BigDecimal("0.75"), converted);
converted = MUOMConversion.convertProductTo(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"), -1);
assertEquals(new BigDecimal("0.75"), converted);
converted = MUOMConversion.convertProductTo(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"), 1);
assertEquals(new BigDecimal("0.8"), converted);
converted = MUOMConversion.convertProductFrom(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"));
assertEquals(hour.round(conv3.getDivideRate(),true), converted);
conv3.deleteEx(true);
conv3 = null;
CacheMgt.get().reset();
converted = MUOMConversion.convertProductFrom(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"));
assertEquals(hour.round(conv2.getDivideRate(),true), converted);
converted = MUOMConversion.convertProductFrom(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"), 1);
assertEquals(conv2.getDivideRate().setScale(1, RoundingMode.HALF_UP), converted);
conv2.deleteEx(true);
conv2 = null;
converted = MUOMConversion.convertProductFrom(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"));
assertEquals(hour.round(conv1.getDivideRate(),true), converted);
converted = MUOMConversion.convertProductFrom(Env.getCtx(), PRODUCT_OAK_TREE, HOUR_ID, new BigDecimal("1"), 1);
assertEquals(conv1.getDivideRate().setScale(1, RoundingMode.HALF_UP), converted);
} finally {
DB.executeUpdateEx("DELETE FROM C_UOM_Conversion WHERE C_UOM_Conversion_ID=?", new Object[] {conv1.get_ID()}, null);
if (conv2 != null)
conv2.deleteEx(true);
if (conv3 != null)
conv3.deleteEx(true);
}
}
}