- 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
| Arguments |
index |
Type |
Required |
| overload1 |
|
|
|
builder |
0 |
OrderBuilder |
false |
| overload2 |
|
|
|
options |
0 |
OrderBuilderConstructor |
false |
| 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. |
| 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[]) |
|
|
| 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 |
| Interface |
Type |
Policies |
Policy | Policy[] |
RemovePolicy |
Policy | string |
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' });
const builder = new OrderBuilder({
policyPickStrategy: 'item-based',
discountMethod: 'price-weighted-average',
roundStrategy: 'every-calculation',
policies: [policy1],
});
const order = builder
.addPolicy(policy2)
.removePolicy('DISCOUNT_1')
.build({
items: [
{
id: 'ItemA',
name: 'Foo',
unitPrice: 100,
quantity: 2,
},
{
id: 'ItemB',
name: 'Bar',
unitPrice: 50,
quantity: 1,
},
],
});
builder.getPolicy('DISCOUNT_1');
builder.getPolicy('DISCOUNT_2');
order.price;
order.itemRecords;
import {
OrderItem,
OrderBuilder,
ItemIncluded,
PriceThreshold,
QuantityThreshold,
ValueDiscount,
StepValueDiscount,
PercentageDiscount,
ItemGiveawayDiscount,
StepPercentageDiscount,
} from '@rytass/order-builder';
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',
},
];
const policy1 = new PercentageDiscount(
0.9,
new ItemIncluded({
items: ['A', 'B', 'C', 'D', 'E', 'F'],
threshold: 3,
}),
{ onlyMatched: true },
);
const policy2 = new StepValueDiscount(
5000,
600,
new ItemIncluded<TestOrderItem>({
items: ['C', 'D', 'E', 'F', 'G', 'H', 'I'],
}),
{ stepUnit: 'price', onlyMatched: true },
);
const policy3 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
scope: 'category',
items: ['shoes'],
conditions: [new PriceThreshold(4000)],
}),
{ onlyMatched: true },
);
const policy4 = new StepPercentageDiscount(
1,
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'brand',
items: ['Swell'],
}),
{
stepUnit: 'quantity',
onlyMatched: true,
},
);
const builder = new OrderBuilder({
policyPickStrategy: 'order-based',
policies: [
[policy1, policy2],
[policy3, policy4],
],
});
const order = builder.build({ items });
order.price;
const policy1 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['B', 'C', 'D', 'E'],
}),
{ onlyMatched: true },
);
const policy2 = new StepValueDiscount(
3000,
200,
new ItemIncluded<TestOrderItem>({
items: ['C', 'D', 'E', 'F', 'G', 'H', 'I'],
scope: 'id',
}),
{ stepUnit: 'price', onlyMatched: true },
);
const policy3 = new ValueDiscount(
100,
new ItemIncluded<TestOrderItem>({
items: ['N21'],
scope: 'brand',
threshold: 2,
}),
{ onlyMatched: true },
);
const policy4 = new StepPercentageDiscount(
2,
0.9,
new ItemIncluded<TestOrderItem>({
items: ['accessory'],
scope: 'category',
}),
{ stepUnit: 'quantity', onlyMatched: true },
);
const policy5 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
items: ['Boyy'],
scope: 'brand',
conditions: [new PriceThreshold(5000)],
}),
{ id: 'P5', onlyMatched: true },
);
const policy6 = new ItemGiveawayDiscount(1, new QuantityThreshold(6));
const builder = new OrderBuilder({
policyPickStrategy: 'order-based',
policies: [[policy1, policy2], [policy3, policy4, policy5], policy6],
});
const order = builder.build({ items });
order.price;
const policy1 = new ItemGiveawayDiscount(1, new QuantityThreshold(6));
const policy2 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
items: ['Boyy'],
scope: 'brand',
conditions: [new PriceThreshold(5000)],
}),
{ onlyMatched: true },
);
const policy3 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['B', 'C', 'D', 'E'],
}),
{ onlyMatched: true },
);
const policy4 = new StepValueDiscount(
3000,
200,
new ItemIncluded<TestOrderItem>({
items: ['C', 'D', 'E', 'F', 'G', 'H', 'I'],
}),
{ stepUnit: 'price', onlyMatched: true },
);
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;
const policy1 = new ItemGiveawayDiscount(1, new PriceThreshold(14000));
const policy2 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['C', 'D', 'E', 'F', 'G', 'H', 'I'],
}),
{ onlyMatched: true },
);
const policy3 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['shoes'],
scope: 'category',
conditions: [new PriceThreshold(6000)],
}),
{ onlyMatched: true },
);
const policy4 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'brand',
items: ['Boyy'],
conditions: [new PriceThreshold(5000)],
}),
{ onlyMatched: true },
);
const policy5 = new ItemGiveawayDiscount(1, new QuantityThreshold(9));
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;
const policy1 = new ValueDiscount(
1000,
new ItemIncluded<TestOrderItem>({
scope: 'category',
items: ['accessory'],
}),
{ onlyMatched: true },
);
const policy2 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'brand',
items: ['Swell'],
conditions: [new PriceThreshold(10000)],
}),
{ onlyMatched: true },
);
const policy3 = new ItemGiveawayDiscount(1, new PriceThreshold(15000));
const policy4 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
items: ['F', 'G', 'H', 'I'],
}),
{ onlyMatched: true },
);
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;
order2.price;
const policy1 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['B', 'C', 'D', 'E'],
}),
{ onlyMatched: true },
);
const policy2 = new ItemGiveawayDiscount(1, new QuantityThreshold(6));
const policy3 = new StepPercentageDiscount(
2,
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'category',
items: ['accessory'],
}),
{ stepUnit: 'quantity', onlyMatched: true },
);
const policy4 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
scope: 'category',
items: ['shoes'],
conditions: [new PriceThreshold(4000)],
}),
{ onlyMatched: true },
);
const policy5 = new PercentageDiscount(
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'brand',
items: ['Boyy'],
conditions: [new PriceThreshold(5000)],
}),
{ onlyMatched: true },
);
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;
order2.price;
const policy1 = new PercentageDiscount(
0.9,
new ItemIncluded({
items: ['A', 'B', 'C', 'D', 'E', 'F'],
threshold: 3,
}),
{ id: 'SPECIFIED_A_F', onlyMatched: true },
);
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 },
);
const policy3 = new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
scope: 'category',
items: ['shoes'],
conditions: [new PriceThreshold(4000)],
}),
{ id: 'GIVEAWAY_BY_SHOES_1', onlyMatched: true },
);
const policy4 = new StepPercentageDiscount(
1,
0.9,
new ItemIncluded<TestOrderItem>({
scope: 'brand',
items: ['Swell'],
}),
{
id: 'SPECIFIED_BRAND_BY_Swell_1',
stepUnit: 'quantity',
onlyMatched: true,
},
);
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;
order1.price;
order2.price;
order1.itemRecords;
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()
.setLogistics({
price: 200,
threshold: 2000,
name: '運費',
})
.addPolicy(
new ItemGiveawayDiscount(
1,
new ItemIncluded<TestOrderItem>({
items: ['B', 'C', 'D', 'E'],
threshold: 2,
}),
{ onlyMatched: true },
),
)
.build({ items: originItems });
order.price;
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',
},
];
const policy1 = new StepValueDiscount(2000, 200, { stepUnit: 'price' });
new OrderBuilder()
.addPolicy(policy1)
.build({ items })
.discounts.find(discount => discount.id === policy1.id)?.matchedTimes;
const policy2 = new StepPercentageDiscount(1499, 0.8, { stepUnit: 'price', excludedInCalculation: true });
const order = new OrderBuilder().addPolicy(policy2).build({ items });
order.discounts.find(discount => discount.id === policy2.id)?.matchedTimes;
order.discountValue;