OrderBuilder
An Order
builder API makes business-logic development more easier.
Features
- Arbitrary-precision order-calculating based on decimal.js.
- Common
Pipe
pattern API.
Core API
-
OrderBuilder
- config
- PolicyPickStrategy
ItemBasedPolicyPickStrategy
OrderBasedPolicyPickStrategy
- DiscountMethod
PriceWeightedAverageDiscountMethod
QuantityWeightedAverageDiscountMethod
- RoundStrategy
EveryCalculationRoundStrategy
FinalPriceRoundStrategy
NoRoundRoundStrategy
-
Policy
- discount
ValueDiscount
PercentageDiscount
StepValueDiscount
StepPercentageDiscount
ItemGiveawayDiscount
-
Condition
- threshold
PriceThreshold
QuantityThreshold
- requirement
ItemIncluded
ItemRequired
QuantityRequired
ItemExcluded
- validator
Documents
OrderBuilder
Arguments |
index |
Type |
Required |
overload1 |
|
|
|
builder |
0 |
OrderBuilder |
false |
overload2 |
|
|
|
options |
0 |
OrderBuilderConstructor |
false |
OrderBuilderConstructor
Properties |
Type |
Required |
policies |
Policy[] |
true |
discountMethod |
"price-weighted-average" | "quantity-weighted-average" |
false |
roundStrategy |
"every-calculation" | "final-price-only" | "no-round" |
false |
logistics |
OrderLogistics |
false |
OrderBuilder |
Return |
Description |
Properties |
|
|
config |
OrderConfig |
Get the configuration of current builder. |
policies |
Policies[] |
Get all the policies of the builder. (activated + none-activated)
|
hasBuiltOrders |
boolean |
Checks whether this builder has already built any instance of order. |
Methods |
|
|
build |
Order |
Create an order instance, and lock the access of mutations on policy. |
clone |
OrderBuilder |
Create a new OrderBuilder instance based on current instance. |
addPolicy(policy: Policies) |
this |
Push policy instance(s) into builder.policies (can active as builder.hasBuiltOrders is false ). |
addPolicy(policies: Policies[]) |
|
|
removePolicy(policy: Policy) |
this |
Remove policy instance(s) from builder.policies based on instance reference or policy.id (can active as builder.hasBuiltOrders is false ) |
removePolicy(policy: string) |
|
|
removePolicy(policies: Policy[]) |
|
|
removePolicy(policies: string[]) |
|
|
getPolicy(policyId: string) |
Policy | undefined |
Get the specified policy instance by policy.id
|
setLogistics(logistics: OrderLogistics) |
this |
Set the logistics of the order. |
Order
OrderConstructor
Properties |
Type |
Required |
items |
OrderItem[] |
true |
coupons |
string[] |
false |
Order |
Return |
Description |
Properties |
|
|
builder |
OrderBuilder |
Get the the owner of current order. |
config |
OrderConfig |
Get the configuration from its builder. |
policies |
Policies[] |
Get all the policies from its builder. |
coupons |
string[] |
Get all the coupons from current order. |
items |
OrderItem[] |
Get all the items from current order. |
itemManager |
OrderItemManager |
Get the item manager. |
itemRecords |
OrderItemRecord[] |
Get all the detail-records of item based on policies. |
discounts |
PolicyDiscountDescription[] |
Get the descriptions of discount based on applied-policies. |
discountValue |
number |
Get total discount in this order. |
itemValue |
number |
Get total value of items in this order. |
itemQuantity |
number |
Get total quantity of items in this order. |
price |
number |
Get final price of order. |
logistics |
OrderLogistics |
Get logistics config of current order. |
logisticsRecord |
OrderItemRecord |
Get logistics in the type of order-item-record. |
Methods |
|
|
subOrder(subCondition: SubOrderCondition) |
Order |
Create a new instance of Order based on the sub-condition on items and coupons . |
addCoupon(coupon: string) |
this |
Push coupon(s) into order.coupons . |
addCoupon(coupons: string[]) |
|
|
removeCoupon(coupon: string) |
this |
Remove coupons(s) from order.coupons
|
removeCoupon(coupons: string[]) |
|
|
addItem(item: OrderItem) |
this |
Push item(s) into order.items . |
addItem(items: OrderItem[]) |
|
|
removeItem(id: string, quantity: number) |
this |
Remove item(s) from order.items . |
removeItem(item: OrderItem) |
|
|
removeItem(item: string) |
|
|
removeItem(items: OrderItem[]) |
|
|
removeItem(items: string[]) |
|
|
Discount
Arguments |
index |
Type |
Required |
overload1 |
|
|
|
value |
0 |
number |
true |
condition |
1 |
Condition |
false |
options |
2 |
DiscountOptions |
false |
overload2 |
|
|
|
value |
0 |
number |
true |
conditions |
1 |
Condition[] |
false |
options |
2 |
DiscountOptions |
false |
overload3 |
|
|
|
value |
0 |
number |
true |
options |
1 |
DiscountOptions |
false |
Interfaces Remark
Interface |
Type |
Policies |
Policy | Policy[]
|
RemovePolicy |
Policy | string
|
Basic Usage
import { OrderBuilder, ValueDiscount, PercentageDiscount } from '@rytass/order-builder';
const policy1 = new ValueDiscount(100, { id: 'DISCOUNT_1' });
const policy2 = new PercentageDiscount(0.8, { id: 'DISCOUNT_2' });
// Initialize an order-builder.
const builder = new OrderBuilder({
policyPickStrategy: 'item-based',
discountMethod: 'price-weighted-average',
roundStrategy: 'every-calculation',
policies: [policy1],
});
// Allowing conditional business-logic to determinate whether to mutate policies.
const order = builder
.addPolicy(policy2) // 20% off
.removePolicy('DISCOUNT_1') // or .removePolicy(policy1)
.build({
items: [
{
id: 'ItemA',
name: 'Foo',
unitPrice: 100,
quantity: 2,
},
{
id: 'ItemB',
name: 'Bar',
unitPrice: 50,
quantity: 1,
},
],
});
builder.getPolicy('DISCOUNT_1'); // undefined
builder.getPolicy('DISCOUNT_2'); // reference to `policy2`
order.price // 250 * 0.8 = 200
order.itemRecords
// [
// {
// itemId: 'ItemA-1',
// originItem: { id: 'ItemA', name: 'Foo', unitPrice: 100 },
// initialValue: 100,
// discountValue: 20,
// finalPrice: 80,
// discountRecords: [{ policyId: 'DISCOUNT_2', discountValue: 20 }],
// appliedPolicies: [policy2]
// },
// {
// itemId: 'ItemA-2',
// originItem: { id: 'ItemA', name: 'Foo', unitPrice: 100 },
// initialValue: 100,
// discountValue: 20,
// finalPrice: 80,
// discountRecords: [{ policyId: 'DISCOUNT_2', discountValue: 20 }],
// appliedPolicies: [policy2]
// },
// {
// itemId: 'ItemB-1',
// originItem: { id: 'ItemB', name: 'Bar', unitPrice: 50 },
// initialValue: 50,
// discountValue: 10,
// finalPrice: 40,
// discountRecords: [{ policyId: 'DISCOUNT_2', discountValue: 10 }],
// appliedPolicies: [policy2]
// }
// ]
Examples
Custom OrderItem Dto
import {
/** Core */
OrderItem,
OrderBuilder,
/** Conditions */
ItemIncluded,
PriceThreshold,
QuantityThreshold,
/** Discount policies */
ValueDiscount,
StepValueDiscount,
PercentageDiscount,
ItemGiveawayDiscount,
StepPercentageDiscount,
} from '@rytass/order-builder';
// Allow custom order-item dto.
type TestOrderItem = OrderItem<{
category: string,
brand: string,
}>;
const items: TestOrderItem[] = [
{
id: 'A',
name: '外套A',
unitPrice: 1000,
quantity: 1,
category: 'jacket',
brand: 'AJE',
},
{
id: 'B',
name: '外套B',
unitPrice: 1500,
quantity: 1,
category: 'jacket',
brand: 'N21',
},
{
id: 'C',
name: '鞋子C',
unitPrice: 2000,
quantity: 1,
category: 'shoes',
brand: 'N21',
},
{
id: 'D',
name: '鞋子D',
unitPrice: 2500,
quantity: 1,
category: 'shoes',
brand: 'Preen',
},
{
id: 'E',
name: '鞋子E',
unitPrice: 3000,
quantity: 1,
category: 'shoes',
brand: 'Preen',
},
{
id: 'F',
name: '飾品F',
unitPrice: 4000,
quantity: 1,
category: 'accessory',
brand: 'Swell',
},
{
id: 'G',
name: '飾品G',
unitPrice: 5000,
quantity: 1,
category: 'accessory',
brand: 'Swell',
},
{
id: 'H',
name: '飾品H',
unitPrice: 6000,
quantity: 1,
category: 'accessory',
brand: 'Swell',
},
{
id: 'I',
name: '飾品I',
unitPrice: 6500,
quantity: 1,
category: 'accessory',
brand: 'Boyy',
},
];
Scenario 1 - 1
/**
* 情境編號:甲
*
* 決策法:擇優取一 (order-based)
*
* 情境敘述:
* 1. 指定商品(A~F)滿3件 打9折
* 2. 指定商品(C~I)每5000元 折600元
* 3. 指定分類(鞋子)滿4000元 送最低價商品
* 4. 指定分類(Swell)每1件 打9折
*
* 輸入:(1|2) & (3|4)
* 預期結果:2 + 4
* 購物車顯示金額 24856
*/
// 1. 指定商品(A~F)滿3件 打9折
const policy1 = new PercentageDiscount(
0.9,
new ItemIncluded({
items: ['A', 'B', 'C', 'D', 'E', 'F'],
threshold: 3,
}),
{ onlyMatched: true },
);
// 2. 指定商品(C~I)每5000元 折600元
const policy2 = new StepValueDiscount(
5000,
600,
new ItemIncluded<TestOrderItem>({
items: ['C', 'D', 'E', 'F', 'G', 'H', 'I'],
}),
{ stepUnit: 'price', onlyMatched: true },
);
// 3. 指定分類(鞋子)滿4000元 送最低價商品
const policy3 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
scope: 'category',
items: ['shoes'],
conditions: [new PriceThreshold(4000)],
}),
{ onlyMatched: true },
);
// 4. 指定分類(Swell)每1件 打9折
const policy4 = new StepPercentageDiscount(
1,
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'brand',
items: ['Swell'],
}),
{
stepUnit: 'quantity',
onlyMatched: true,
}
);
// 擇優 (1|2) & (3|4)
const builder = new OrderBuilder({
policyPickStrategy: 'order-based',
policies: [
[policy1, policy2],
[policy3, policy4],
],
});
const order = builder.build({ items });
order.price // 24856
Scenario 2
/**
* 情境編號:乙
*
* 決策法:擇優取一 (order-based)
*
* 情境敘述:
* 1. 指定商品(B~E)無條件 送最低價商品
* 2. 指定商品(C~I)每3000元 折200元
* 3. 指定分類(N21)滿2件 折100元
* 4. 指定分類(飾品)每2件 打9折
* 5. 指定分類(Boyy)滿5000元 打9折
* 6. 全館 滿6件 送最低價商品
*
* 輸入:(1|2) & (3|4|5) & 6
* 輸出:2 + 4 + 6
* 購物車顯示金額 24868
*/
// 1. 指定商品(B~E)無條件 送最低價商品
const policy1 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['B', 'C', 'D', 'E'],
}),
{ onlyMatched: true }
);
// 2. 指定商品(C~I)每3000元 折200元
const policy2 = new StepValueDiscount(
3000,
200,
new ItemIncluded<TestOrderItem>({
items: ['C', 'D', 'E', 'F', 'G', 'H', 'I'],
scope: 'id',
}),
{ stepUnit: 'price', onlyMatched: true }
);
// 3. 指定分類(N21)滿2件 折100元
const policy3 = new ValueDiscount(
100,
new ItemIncluded<TestOrderItem>({
items: ['N21'],
scope: 'brand',
threshold: 2,
}),
{ onlyMatched: true }
);
// 4. 指定分類(飾品)每2件 打9折
const policy4 = new StepPercentageDiscount(
2,
0.9,
new ItemIncluded<TestOrderItem>({
items: ['accessory'],
scope: 'category',
}),
{ stepUnit: 'quantity', onlyMatched: true }
);
// 5. 指定分類(Boyy)滿5000元 打9折
const policy5 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
items: ['Boyy'],
scope: 'brand',
conditions: [new PriceThreshold(5000)],
}),
{ id: 'P5', onlyMatched: true }
);
// 6. 全館 滿6件 送最低價商品
const policy6 = new ItemGiveawayDiscount(1, new QuantityThreshold(6));
// 擇優 (1|2) & (3|4|5) & 6
const builder = new OrderBuilder({
policyPickStrategy: 'order-based',
policies: [
[policy1, policy2],
[policy3, policy4, policy5],
policy6,
],
});
const order = builder.build({ items });
order.price // 24868
Scenario 3
/**
* 情境編號:丙 - 1
*
* 決策法:擇優取一 (order-based)
*
* 情境敘述:
* 1. 全館 滿6件 送最低價商品
* 2. 指定分類(Boyy)滿5000元 打9折
* 3. 指定商品(B~E)無條件 送最低價商品
* 4. 指定商品(C~I)每3000元 折200元
* 5. 指定分類(鞋子)滿4000元 送最低價商品
*
* 輸入:1 & 2 & 3 & 4 & 5
* 輸出:1 + 2 + 3 + 4 + 5
* 購物車顯示金額 24677
*/
// 1. 全館 滿6件 送最低價商品
const policy1 = new ItemGiveawayDiscount(
1,
new QuantityThreshold(6),
);
// 2. 指定分類(Boyy)滿5000元 打9折
const policy2 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
items: ['Boyy'],
scope: 'brand',
conditions: [new PriceThreshold(5000)],
}),
{ onlyMatched: true }
);
// 3. 指定商品(B~E)無條件 送最低價商品
const policy3 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['B''C', 'D', 'E'],
}),
{ onlyMatched: true }
);
// 4. 指定商品(C~I)每3000元 折200元
const policy4 = new StepValueDiscount(
3000,
200,
new ItemIncluded<TestOrderItem>({
items: ['C', 'D', 'E', 'F', 'G', 'H', 'I'],
}),
{ stepUnit: 'price', onlyMatched: true }
);
// 5. 指定分類(鞋子)滿4000元 送最低價商品
const policy5 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['shoes'],
scope: 'category',
conditions: [new PriceThreshold(4000)],
}),
{ onlyMatched: true }
);
const builder = new OrderBuilder({
policyPickStrategy: 'order-based',
policies: [
policy1,
policy2,
policy3,
policy4,
policy5,
],
});
const order = builder.build({ items });
order.price // 24677
Scenario 3 - 2
/**
* 情境編號:丙 - 2
*
* 決策法:擇優取一 (order-based)
*
* 情境敘述:
* 1. 全館 滿14000元 贈最低價品
* 2. 指定商品(C~E)無條件 送最低價商品
* 3. 指定分類(鞋子)滿6000元 送最低價商品
* 4. 指定分類(Boyy)滿5000元 打9折
* 5. 全館 滿9件 送最低價商品
* 6. 指定商品(C~I)每3000元 折200元
*
* 輸入:1 & 2 & 3 & 4 & 5 & 6
* 輸出:1 + 2 + 3 + 4 + 5 + 6
* 購物車顯示金額 26250
*/
// 1. 全館 滿14000元 送最低價商品
const policy1 = new ItemGiveawayDiscount(
1,
new PriceThreshold(14000),
);
// 2. 指定商品(C~E)無條件 送最低價商品
const policy2 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['C', 'D', 'E', 'F', 'G', 'H', 'I'],
}),
{ onlyMatched: true }
);
// 3. 指定分類(鞋子)滿6000元 送最低價商品
const policy3 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['shoes'],
scope: 'category',
conditions: [new PriceThreshold(6000)],
}),
{ onlyMatched: true }
);
// 4. 指定分類(Boyy)滿5000元 打9折
const policy4 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'brand',
items: ['Boyy'],
conditions: [new PriceThreshold(5000)],
}),
{ onlyMatched: true }
);
// 5. 全館 滿9件 送最低價商品
const policy5 = new ItemGiveawayDiscount(
1,
new QuantityThreshold(9),
);
// 6. 指定商品(C~I)每3000元 折200元
const policy6 = new StepValueDiscount(
3000,
200,
new ItemIncluded({
items: ['C', 'D', 'E', 'F''G', 'H', 'I'],
}),
{ stepUnit: 'price', onlyMatched: true }
);
const builder = new OrderBuilder({
policyPickStrategy: 'order-based',
discountMethod: 'price-weighted-average',
policies: [
policy1,
policy2,
policy3,
policy4,
policy5,
policy6,
],
});
const order = builder.build({ items });
order.price // 26250
Scenario 4
/**
* 情境編號:丁
*
* 決策法:擇優取一 (order-based)
*
* 情境敘述:
* 1. 指定分類(飾品)無條件 折1,000 元
* 2. 指定分類(Swell)滿10,000元 打9折
* 3. 全館 滿15,000元 送最低價品
* 4. 指定商品(F~I)無條件 打9折
* 5. 指定分類(Boyy)滿5000 打9折
*
* 預期結果:(1&2&3) | (4&5)
* 購物車顯示金額 28070
*/
// 1. 指定分類(飾品)無條件 折1,000 元
const policy1 = new ValueDiscount(
1000,
new ItemIncluded<TestOrderItem>({
scope: 'category',
items: ['accessory'],
}),
{ onlyMatched: true }
);
// 2. 指定分類(Swell)滿10,000元 打9折
const policy2 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'brand',
items: ['Swell'],
conditions: [new PriceThreshold(10000)],
}),
{ onlyMatched: true }
);
// 3. 全館 滿15,000元 送最低價品
const policy3 = new ItemGiveawayDiscount(
1,
new PriceThreshold(15000),
);
// 4. 指定商品(F~I)無條件 打9折
const policy4 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
items: ['F', 'G', 'H', 'I'],
}),
{ onlyMatched: true }
);
// 5. 指定分類(Boyy)滿5000 打9折
const policy5 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'brand',
items: ['Boyy'],
conditions: [new PriceThreshold(5000)],
}),
{ onlyMatched: true }
);
const builder1 = new OrderBuilder({
policyPickStrategy: 'order-based',
policies: [
policy1,
policy2,
policy3,
],
});
const builder2 = new OrderBuilder({
policyPickStrategy: 'order-based',
policies: [
policy4,
policy5,
],
});
const order1 = builder1.build({ items });
const order2 = builder2.build({ items });
order1.price // 28070
order2.price // 28765
Scenario 5
/**
* 情境編號:戊
*
* 決策法:擇優取一 (order-based)
*
* 情境敘述:
* 1. 指定商品(B~E)無條件 送最低價商品
* 2. 全館 滿6件 送最低價商品
* 3. 指定分類(飾品)每2件 打9折
* 4. 指定分類(鞋子)滿4000元 送最低價商品
* 5. 指定分類(Boyy)滿5000元 打9折
* 6. 全館 滿15,000元 送最低價品
*
* 預期結果:(1&2&3) | (4&5&6)
* 購物車顯示金額 24915
*/
// 1. 指定商品(B~E)無條件 送最低價商品
const policy1 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['B', 'C', 'D', 'E'],
}),
{ onlyMatched: true }
);
// 2.全館 滿6件 送最低價商品
const policy2 = new ItemGiveawayDiscount(
1,
new QuantityThreshold(6),
);
// 3. 指定分類(飾品)每2件 打9折
const policy3 = new StepPercentageDiscount(
2,
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'category',
items: ['accessory'],
}),
{ stepUnit: 'quantity', onlyMatched: true }
);
// 4. 指定分類(鞋子)滿4000元 送最低價商品
const policy4 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
scope: 'category',
items: ['shoes'],
conditions: [new PriceThreshold(4000)],
}),
{ onlyMatched: true }
);
// 5. 指定分類(Boyy)滿5000元 打9折
const policy5 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'brand',
items: ['Boyy'],
conditions: [new PriceThreshold(5000)],
}),
{ onlyMatched: true }
);
// 6. 全館 滿15,000元 送最低價品
const policy6 = new ItemGiveawayDiscount(
1,
new PriceThreshold(15000),
);
const builder1 = new OrderBuilder({
policyPickStrategy: 'order-based',
policies: [
policy1,
policy2,
policy3,
],
});
const builder2 = new OrderBuilder({
policyPickStrategy: 'order-based',
policies: [
policy4,
policy5,
policy6,
],
});
const order1 = builder1.build({ items });
const order2 = builder2.build({ items });
order1.price // 24915
order2.price // 27850
Scenario 1 - 2
/**
* 情境編號:甲 - 2
*
* 決策法:最適組合 (item-based)
*
* 情境敘述:
* 1. 指定商品(A~F)滿3件 打9折
* 2. 指定商品(C~I)每5000元 折600元
* 3. 指定分類(鞋子)滿4000元 送最低價商品
* 4. 指定分類(Swell)每1件 打9折
* 5. 全館 滿6件 贈紅利點數1000點
* 6. 全館 滿6000元 免運
*
* 預期結果:
* 取最優排列組合:2+4
* 購物車顯示金額 24856
*/
// 指定商品(A~F)滿3件 打9折
const policy1 = new PercentageDiscount(
0.9,
new ItemIncluded({
items: ['A', 'B', 'C', 'D', 'E', 'F'],
threshold: 3,
}),
{ id: 'SPECIFIED_A_F', onlyMatched: true }
);
// 2. 指定商品(C~I)每5000元 折600元
const policy2 = new StepValueDiscount(
5000,
600,
new ItemIncluded<TestOrderItem>({
items: ['C', 'D', 'E', 'F', 'G', 'H', 'I'],
}),
{ id: 'SPECIFIED_C_I', stepUnit: 'price', onlyMatched: true }
);
// 3. 指定分類(鞋子)滿4000元 送最低價商品
const policy3 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
scope: 'category',
items: ['shoes'],
conditions: [new PriceThreshold(4000)],
}),
{ id: 'GIVEAWAY_BY_SHOES_1', onlyMatched: true }
);
// * 4. 指定分類(Swell)每1件 打9折
const policy4 = new StepPercentageDiscount(
1,
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'brand',
items: ['Swell'],
}),
{
id: 'SPECIFIED_BRAND_BY_Swell_1',
stepUnit: 'quantity',
onlyMatched: true,
}
);
// (1|2)&(3|4)
const builder1 = new OrderBuilder<TestOrderItem>({
policyPickStrategy: 'item-based',
policies: [
[policy1, policy2],
[policy3, policy4],
],
});
const order1 = builder1.build({ items });
const builder2 = new OrderBuilder<TestOrderItem>({
policyPickStrategy: 'item-based',
policies: [
[policy1, policy2],
policy3,
policy4,
],
});
const order2 = builder2.build({ items });
order1.price === order2.price // true
order1.price // 22491
order2.price // 22491
order1.itemRecords
// [
// {
// itemId: 'A-1',
// originItem: {
// id: 'A',
// name: '外套A',
// unitPrice: 1000,
// category: 'jacket',
// brand: 'AJE',
// },
// initialValue: 1000,
// discountValue: 100,
// finalPrice: 900,
// discountRecords: [
// {
// policyId: 'SPECIFIED_A_F',
// discountValue: 100,
// },
// ],
// appliedPolicies: [
// {
// prefix: 'DISCOUNT',
// type: 'PERCENTAGE',
// id: 'SPECIFIED_A_F',
// value: 0.9,
// conditions: [
// {
// type: 'QUANTITY',
// items: ['A', 'B', 'C', 'D', 'E', 'F'],
// threshold: 3,
// conditions: [],
// scope: 'id',
// },
// ],
// options: {
// id: 'SPECIFIED_A_F',
// onlyMatched: true,
// },
// },
// ],
// },
// ...
Logistics Fee
/**
* 情境編號:總額滿 2000元 免運
*
* 情境敘述:
* 1. 指定商品(B~E)滿2件 送最低價品
* 2. 整筆訂單 滿 2000 元免運
*
* 預期結果: 4500 + 200 - 1500 - 200 = 3000
* 購物車顯示金額 3000
*/
const originItems: TestOrderItem[] = [
{
id: 'A',
name: '外套A',
unitPrice: 1000,
quantity: 1,
category: 'jacket',
brand: 'AJE',
},
{
id: 'B',
name: '外套B',
unitPrice: 1500,
quantity: 1,
category: 'jacket',
brand: 'N21',
},
{
id: 'C',
name: '鞋子C',
unitPrice: 2000,
quantity: 1,
category: 'shoes',
brand: 'N21',
},
];
const order = new OrderBuilder()
// 滿 2000 免運
.setLogistics({
price: 200,
threshold: 2000,
name: '運費',
})
// 指定商品 B, C, D, E 滿兩件送最低價品
.addPolicy(
new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['B', 'C', 'D', 'E'],
threshold: 2,
}),
{ onlyMatched: true }
)
)
.build({ items: originItems });
order.price // 4500 + 200 - 1500 - 200 = 3000
Matched Times & Excluded Calculating Policy
/**
* 情境敘述:
* 滿足條件贈送紅利點數、滿足條件可加購商品
* 需要能單純判斷是否滿足特定條件、滿足幾次,不影響訂單計算的 policy 功能
*/
const items: TestOrderItem[] = [
{
id: 'A',
name: '外套A',
unitPrice: 1000,
quantity: 1,
category: 'jacket',
brand: 'AJE',
},
{
id: 'B',
name: '外套B',
unitPrice: 1500,
quantity: 1,
category: 'jacket',
brand: 'N21',
},
{
id: 'C',
name: '鞋子C',
unitPrice: 2000,
quantity: 1,
category: 'shoes',
brand: 'N21',
},
];
// Policy1: 每 2000 元折 200 元
const policy1 = new StepValueDiscount(
2000,
200,
{ stepUnit: 'price' },
);
// Policy1 滿足次數
new OrderBuilder()
.addPolicy(policy1)
.build({ items })
.discounts
.find(discount => discount.id === policy1.id)
?.matchedTimes; // step(4500, 2000) = 2
// Policy2: 每 1499 元打 8折 (Matched only Policy, will not participant in discounting)
const policy2 = new StepPercentageDiscount(
1499,
0.8,
{ stepUnit: 'price', excludedInCalculation: true }
);
const order = new OrderBuilder()
.addPolicy(policy2)
.build({ items });
// Policy2: 滿足次數
order
.discounts
.find(discount => discount.id === policy2id)
?.matchedTimes; // step(4500, 1499) = 3
// Policy2: Excluded in calculation.
order.discountValue // 0