Skip to content

開始使用

TIP

建議使用 Web Worker 運行此模組,以免運算卡住主頁面,導致體驗不佳。

安裝

  1. 在根目錄建立 .npmrc

  2. 加入以下資訊

    shell
    @inner-products:registry=http://npm.bgmotion.com.tw
  3. 安裝套件

    shell
    npm i @inner-products/bgm-promotion-module

如何使用

簡易使用順序為:

1. 準備商品與待用優惠資料

例如:

ts
import {
  
Item
,
Promotion
} from '@inner-products/bgm-promotion-module' /** 購物車商品 */ const
items
:
Item
[] = [
{
commodity
: {
// key 很重要,模組使用 key 來區分商品
key
: '1',
// name 用於方便識別,不影響計算
name
: 'A-30ml',
price
: 140,
},
quantity
: 5
}, {
commodity
: {
key
: '2',
name
: 'A-50ml',
price
: 300,
},
quantity
: 1
} ] /** 可用優惠 */ const
promotions
:
Promotion
[] = [
{
key
: '1',
type
: 'commodity-offer',
name
: '周年慶大放送',
description
: '買 5 瓶 A 30ml 送 1 瓶 A 50ml',
constraints
: [],
contents
: [
{
condition
: [
{
list
: [
{
key
: '1',
name
: 'A-30ml',
price
: 140,
} ],
comparison
: {
logic
: 'greater-than-equal-to',
target
: 'purchase-quantity',
value
: 5
} } ],
result
: {
commodities
: [
{
key
: '2',
name
: 'A-50ml',
price
: 300,
} ],
quantity
: 1
} }, ], } ]

2. 取得最佳優惠結果

預設取得前 3 名,可以指定名次。

ts
/** 購物車商品 */
const items: Item[] = []
/** 可用優惠 */
const promotions: Promotion[] = []

const rankedResults = getPromotionsRanking({ items, promotions })
// 限制數量
const rankedResultsTop2 = getPromotionsRanking({
  items,
  promotions,
  topN: 2,
})

// 設定 autoOffsetMode 可以讓贈品自動抵銷商品
const offsetResults = getPromotionsRanking({
  items,
  promotions,
  autoOffsetMode: 'multiple-types-from-highest',
})

// 預設會保證相同輸入會有相同輸出,若希望結果不太一樣,可以將 reproducibility 設為 false
// 不建議這麼做就是惹。(○´・д・)ノ
const irreproducibilityResults = getPromotionsRanking({
  items,
  promotions,
  reproducibility: false,
})

// 可以使用 checkPromotionResult 檢查優惠結果
rankedResults.forEach((result) => {
  const errorMessage = checkPromotionResult(result)
})

💡 自動抵銷模式

細節請見文件

優惠結果為:

ts
/** 優惠結果 */
export interface PromotionResult<Data = undefined> {
  /** 原始購物清單 */
  originalItems: Item<Data>[];
  /** 剩餘的商品。沒有商品優惠可用,剩下來的商品 */
  remainingItems: Item<Data>[];
  /** 套用優惠的商品 */
  promotedList: PromotedListItem<Data>[];
  /** 可用贈品。 */
  giveawayList: GiveawayListItem<Data>[];
  /** 被贈品抵銷的商品。此區商品要被視為 0 元 */
  offsetList: OffsetListItem<Data>[];
  /** 所有已使用的優惠,彙整自 promotedList.promotion */
  usedPromotions: Promotion[];
}

/** 已套用優惠結果的商品 */
export interface PromotedListItem<Data = undefined> {
  /** 符合條件之商品與優惠結果 */
  appliedList: Array<{
    item: Item<Data>;
    /** 優惠結果 */
    result?: SelectableGiveaway<Data> | Arithmetic;
  }>;
  /** 原始優惠 */
  promotion: Promotion;
  /** 套用的優惠內容 */
  promotedContent: Promotion['contents'][number];
  /** 匹配的 Decision */
  matchedDecision?: PromotionDecision<Data>;
}

/** 可用贈品。
 *
 * 集結 promotedList.appliedList.result 中的 SelectableGiveaway 資料。
 *
 * 不包含以自動抵銷的贈品,自動抵銷的贈品在 offsetList 中。
 */
export interface GiveawayListItem<Data = undefined> {
  /** 贈品 */
  item: SelectableGiveaway<Data>;
  /** 原始優惠 */
  promotion: Promotion;
  /** 套用的優惠內容 */
  promotedContent: Promotion['contents'][number];
  /** 匹配的 Decision */
  matchedDecision?: PromotionDecision<Data>;
}
/** 被贈品抵銷的商品 */
export interface OffsetListItem<Data = undefined> {
  /** 商品 */
  item: Item<Data>;
  /** 原始優惠 */
  promotion: Promotion;
  /** 套用的優惠內容 */
  promotedContent: Promotion['contents'][number];
  /** 匹配的 Decision */
  matchedDecision?: PromotionDecision<Data>;
}

/** 已排名後的優惠結果 */
export interface RankedPromotionResult<Data> extends PromotionResult<Data> {
  /** 統計資料 */
  analyticalData: AnalyticalData;
}

3. 內容解析

以下功能可以進行結果解析

計算優惠結果金額:getAnalyticalData

💡 排名結果已有此資料

RankedPromotionResult 內已包含 analyticalData,不用再次進行計算

ts
/** 優惠結果 */
const promotionResult: PromotionResult = {}
const analyticalData = getAnalyticalData(promotionResult)

analyticalData 結果為:

ts
export interface AnalyticalData {
  /** 原始總價 */
  originalTotalPrice: number;
  /** 滿額累積,用於判斷滿額優惠是否可用
   *
   * 去除不計入滿額贈優惠後的金額總和
   */
  spendTotalPrice: number;
  /** 商品總價。包含剩餘商品和商品優惠(commodity-offer) */
  commodityTotalPrice: number;
  /** commodityTotalPrice 加入訂單優惠(order-offer)後的價格 */
  orderTotalPrice: number;
  /** 包含全部優惠(commodity-offer、order-offer、minimum-spend-offer)總價 */
  totalPrice: number;
}

取得每個商品優惠後的價格:getItemizedResult

ts
const promotionResult: PromotionResult = {}
const result = getItemizedResult(promotionResult)

result 結果為:

ts
export interface ItemizedResult<Data = undefined> {
  list: Array<{
    /** 商品 */
    commodity: Commodity<Data>;
    quantity: number;
    /** 優惠後的商品單價 */
    promotedPrice: number;
    /** 優惠來源 */
    sources: Array<{
      promotion: Promotion;
      promotedContent: Promotion['contents'][number];
      /** 匹配的 Decision */
      matchedDecision?: PromotionDecision<Data>;
    }>;
  }>;
}

5. 推薦更多商品

推薦模組會依照目前商品與待用優惠,推薦可再追加多少商品或金額,即可達成指定優惠

ts
const results = getRecommendResults({
  items,
  promotions,
})

results 結果為:

ts
/** 推薦商品與數量
 *
 * 來自 promotedContent.condition 內容
 */
type RecommendResultContentList<
  Data = undefined,
> = Array<{
  /** 候選商品 */
  commodities: Commodity<Data>[];
  /** 原始的比對條件 */
  comparison: Comparison;
  /** 推薦數量 */
  quantity: number;
}>

interface RecommendResult<Data = undefined> {
  /** 推薦列表。可以同時使用、不會互斥的優惠 */
  recommendedList: Array<{
    /** 與 recommendedItems 一起搭配的商品項目
     *
     * 如果條件是 price,則此欄位為空
     */
    bundledItems: Item<Data>[];
    /** 推薦內容。可能為商品或金額。 */
    content: {
      /** 推薦商品與數量 */
      list: RecommendResultContentList<Data>;
    } | {
      /** 還差多少金額
       *
       * 金額目前只實作差多少,不實作其他情境
       */
      price: number;
    };
    /** 差一點就能達成的條件 */
    promotedContent: Promotion['contents'][number];
    promotion: Promotion;
  }>;
}

檢查結果有效性

檢查結果是否有效,可用來確認資料是否被竄改

檢查優惠結果重現性:checkParamsAndRankedResult

檢查優惠結果是否與指定參數結果一致。

ts
/** 購物車商品 */
const items: Item[] = []
/** 可用優惠 */
const promotions: Promotion[] = []

const rankedResults = getPromotionsRanking({ items, promotions })

// 結果是否相同
const isSame = checkParamsAndRankedResult({
  items,
  promotions,
  results: rankedResults,
})