Hybris Promotion
分为Product Promotion、Order Promotion两种。
Product Promotion
产品级别促销例子:BOGOF(Buy One Get One Free)买一送一
定义 Promotion itemtype
<!-- 继承ProductPromotion -->
<itemtype code="ProductBOGOFPromotion" extends="ProductPromotion"
jaloclass="de.hybris.platform.promotions.jalo.ProductBOGOFPromotion" autocreate="true" generate="true">
<attributes>
<attribute
qualifier="qualifyingCount"
autocreate="true"
type="java.lang.Integer">
<defaultvalue>Integer.valueOf(2)</defaultvalue>
<description>定义属性:出发促销的数量 The number of products required in the cart to activate the promotion. (For standard BOGOF this is 2).</description>
<modifiers read="true" write="true" search="true" optional="true"/>
<persistence type="property"/>
</attribute>
<attribute
qualifier="freeCount"
autocreate="true"
type="java.lang.Integer">
<defaultvalue>Integer.valueOf(1)</defaultvalue>
<description>定义属性:赠送的数量 The number of products within the cart to give away free. (For standard BOGOF this is 1).</description>
<modifiers read="true" write="true" search="true" optional="true"/>
<persistence type="property"/>
</attribute>
<attribute qualifier="messageFired" type="localized:java.lang.String">
<description>发生促销时 要提示的信息 The message to show when the promotion has fired.</description>
<modifiers read="true" write="true" optional="true" />
<persistence type="property">
<columntype database="oracle">
<value>varchar2(4000)</value>
</columntype>
<columntype database="mysql">
<value>text</value>
</columntype>
<columntype database="sqlserver">
<value>nvarchar(max)</value>
</columntype>
<columntype database="hsqldb">
<value>LONGVARCHAR</value>
</columntype>
<columntype>
<value>varchar</value>
</columntype>
</persistence>
</attribute>
<attribute qualifier="messageCouldHaveFired" type="localized:java.lang.String">
<description>存在潜在促销时 要提示的信息 The message to show when the promotion could have potentially fire.</description>
<modifiers read="true" write="true" optional="true" />
<persistence type="property">
<columntype database="oracle">
<value>varchar2(4000)</value>
</columntype>
<columntype database="mysql">
<value>text</value>
</columntype>
<columntype database="sqlserver">
<value>nvarchar(max)</value>
</columntype>
<columntype database="hsqldb">
<value>LONGVARCHAR</value>
</columntype>
<columntype>
<value>varchar</value>
</columntype>
</persistence>
</attribute>
</attributes>
</itemtype>
ProductBOGOFPromotion 继承 ProductPromotion 扩展一些所需字段,创建新的 Model 对象 ProductBOGOFPromotionModel,同时生成一个 java 类(里面的方法自己改自己的逻辑)
执行 ant all 生成促销逻辑 jalo class 如下面 ProductBOGOFPromotion.java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package de.hybris.platform.promotions.jalo;
import de.hybris.platform.jalo.SessionContext;
import de.hybris.platform.jalo.c2l.Currency;
import de.hybris.platform.jalo.order.AbstractOrder;
import de.hybris.platform.promotions.jalo.GeneratedProductBOGOFPromotion;
import de.hybris.platform.promotions.jalo.PromotionOrderEntryConsumed;
import de.hybris.platform.promotions.jalo.PromotionResult;
import de.hybris.platform.promotions.jalo.PromotionsManager;
import de.hybris.platform.promotions.jalo.PromotionsManager.RestrictionSetResult;
import de.hybris.platform.promotions.result.PromotionEvaluationContext;
import de.hybris.platform.promotions.result.PromotionOrderView;
import de.hybris.platform.promotions.util.Helper;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import org.apache.log4j.Logger;
public class ProductBOGOFPromotion extends GeneratedProductBOGOFPromotion {
private static final Logger LOG = Logger.getLogger(ProductBOGOFPromotion.class);
public ProductBOGOFPromotion() {
}
//核心方法
@Override
public List<PromotionResult> evaluate(SessionContext ctx, PromotionEvaluationContext promoContext) {
ArrayList results = new ArrayList();
//获取符合促销的产品,并应用任何限制
RestrictionSetResult restrictResult = this.findEligibleProductsInBasket(ctx, promoContext);
//如果这些限制没有拒绝这项促销活动,并且在限制之后仍然允许使用产品
if(restrictResult.isAllowedToContinue() && !restrictResult.getAllowedProducts().isEmpty()) {
//获取促销属性值
int qualifyingCount = this.getQualifyingCount(ctx).intValue();
int freeCount = this.getFreeCount(ctx).intValue();
PromotionsManager promotionsManager = PromotionsManager.getInstance();
//创建一个视图仅包含允许产品的订单
PromotionOrderView orderView = promoContext.createView(ctx, this, restrictResult.getAllowedProducts());
PromotionResult result1;
//获取购物车中基本产品的实际数量
long realQuantity =orderView.getTotalQuantity(ctx)
while(realQuantity >= (long)qualifyingCount) {
promoContext.startLoggingConsumed(this);
Comparator remainingCount = PromotionEvaluationContext.createPriceComparator(ctx);
orderView.consumeFromTail(ctx, remainingCount, (long)(qualifyingCount - freeCount));
List freeItems = orderView.consumeFromHead(ctx, remainingCount, (long)freeCount);
ArrayList certainty = new ArrayList();
Iterator consumed = freeItems.iterator();
while(consumed.hasNext()) {
PromotionOrderEntryConsumed result = (PromotionOrderEntryConsumed)consumed.next();
result.setAdjustedUnitPrice(ctx, 0.0D);
double adjustment = result.getEntryPrice(ctx) * -1.0D;
certainty.add(promotionsManager.createPromotionOrderEntryAdjustAction(ctx, result.getOrderEntry(ctx), adjustment));
}
//createPromotionResult()最后一个参数传入的是1.0F表示该Promotion Fired
//createPromotionResult()最后一个参数传入的是0.75F表示该Promotion Could Fire.
result1 = promotionsManager.createPromotionResult(ctx, this, promoContext.getOrder(), 1.0F);
List consumed1 = promoContext.finishLoggingAndGetConsumed(this, true);
result1.setConsumedEntries(ctx, consumed1);
result1.setActions(ctx, certainty);
results.add(result1);
}
long remainingCount1 = orderView.getTotalQuantity(ctx);
if(orderView.getTotalQuantity(ctx) > 0L) {
promoContext.startLoggingConsumed(this);
orderView.consume(ctx, remainingCount1);
float certainty1 = (float)remainingCount1 / (float)qualifyingCount;
result1 = promotionsManager.createPromotionResult(ctx, this, promoContext.getOrder(), certainty1);
result1.setConsumedEntries(promoContext.finishLoggingAndGetConsumed(this, false));
results.add(result1);
}
}
return results;
}
//用于返回Promotion提示的消息
//根据该方法传入的promotionResult判断改Promotion是否Fired,根据结果返回相应的Message。
@Override
public String getResultDescription(SessionContext ctx, PromotionResult promotionResult, Locale locale) {
AbstractOrder order = promotionResult.getOrder(ctx);
if(order != null) {
Currency orderCurrency = order.getCurrency(ctx);
Integer qualifyingCount = this.getQualifyingCount(ctx);
Integer freeCount = this.getFreeCount(ctx);
//结果为Fired显示该Promotion的MessageFired字段
if(promotionResult.getFired(ctx)) {
double args2 = promotionResult.getTotalDiscount(ctx);
Object[] args1 = new Object[]{qualifyingCount, freeCount, Double.valueOf(args2), Helper.formatCurrencyAmount(ctx, locale, orderCurrency, args2)};
return formatMessage(this.getMessageFired(ctx), args1, locale);
}
//结果为Could Fire显示该Promotion的MessageFired字段
if(promotionResult.getCouldFire(ctx)) {
Object[] args = new Object[]{Long.valueOf(this.getQualifyingCount(ctx).longValue() - promotionResult.getConsumedCount(ctx, true)), qualifyingCount, freeCount};
return formatMessage(this.getMessageCouldHaveFired(ctx), args, locale);
}
}
return "";
}
}
编写hmc.xml文件,将自定义的Promotion加入促销组中用于后台管理及维护
<type name="ProductBOGOFPromotion" mode="append">
<organizer>
<editor mode="append">
<tab name="tab.promotion.properties" mode="append">
<section name="section.promotion.description" mode="replace" position="0">
<listlayout>
<text name="text.productbogofpromotion.detaileddescription" />
</listlayout>
</section>
<section name="section.promotion.products" mode="append">
<columnlayout leftwidth="370">
<row>
<text name="text.productbogofpromotion.qualifyingoverview" />
</row>
<row>
<attribute name="qualifyingCount" labelwidth="100" />
<text name="text.productbogofpromotion.qualifyingcount" />
</row>
<row>
<attribute name="freeCount" labelwidth="100" />
<text name="text.productbogofpromotion.freecount" />
</row>
</columnlayout>
</section>
</tab>
<tab name="tab.promotion.messages" mode="append">
<section name="section.promotion.messages" mode="append">
<columnlayout leftwidth="500">
<row>
<text name="text.promotion.messages.overview" />
</row>
<row>
<text name="text.productbogofpromotion.messagefired" />
</row>
<row>
<attribute name="messageFired" labelwidth="100" width="400">
<textareaeditor expanded="true" rows="5" />
</attribute>
<text name="text.productbogofpromotion.messagefiredargs" />
</row>
<row>
<text name="text.productbogofpromotion.messagecouldhavefired" />
</row>
<row>
<attribute name="messageCouldHaveFired" labelwidth="100" width="400">
<textareaeditor expanded="true" rows="5" />
</attribute>
<text name="text.productbogofpromotion.messagecouldhavefiredargs" />
</row>
</columnlayout>
</section>
</tab>
</editor>
</organizer>
</type>
将在hmc.xml中用到的key加入properties,实现国际化.
# =============================================================================
# promotions/hmc/resources/de/hybris/platform/promotions/hmc/locales_zh.properties
# BOGOF Promotion specific text
# =============================================================================
text.productbogofpromotion.detaileddescription= <div class=“promo-detailedDescription“> <div>购买特定数量的商品,即可免费获得指定数量的低价商品。</div> <div>例如: <i>买一送一</i>(也叫作<i>一件价买两件</i>)、<i>两件价买三件</i>或买就送产品的任何组合。</div> <div>商品必须都来自符合条件产品的范围。</div> </div>
text.productbogofpromotion.qualifyingoverview= 在此处指定用户的购物车中必须存在的项目数量以及其中免费项目的数量。在典型的“买一送一”促销中,符合条件的数量是 2,而免费数是 1(&Q)。
text.productbogofpromotion.qualifyingcount=要符合此促销的条件,用户的购物车中必须具有的单位数。
text.productbogofpromotion.freecount=免费赠送的单位数。该值必须小于符合条件的数量。
text.productbogofpromotion.messagefired=此促销开始后要显示的文本。
text.productbogofpromotion.messagecouldhavefired=要求产品数未满足时显示的文本。
text.productbogofpromotion.messagefiredargs= <div class=“promo-messageDetails“> <table border=“0”cellpadding=“0”cellspacing=“0”class=“promo-messageParams“> <thead><tr><th>位置</th><th>类型</th><th>描述</th></tr></thead> <tbody> <tr><td>0</td><td>长数</td><td>符合条件数</td></tr> <tr><td>1</td><td>长数</td><td>免费数</td></tr> <tr><td>2</td><td>双精度数</td><td>总折扣,为数字</td></tr> <tr><td>3</td><td>货币字符串</td><td>总折扣,为带规定格式的字符串</td></tr> </tbody> </table> </div>
text.productbogofpromotion.messagecouldhavefiredargs= <div class=“promo-messageDetails“> <table border=“0”cellpadding=“0”cellspacing=“0”class=“promo-messageParams“> <thead><tr><th>位置</th><th>类型</th><th>描述</th></tr></thead> <tbody> <tr><td>0</td><td>长数</td><td>满足促销条件尚需的产品数</td></tr> <tr><td>1</td><td>长数</td><td>符合条件数</td></tr> <tr><td>2</td><td>长数</td><td>免费数</td></tr> </tbody> </table> </div>
# =============================================================================
# promotions/hmc/resources/de/hybris/platform/promotions/hmc/locales_en.properties
# BOGOF Promotion specific text
# =============================================================================
text.productbogofpromotion.detaileddescription= \
<div class="promo-detailedDescription"> \
<div>Buy a certain number of items, get specified number of lowest valued items for free.</div> \
<div>For example: <i>Buy 1 get 1 free</i> (also known as <i>Buy 2 for the price of 1</i>), \
<i>Buy 3 for the price of 2</i> or any combination of paid for and free products.</div> \
<div>The items must all be from the range of qualifying products.</div> \
</div>
text.productbogofpromotion.qualifyingoverview= \
Here you specify how many items the user must have in their cart and how \
many of those will be free. In a classic "Buy One Get One Free" the qualifying count \
is 2 and the free count is 1.
text.productbogofpromotion.qualifyingcount=The number of units the user must have in their cart to qualify for this promotion.
text.productbogofpromotion.freecount=The number of units to give away free of charge. This must be less than the qualifying count.
text.productbogofpromotion.messagefired=Text displayed when this promotion fires.
text.productbogofpromotion.messagecouldhavefired=Text displayed when the required number of products has not been met.
text.productbogofpromotion.messagefiredargs= \
<div class="promo-messageDetails"> \
<table border="0" cellpadding="0" cellspacing="0" class="promo-messageParams"> \
<thead><tr><th>Position</th><th>Type</th><th>Description</th></tr></thead> \
<tbody> \
<tr><td>0</td><td>Long Number</td><td>The qualifying count</td></tr> \
<tr><td>1</td><td>Long Number</td><td>The free count</td></tr> \
<tr><td>2</td><td>Double Number</td><td>The total discount as a number</td></tr> \
<tr><td>3</td><td>Currency String</td><td>The total discount as a formatted string</td></tr> \
</tbody> \
</table> \
</div>
text.productbogofpromotion.messagecouldhavefiredargs= \
<div class="promo-messageDetails"> \
<table border="0" cellpadding="0" cellspacing="0" class="promo-messageParams"> \
<thead><tr><th>Position</th><th>Type</th><th>Description</th></tr></thead> \
<tbody> \
<tr><td>0</td><td>Long Number</td><td>How many more products are required to qualify for the promotion</td></tr> \
<tr><td>1</td><td>Long Number</td><td>The qualifying count</td></tr> \
<tr><td>2</td><td>Long Number</td><td>The free count</td></tr> \
</tbody> \
</table> \
</div>
重新启动服务,并在hac中勾选下图选项,进行Update。
Order Promotion
订单级别促销例子:Order Threshold Discount Promotion 订单阈值固定折扣
<itemtype code="OrderThresholdDiscountPromotion" extends="OrderPromotion"
jaloclass="de.hybris.platform.promotions.jalo.OrderThresholdDiscountPromotion" autocreate="true" generate="true">
<attributes>
<attribute
qualifier="thresholdTotals"
autocreate="true"
type="PromotionPriceRowCollectionType">
<description>The cart total value threshold in specific currencies.</description>
<persistence type="property" />
<modifiers read="true" write="true" search="false" initial="false" optional="true" partof="true"/>
</attribute>
<attribute
qualifier="discountPrices"
autocreate="true"
type="PromotionPriceRowCollectionType">
<description>Fixed price for discount in specific currencies.</description>
<persistence type="property" />
<modifiers read="true" write="true" search="false" initial="false" optional="true" partof="true"/>
</attribute>
<attribute qualifier="messageFired" type="localized:java.lang.String">
<description>The message to show when the promotion has fired.</description>
<modifiers read="true" write="true" optional="true" />
<persistence type="property">
<columntype database="oracle">
<value>varchar2(4000)</value>
</columntype>
<columntype database="mysql">
<value>text</value>
</columntype>
<columntype database="sqlserver">
<value>nvarchar(max)</value>
</columntype>
<columntype database="hsqldb">
<value>LONGVARCHAR</value>
</columntype>
<columntype>
<value>varchar</value>
</columntype>
</persistence>
</attribute>
<attribute qualifier="messageCouldHaveFired" type="localized:java.lang.String">
<description>The message to show when the promotion could have potentially fire.</description>
<modifiers read="true" write="true" optional="true" />
<persistence type="property">
<columntype database="oracle">
<value>varchar2(4000)</value>
</columntype>
<columntype database="mysql">
<value>text</value>
</columntype>
<columntype database="sqlserver">
<value>nvarchar(max)</value>
</columntype>
<columntype database="hsqldb">
<value>LONGVARCHAR</value>
</columntype>
<columntype>
<value>varchar</value>
</columntype>
</persistence>
</attribute>
</attributes>
</itemtype>
OrderThresholdDiscountPromotion继承OrderPromotion扩展一些所需字段,创建新的Model对象OrderThresholdDiscountPromotionModel,同时生成一个java类(里面的方法自己改自己的逻辑)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package de.hybris.platform.promotions.jalo;
import de.hybris.platform.jalo.ConsistencyCheckException;
import de.hybris.platform.jalo.SessionContext;
import de.hybris.platform.jalo.c2l.Currency;
import de.hybris.platform.jalo.order.AbstractOrder;
import de.hybris.platform.promotions.jalo.AbstractPromotionAction;
import de.hybris.platform.promotions.jalo.GeneratedOrderThresholdDiscountPromotion;
import de.hybris.platform.promotions.jalo.PromotionResult;
import de.hybris.platform.promotions.jalo.PromotionsManager;
import de.hybris.platform.promotions.result.PromotionEvaluationContext;
import de.hybris.platform.promotions.util.Helper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.log4j.Logger;
public class OrderThresholdDiscountPromotion extends GeneratedOrderThresholdDiscountPromotion {
private static final Logger log = Logger.getLogger(OrderThresholdDiscountPromotion.class.getName());
public OrderThresholdDiscountPromotion() {
}
public void remove(SessionContext ctx) throws ConsistencyCheckException {
deletePromotionPriceRows(ctx, this.getThresholdTotals(ctx));
deletePromotionPriceRows(ctx, this.getDiscountPrices(ctx));
super.remove(ctx);
}
public List<PromotionResult> evaluate(SessionContext ctx, PromotionEvaluationContext promoContext) {
ArrayList promotionResults = new ArrayList();
if(this.checkRestrictions(ctx, promoContext)) {
Double threshold = this.getPriceForOrder(ctx, this.getThresholdTotals(ctx), promoContext.getOrder(), "thresholdTotals");
if(threshold != null) {
Double discountPriceValue = this.getPriceForOrder(ctx, this.getDiscountPrices(ctx), promoContext.getOrder(), "discountPrices");
if(discountPriceValue != null) {
AbstractOrder order = promoContext.getOrder();
double orderSubtotalAfterDiscounts = getOrderSubtotalAfterDiscounts(ctx, order);
if(orderSubtotalAfterDiscounts >= threshold.doubleValue()) {
if(log.isDebugEnabled()) {
log.debug("(" + this.getPK() + ") evaluate: Subtotal " + orderSubtotalAfterDiscounts + ">" + threshold + ". Creating a discount action for value:" + discountPriceValue + ".");
}
PromotionResult certainty = PromotionsManager.getInstance().createPromotionResult(ctx, this, promoContext.getOrder(), 1.0F);
double result = discountPriceValue.doubleValue();
if(result > orderSubtotalAfterDiscounts) {
result = orderSubtotalAfterDiscounts;
}
certainty.addAction(ctx, PromotionsManager.getInstance().createPromotionOrderAdjustTotalAction(ctx, -result));
promotionResults.add(certainty);
} else {
if(log.isDebugEnabled()) {
log.debug("(" + this.getPK() + ") evaluate: Subtotal " + orderSubtotalAfterDiscounts + "<" + threshold + ". Skipping discount action.");
}
float certainty1 = (float)(orderSubtotalAfterDiscounts / threshold.doubleValue());
PromotionResult result1 = PromotionsManager.getInstance().createPromotionResult(ctx, this, promoContext.getOrder(), certainty1);
promotionResults.add(result1);
}
}
}
}
return promotionResults;
}
public String getResultDescription(SessionContext ctx, PromotionResult result, Locale locale) {
AbstractOrder order = result.getOrder(ctx);
if(order != null) {
Currency orderCurrency = order.getCurrency(ctx);
Double threshold = this.getPriceForOrder(ctx, this.getThresholdTotals(ctx), order, "thresholdTotals");
if(threshold != null) {
Double discountPriceValue = this.getPriceForOrder(ctx, this.getDiscountPrices(ctx), order, "discountPrices");
if(discountPriceValue != null) {
if(result.getFired(ctx)) {
Object[] orderSubtotalAfterDiscounts1 = new Object[]{threshold, Helper.formatCurrencyAmount(ctx, locale, orderCurrency, threshold.doubleValue()), discountPriceValue, Helper.formatCurrencyAmount(ctx, locale, orderCurrency, discountPriceValue.doubleValue())};
return formatMessage(this.getMessageFired(ctx), orderSubtotalAfterDiscounts1, locale);
}
if(result.getCouldFire(ctx)) {
double orderSubtotalAfterDiscounts = getOrderSubtotalAfterDiscounts(ctx, order);
double amountRequired = threshold.doubleValue() - orderSubtotalAfterDiscounts;
Object[] args = new Object[]{threshold, Helper.formatCurrencyAmount(ctx, locale, orderCurrency, threshold.doubleValue()), discountPriceValue, Helper.formatCurrencyAmount(ctx, locale, orderCurrency, discountPriceValue.doubleValue()), Double.valueOf(amountRequired), Helper.formatCurrencyAmount(ctx, locale, orderCurrency, amountRequired)};
return formatMessage(this.getMessageCouldHaveFired(ctx), args, locale);
}
}
}
}
return "";
}
}