分爲Product Promotion、Order Promotion兩種。java
產品級別促銷例子:BOGOF(Buy One Get One Free)買一送一mysql
定義 Promotion itemtype sql
<!-- 繼承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 類(裏面的方法本身改本身的邏輯)apache
執行 ant all 生成促銷邏輯 jalo class 以下面 ProductBOGOFPromotion.java oracle
// // 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加入促銷組中用於後臺管理及維護app
<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,實現國際化.less
# ============================================================================= # 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。ide
訂單級別促銷例子:Order Threshold Discount Promotion 訂單閾值固定折扣sqlserver
<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類(裏面的方法本身改本身的邏輯)ui
// // 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 ""; } }