IDEMPIERE-5260 Enhance Discount Schema for Fixed Pricing (#1313)

* IDEMPIERE-5260 Enhance Discount Schema for Fixed Pricing

* IDEMPIERE-5260 Enhance Discount Schema for Fixed Pricing

- Fix unit test error.
This commit is contained in:
hengsin 2022-05-12 00:39:58 +08:00 committed by GitHub
parent 416824b57c
commit 9e43a36862
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 334 additions and 12 deletions

View File

@ -0,0 +1,38 @@
SELECT register_migration_script('202204280337_IDEMPIERE-5260.sql') FROM dual
;
SET SQLBLANKLINES ON
SET DEFINE OFF
-- Apr 27, 2022 6:33:58 PM GMT+08:00
INSERT INTO AD_Element (AD_Element_ID,ColumnName,Updated,Name,Description,PrintName,AD_Element_UU,IsActive,Created,AD_Org_ID,CreatedBy,UpdatedBy,AD_Client_ID,EntityType) VALUES (203079,'FixedPrice',TO_DATE('2022-04-27 18:33:57','YYYY-MM-DD HH24:MI:SS'),'Fixed Price','Fixed Price for Product','Fixed Price','9b4054b1-0202-4025-92a1-0778028c124e','Y',TO_DATE('2022-04-27 18:33:57','YYYY-MM-DD HH24:MI:SS'),0,100,100,0,'D')
;
-- Apr 27, 2022 6:35:29 PM GMT+08:00
INSERT INTO AD_Column (AD_Column_ID,SeqNoSelection,IsSyncDatabase,Version,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsParent,FieldLength,IsSelectionColumn,IsKey,IsAutocomplete,IsAllowLogging,AD_Column_UU,Updated,IsUpdateable,ColumnName,Description,Name,IsAllowCopy,IsActive,CreatedBy,UpdatedBy,IsAlwaysUpdateable,AD_Client_ID,AD_Org_ID,Created,EntityType,IsEncrypted,IsSecure,FKConstraintType,AD_Element_ID,AD_Table_ID,AD_Reference_ID,IsToolbarButton) VALUES (213025,0,'N',0,'N','N','N',0,'N',10,'N','N','N','Y','670c6edf-b9f2-4a6f-9dd4-4af4daea32ac',TO_DATE('2022-04-27 18:35:28','YYYY-MM-DD HH24:MI:SS'),'Y','FixedPrice','Fixed Price for Product','Fixed Price','Y','Y',100,100,'N',0,0,TO_DATE('2022-04-27 18:35:28','YYYY-MM-DD HH24:MI:SS'),'D','N','N','N',203079,476,37,'N')
;
-- Apr 27, 2022 6:35:37 PM GMT+08:00
ALTER TABLE M_DiscountSchemaBreak ADD FixedPrice NUMBER DEFAULT 0
;
-- Apr 27, 2022 6:37:03 PM GMT+08:00
INSERT INTO AD_Field (SortNo,AD_Field_ID,IsEncrypted,DisplayLength,IsSameLine,IsHeading,SeqNo,IsCentrallyMaintained,IsReadOnly,AD_Org_ID,Updated,Description,Name,AD_Field_UU,IsDisplayed,IsFieldOnly,CreatedBy,UpdatedBy,IsActive,IsDisplayedGrid,SeqNoGrid,XPosition,IsQuickEntry,AD_Client_ID,Created,ColumnSpan,NumLines,IsAdvancedField,IsDefaultFocus,AD_Column_ID,EntityType,AD_Tab_ID) VALUES (0,204441,'N',0,'N','N',110,'Y','N',0,TO_DATE('2022-04-27 18:37:02','YYYY-MM-DD HH24:MI:SS'),'Fixed Price for Product','Fixed Price','d1c77e14-5d3d-4b0f-97f3-b800db7063b4','Y','N',100,100,'Y','Y',110,1,'N',0,TO_DATE('2022-04-27 18:37:02','YYYY-MM-DD HH24:MI:SS'),2,1,'N','N',213025,'D',406)
;
-- Apr 27, 2022 6:37:59 PM GMT+08:00
UPDATE AD_Field SET SeqNo=100, AD_Val_Rule_ID=NULL, IsDisplayed='Y', XPosition=1, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2022-04-27 18:37:59','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=5279
;
-- Apr 27, 2022 6:39:22 PM GMT+08:00
UPDATE AD_Field SET DisplayLogic='@IsBPartnerFlatDiscount@=N&@FixedPrice@=0', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2022-04-27 18:39:22','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=5279
;
-- Apr 27, 2022 6:40:12 PM GMT+08:00
UPDATE AD_Field SET DisplayLogic='@IsBPartnerFlatDiscount@=N&@BreakDiscount@=0', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2022-04-27 18:40:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=204441
;
-- Apr 27, 2022 6:40:25 PM GMT+08:00
UPDATE AD_Column SET DefaultValue='0',Updated=TO_DATE('2022-04-27 18:40:25','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=213025
;

View File

@ -0,0 +1,35 @@
SELECT register_migration_script('202204280337_IDEMPIERE-5260.sql') FROM dual
;
-- Apr 27, 2022 6:33:58 PM GMT+08:00
INSERT INTO AD_Element (AD_Element_ID,ColumnName,Updated,Name,Description,PrintName,AD_Element_UU,IsActive,Created,AD_Org_ID,CreatedBy,UpdatedBy,AD_Client_ID,EntityType) VALUES (203079,'FixedPrice',TO_TIMESTAMP('2022-04-27 18:33:57','YYYY-MM-DD HH24:MI:SS'),'Fixed Price','Fixed Price for Product','Fixed Price','9b4054b1-0202-4025-92a1-0778028c124e','Y',TO_TIMESTAMP('2022-04-27 18:33:57','YYYY-MM-DD HH24:MI:SS'),0,100,100,0,'D')
;
-- Apr 27, 2022 6:35:29 PM GMT+08:00
INSERT INTO AD_Column (AD_Column_ID,SeqNoSelection,IsSyncDatabase,Version,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsParent,FieldLength,IsSelectionColumn,IsKey,IsAutocomplete,IsAllowLogging,AD_Column_UU,Updated,IsUpdateable,ColumnName,Description,Name,IsAllowCopy,IsActive,CreatedBy,UpdatedBy,IsAlwaysUpdateable,AD_Client_ID,AD_Org_ID,Created,EntityType,IsEncrypted,IsSecure,FKConstraintType,AD_Element_ID,AD_Table_ID,AD_Reference_ID,IsToolbarButton) VALUES (213025,0,'N',0,'N','N','N',0,'N',10,'N','N','N','Y','670c6edf-b9f2-4a6f-9dd4-4af4daea32ac',TO_TIMESTAMP('2022-04-27 18:35:28','YYYY-MM-DD HH24:MI:SS'),'Y','FixedPrice','Fixed Price for Product','Fixed Price','Y','Y',100,100,'N',0,0,TO_TIMESTAMP('2022-04-27 18:35:28','YYYY-MM-DD HH24:MI:SS'),'D','N','N','N',203079,476,37,'N')
;
-- Apr 27, 2022 6:35:37 PM GMT+08:00
ALTER TABLE M_DiscountSchemaBreak ADD COLUMN FixedPrice NUMERIC DEFAULT 0
;
-- Apr 27, 2022 6:37:03 PM GMT+08:00
INSERT INTO AD_Field (SortNo,AD_Field_ID,IsEncrypted,DisplayLength,IsSameLine,IsHeading,SeqNo,IsCentrallyMaintained,IsReadOnly,AD_Org_ID,Updated,Description,Name,AD_Field_UU,IsDisplayed,IsFieldOnly,CreatedBy,UpdatedBy,IsActive,IsDisplayedGrid,SeqNoGrid,XPosition,IsQuickEntry,AD_Client_ID,Created,ColumnSpan,NumLines,IsAdvancedField,IsDefaultFocus,AD_Column_ID,EntityType,AD_Tab_ID) VALUES (0,204441,'N',0,'N','N',110,'Y','N',0,TO_TIMESTAMP('2022-04-27 18:37:02','YYYY-MM-DD HH24:MI:SS'),'Fixed Price for Product','Fixed Price','d1c77e14-5d3d-4b0f-97f3-b800db7063b4','Y','N',100,100,'Y','Y',110,1,'N',0,TO_TIMESTAMP('2022-04-27 18:37:02','YYYY-MM-DD HH24:MI:SS'),2,1,'N','N',213025,'D',406)
;
-- Apr 27, 2022 6:37:59 PM GMT+08:00
UPDATE AD_Field SET SeqNo=100, AD_Val_Rule_ID=NULL, IsDisplayed='Y', XPosition=1, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2022-04-27 18:37:59','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=5279
;
-- Apr 27, 2022 6:39:22 PM GMT+08:00
UPDATE AD_Field SET DisplayLogic='@IsBPartnerFlatDiscount@=N&@FixedPrice@=0', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2022-04-27 18:39:22','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=5279
;
-- Apr 27, 2022 6:40:12 PM GMT+08:00
UPDATE AD_Field SET DisplayLogic='@IsBPartnerFlatDiscount@=N&@BreakDiscount@=0', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2022-04-27 18:40:12','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=204441
;
-- Apr 27, 2022 6:40:25 PM GMT+08:00
UPDATE AD_Column SET DefaultValue='0',Updated=TO_TIMESTAMP('2022-04-27 18:40:25','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=213025
;

View File

@ -22,7 +22,7 @@ import org.compiere.util.KeyNamePair;
/** Generated Interface for M_DiscountSchemaBreak
* @author iDempiere (generated)
* @version Release 9
* @version Release 10
*/
public interface I_M_DiscountSchemaBreak
{
@ -44,8 +44,8 @@ public interface I_M_DiscountSchemaBreak
/** Column name AD_Client_ID */
public static final String COLUMNNAME_AD_Client_ID = "AD_Client_ID";
/** Get Client.
* Client/Tenant for this installation.
/** Get Tenant.
* Tenant for this installation.
*/
public int getAD_Client_ID();
@ -53,12 +53,12 @@ public interface I_M_DiscountSchemaBreak
public static final String COLUMNNAME_AD_Org_ID = "AD_Org_ID";
/** Set Organization.
* Organizational entity within client
* Organizational entity within tenant
*/
public void setAD_Org_ID (int AD_Org_ID);
/** Get Organization.
* Organizational entity within client
* Organizational entity within tenant
*/
public int getAD_Org_ID();
@ -104,6 +104,19 @@ public interface I_M_DiscountSchemaBreak
*/
public int getCreatedBy();
/** Column name FixedPrice */
public static final String COLUMNNAME_FixedPrice = "FixedPrice";
/** Set Fixed Price.
* Fixed Price for Product
*/
public void setFixedPrice (BigDecimal FixedPrice);
/** Get Fixed Price.
* Fixed Price for Product
*/
public BigDecimal getFixedPrice();
/** Column name IsActive */
public static final String COLUMNNAME_IsActive = "IsActive";

View File

@ -255,16 +255,33 @@ public class MDiscountSchema extends X_M_DiscountSchema implements ImmutablePOSu
M_Product_ID, M_Product_Category_ID, BPartnerFlatDiscount);
// nothing to calculate
if (discount == null || discount.signum() == 0)
return Price;
{
BigDecimal fixedPrice = calculateFixedPrice(Qty, Price, M_Product_ID, M_Product_Category_ID);
if (fixedPrice != null)
return fixedPrice;
else
return Price;
}
//
BigDecimal onehundred = Env.ONEHUNDRED;
BigDecimal multiplier = (onehundred).subtract(discount);
multiplier = multiplier.divide(onehundred, 6, RoundingMode.HALF_UP);
BigDecimal newPrice = Price.multiply(multiplier);
BigDecimal newPrice = calculateDiscountedPrice(Price, discount);
if (log.isLoggable(Level.FINE)) log.fine("=>" + newPrice);
return newPrice;
} // calculatePrice
/**
*
* @param price input price
* @param discount discount percentage, for e.g 5.00 for 5%
* @return discounted price
*/
public static BigDecimal calculateDiscountedPrice(BigDecimal price, BigDecimal discount) {
BigDecimal onehundred = Env.ONEHUNDRED;
BigDecimal multiplier = (onehundred).subtract(discount);
multiplier = multiplier.divide(onehundred, 6, RoundingMode.HALF_UP);
BigDecimal newPrice = price.multiply(multiplier);
return newPrice;
}
/**
* Calculate Discount Percentage
* @param Qty quantity
@ -342,6 +359,56 @@ public class MDiscountSchema extends X_M_DiscountSchema implements ImmutablePOSu
return Env.ZERO;
} // calculateDiscount
/**
* Get fix discounted price
* @param Qty quantity
* @param Price price
* @param M_Product_ID product
* @param M_Product_Category_ID category
* @return fix discounted price or zero
*/
private BigDecimal calculateFixedPrice (BigDecimal Qty, BigDecimal Price,
int M_Product_ID, int M_Product_Category_ID)
{
if (DISCOUNTTYPE_FlatPercent.equals(getDiscountType()) || DISCOUNTTYPE_Formula.equals(getDiscountType())
|| DISCOUNTTYPE_Pricelist.equals(getDiscountType()))
{
return null;
}
// Price Breaks
getBreaks(false);
BigDecimal Amt = Price.multiply(Qty);
for (int i = 0; i < m_breaks.length; i++)
{
MDiscountSchemaBreak br = m_breaks[i];
if (!br.isActive())
continue;
if (isQuantityBased())
{
if (!br.applies(Qty, M_Product_ID, M_Product_Category_ID))
continue;
}
else
{
if (!br.applies(Amt, M_Product_ID, M_Product_Category_ID))
continue;
}
// Line applies
if (!br.isBPartnerFlatDiscount())
{
if (br.getFixedPrice() != null && br.getFixedPrice().signum() > 0)
{
return br.getFixedPrice();
}
}
return null;
} // for all breaks
return null;
} // calculateDiscount
/**
* Before Save

View File

@ -150,9 +150,14 @@ public class MDiscountSchemaBreak extends X_M_DiscountSchemaBreak implements Imm
sb.append(",M_Product_ID=").append(getM_Product_ID());
sb.append(",Break=").append(getBreakValue());
if (isBPartnerFlatDiscount())
{
sb.append(",FlatDiscount");
}
else
{
sb.append(",Discount=").append(getBreakDiscount());
sb.append(",FixedPrice=").append(getFixedPrice());
}
sb.append ("]");
return sb.toString ();
} // toString

View File

@ -25,7 +25,7 @@ import org.compiere.util.KeyNamePair;
/** Generated Model for M_DiscountSchemaBreak
* @author iDempiere (generated)
* @version Release 9 - $Id$ */
* @version Release 10 - $Id$ */
@org.adempiere.base.Model(table="M_DiscountSchemaBreak")
public class X_M_DiscountSchemaBreak extends PO implements I_M_DiscountSchemaBreak, I_Persistent
{
@ -33,7 +33,7 @@ public class X_M_DiscountSchemaBreak extends PO implements I_M_DiscountSchemaBre
/**
*
*/
private static final long serialVersionUID = 20220116L;
private static final long serialVersionUID = 20220428L;
/** Standard Constructor */
public X_M_DiscountSchemaBreak (Properties ctx, int M_DiscountSchemaBreak_ID, String trxName)
@ -135,6 +135,25 @@ public class X_M_DiscountSchemaBreak extends PO implements I_M_DiscountSchemaBre
return bd;
}
/** Set Fixed Price.
@param FixedPrice Fixed Price for Product
*/
public void setFixedPrice (BigDecimal FixedPrice)
{
set_Value (COLUMNNAME_FixedPrice, FixedPrice);
}
/** Get Fixed Price.
@return Fixed Price for Product
*/
public BigDecimal getFixedPrice()
{
BigDecimal bd = (BigDecimal)get_Value(COLUMNNAME_FixedPrice);
if (bd == null)
return Env.ZERO;
return bd;
}
/** Set B.Partner Flat Discount.
@param IsBPartnerFlatDiscount Use flat discount defined on Business Partner Level
*/

View File

@ -0,0 +1,145 @@
/***********************************************************************
* 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 static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import org.compiere.model.MBPartner;
import org.compiere.model.MDiscountSchema;
import org.compiere.model.MDiscountSchemaBreak;
import org.compiere.model.MOrder;
import org.compiere.model.MOrderLine;
import org.compiere.model.MPriceList;
import org.compiere.model.MPriceListVersion;
import org.compiere.model.MProduct;
import org.compiere.model.MProductPrice;
import org.compiere.process.DocAction;
import org.compiere.util.CacheMgt;
import org.compiere.util.Env;
import org.compiere.util.TimeUtil;
import org.idempiere.test.AbstractTestCase;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
/**
*
* @author hengsin
*
*/
public class MDiscountSchemaTest extends AbstractTestCase {
private static final int FIVE_PERCENT_IF_100_ID=102;
private static final int PRODUCT_MULCH_ID = 137;
private static final int BP_JOE_BLOCK_ID = 118;
public MDiscountSchemaTest() {
}
@Test
@Order(1)
public void testPercentageDiscount() {
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
//Joe Block
order.setBPartner(MBPartner.get(Env.getCtx(), BP_JOE_BLOCK_ID));
order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
order.setDatePromised(today);
order.saveEx();
MOrderLine line1 = new MOrderLine(order);
line1.setLine(10);
//Azalea Bush
line1.setProduct(MProduct.get(Env.getCtx(), PRODUCT_MULCH_ID));
line1.setQty(new BigDecimal("100"));
line1.setDatePromised(today);
line1.saveEx();
MDiscountSchema schema = new MDiscountSchema(Env.getCtx(), FIVE_PERCENT_IF_100_ID, getTrxName());
MDiscountSchemaBreak[] breaks = schema.getBreaks(false);
assertTrue(breaks.length > 0, "No discount schema breaks");
MPriceList priceList = MPriceList.get(order.getM_PriceList_ID());
MPriceListVersion priceListVersion = priceList.getPriceListVersion(order.getDateOrdered());
MProductPrice[] productPrice = priceListVersion.getProductPrice(" AND M_Product_ID="+PRODUCT_MULCH_ID);
assertEquals(1, productPrice.length, "Unexpected number of ProductPrice record");
BigDecimal discounted = MDiscountSchema.calculateDiscountedPrice(productPrice[0].getPriceStd(), breaks[0].getBreakDiscount());
assertEquals(discounted.setScale(2, RoundingMode.HALF_UP), line1.getPriceActual().setScale(2, RoundingMode.HALF_UP), "Unexpected Order Line price");
}
@Test
@Order(2)
public void testFixedPriceDiscount() {
BigDecimal fixedPrice = new BigDecimal("1.00");
MDiscountSchema schema = new MDiscountSchema(Env.getCtx(), FIVE_PERCENT_IF_100_ID, getTrxName());
MDiscountSchemaBreak discountBreak = null;
try {
discountBreak = new MDiscountSchemaBreak(Env.getCtx(), 0, null);
discountBreak.setM_DiscountSchema_ID(schema.getM_DiscountSchema_ID());
discountBreak.setBreakDiscount(new BigDecimal("0.00"));
discountBreak.setBreakValue(new BigDecimal("10"));
discountBreak.setFixedPrice(fixedPrice);
discountBreak.setM_Product_ID(PRODUCT_MULCH_ID);
discountBreak.setIsBPartnerFlatDiscount(false);
discountBreak.setIsActive(true);
discountBreak.setSeqNo(20);
discountBreak.saveEx();
CacheMgt.get().reset(MDiscountSchema.Table_Name, schema.get_ID());
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
//Joe Block
order.setBPartner(MBPartner.get(Env.getCtx(), BP_JOE_BLOCK_ID));
order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
order.setDatePromised(today);
order.saveEx();
MOrderLine line1 = new MOrderLine(order);
line1.setLine(10);
//Azalea Bush
line1.setProduct(MProduct.get(Env.getCtx(), PRODUCT_MULCH_ID));
line1.setQty(new BigDecimal("10"));
line1.setDatePromised(today);
line1.saveEx();
assertEquals(fixedPrice, line1.getPriceActual().setScale(2, RoundingMode.HALF_UP), "Unexpected Order Line price");
} finally {
if (discountBreak != null && discountBreak.get_ID() > 0)
discountBreak.deleteEx(true);
}
}
}