@labdigital/commercetools-mock 3.0.0-beta.2 → 3.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{config-BcNSzPZz.d.mts → config-CN3DgmWu.d.mts} +7 -5
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +287 -53
- package/dist/index.mjs.map +1 -1
- package/dist/storage/sqlite.d.mts +1 -1
- package/package.json +1 -1
- package/src/lib/tax.test.ts +53 -0
- package/src/lib/tax.ts +90 -13
- package/src/repositories/cart/actions.ts +399 -10
- package/src/repositories/cart/helpers.ts +61 -18
- package/src/repositories/cart/index.test.ts +76 -0
- package/src/repositories/cart/index.ts +38 -5
- package/src/repositories/order/index.test.ts +6 -2
- package/src/repositories/order/index.ts +82 -25
- package/src/services/cart.test.ts +633 -0
- package/src/shipping.ts +21 -12
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as QueryParams, c as ResourceType, n as AbstractStorage, o as PagedQueryResponseMap, r as GetParams, s as ResourceMap } from "../config-
|
|
1
|
+
import { a as QueryParams, c as ResourceType, n as AbstractStorage, o as PagedQueryResponseMap, r as GetParams, s as ResourceMap } from "../config-CN3DgmWu.mjs";
|
|
2
2
|
import { CustomObject, Project } from "@commercetools/platform-sdk";
|
|
3
3
|
|
|
4
4
|
//#region src/storage/sqlite.d.ts
|
package/package.json
CHANGED
package/src/lib/tax.test.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
} from "@commercetools/platform-sdk";
|
|
6
6
|
import { describe, expect, test } from "vitest";
|
|
7
7
|
import {
|
|
8
|
+
buildTaxedPriceFromExternalAmount,
|
|
8
9
|
calculateTaxedPrice,
|
|
9
10
|
calculateTaxedPriceFromRate,
|
|
10
11
|
calculateTaxTotals,
|
|
@@ -85,6 +86,58 @@ describe("tax helpers", () => {
|
|
|
85
86
|
expect(taxed.totalTax?.centAmount).toBe(250);
|
|
86
87
|
});
|
|
87
88
|
|
|
89
|
+
test("calculateTaxedPriceFromRate respects HalfEven rounding (banker's)", () => {
|
|
90
|
+
const rate: TaxRate = {
|
|
91
|
+
amount: 0.05,
|
|
92
|
+
includedInPrice: false,
|
|
93
|
+
name: "5%",
|
|
94
|
+
country: "NL",
|
|
95
|
+
id: "rate",
|
|
96
|
+
subRates: [],
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// 250 * 0.05 = 12.5 — HalfEven rounds to 12 (toward even)
|
|
100
|
+
const halfEven = calculateTaxedPriceFromRate(250, "EUR", rate, "HalfEven")!;
|
|
101
|
+
expect(halfEven.totalTax?.centAmount).toBe(12);
|
|
102
|
+
expect(halfEven.totalGross.centAmount).toBe(262);
|
|
103
|
+
|
|
104
|
+
// HalfUp rounds 12.5 → 13
|
|
105
|
+
const halfUp = calculateTaxedPriceFromRate(250, "EUR", rate, "HalfUp")!;
|
|
106
|
+
expect(halfUp.totalTax?.centAmount).toBe(13);
|
|
107
|
+
expect(halfUp.totalGross.centAmount).toBe(263);
|
|
108
|
+
|
|
109
|
+
// HalfDown rounds 12.5 → 12
|
|
110
|
+
const halfDown = calculateTaxedPriceFromRate(250, "EUR", rate, "HalfDown")!;
|
|
111
|
+
expect(halfDown.totalTax?.centAmount).toBe(12);
|
|
112
|
+
expect(halfDown.totalGross.centAmount).toBe(262);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("buildTaxedPriceFromExternalAmount respects rounding mode", () => {
|
|
116
|
+
const draft = {
|
|
117
|
+
totalGross: {
|
|
118
|
+
type: "centPrecision" as const,
|
|
119
|
+
currencyCode: "EUR",
|
|
120
|
+
centAmount: 525,
|
|
121
|
+
fractionDigits: 2,
|
|
122
|
+
},
|
|
123
|
+
taxRate: {
|
|
124
|
+
name: "5%",
|
|
125
|
+
amount: 0.05,
|
|
126
|
+
country: "NL",
|
|
127
|
+
includedInPrice: true,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// 525 * 0.05 / 1.05 = 25.0 — exact, no rounding ambiguity
|
|
132
|
+
// Use a case with .5 ambiguity: 315 * 0.05 / 1.05 = 15.0 — also exact
|
|
133
|
+
// 105 * 0.05 / 1.05 = 5.0
|
|
134
|
+
// Pick 11 * 0.5 / 1.5 = 3.666... not clean. Use 525 to confirm baseline
|
|
135
|
+
const halfEven = buildTaxedPriceFromExternalAmount(draft, "HalfEven");
|
|
136
|
+
expect(halfEven.totalGross.centAmount).toBe(525);
|
|
137
|
+
expect(halfEven.totalTax?.centAmount).toBe(25);
|
|
138
|
+
expect(halfEven.totalNet.centAmount).toBe(500);
|
|
139
|
+
});
|
|
140
|
+
|
|
88
141
|
test("calculateTaxedPrice selects matching tax rate from category", () => {
|
|
89
142
|
const taxCategory: TaxCategory = {
|
|
90
143
|
id: "tax-cat",
|
package/src/lib/tax.ts
CHANGED
|
@@ -1,12 +1,82 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Cart,
|
|
3
|
+
ExternalTaxAmountDraft,
|
|
4
|
+
ExternalTaxRateDraft,
|
|
5
|
+
RoundingMode,
|
|
3
6
|
TaxCategory,
|
|
4
7
|
TaxedItemPrice,
|
|
5
8
|
TaxedPrice,
|
|
6
9
|
TaxPortion,
|
|
7
10
|
TaxRate,
|
|
8
11
|
} from "@commercetools/platform-sdk";
|
|
9
|
-
import {
|
|
12
|
+
import { Decimal } from "decimal.js";
|
|
13
|
+
import {
|
|
14
|
+
createCentPrecisionMoney,
|
|
15
|
+
roundDecimal,
|
|
16
|
+
} from "#src/repositories/helpers.ts";
|
|
17
|
+
|
|
18
|
+
const roundCents = (value: number, mode: RoundingMode = "HalfEven"): number =>
|
|
19
|
+
roundDecimal(new Decimal(value), mode).toNumber();
|
|
20
|
+
|
|
21
|
+
export const buildTaxedPriceFromExternalAmount = (
|
|
22
|
+
draft: ExternalTaxAmountDraft,
|
|
23
|
+
roundingMode: RoundingMode = "HalfEven",
|
|
24
|
+
): TaxedItemPrice => {
|
|
25
|
+
const taxRate = taxRateFromExternalDraft(draft.taxRate);
|
|
26
|
+
const totalGross = createCentPrecisionMoney(draft.totalGross);
|
|
27
|
+
const currencyCode = totalGross.currencyCode;
|
|
28
|
+
const toMoney = (centAmount: number) =>
|
|
29
|
+
createCentPrecisionMoney({ currencyCode, centAmount });
|
|
30
|
+
|
|
31
|
+
const grossAmount = totalGross.centAmount;
|
|
32
|
+
// totalGross is authoritative in ExternalAmount mode, so net is always
|
|
33
|
+
// gross / (1 + rate) regardless of taxRate.includedInPrice. The flag is kept
|
|
34
|
+
// on the stored taxRate for callers that read it back.
|
|
35
|
+
const taxAmount =
|
|
36
|
+
taxRate.amount > 0
|
|
37
|
+
? roundCents(
|
|
38
|
+
new Decimal(grossAmount)
|
|
39
|
+
.mul(taxRate.amount)
|
|
40
|
+
.div(1 + taxRate.amount)
|
|
41
|
+
.toNumber(),
|
|
42
|
+
roundingMode,
|
|
43
|
+
)
|
|
44
|
+
: 0;
|
|
45
|
+
const netAmount = grossAmount - taxAmount;
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
totalNet: toMoney(netAmount),
|
|
49
|
+
totalGross,
|
|
50
|
+
totalTax: taxAmount > 0 ? toMoney(taxAmount) : undefined,
|
|
51
|
+
taxPortions:
|
|
52
|
+
taxAmount > 0
|
|
53
|
+
? [
|
|
54
|
+
{
|
|
55
|
+
rate: taxRate.amount,
|
|
56
|
+
name: taxRate.name,
|
|
57
|
+
amount: toMoney(taxAmount),
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
: [],
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const taxRateFromExternalDraft = (
|
|
65
|
+
draft: ExternalTaxRateDraft,
|
|
66
|
+
): TaxRate => {
|
|
67
|
+
const amount =
|
|
68
|
+
draft.amount ??
|
|
69
|
+
draft.subRates?.reduce((acc, subRate) => acc + subRate.amount, 0) ??
|
|
70
|
+
0;
|
|
71
|
+
return {
|
|
72
|
+
name: draft.name,
|
|
73
|
+
amount,
|
|
74
|
+
includedInPrice: draft.includedInPrice ?? false,
|
|
75
|
+
country: draft.country,
|
|
76
|
+
state: draft.state,
|
|
77
|
+
subRates: draft.subRates,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
10
80
|
|
|
11
81
|
type TaxableResource = Pick<
|
|
12
82
|
Cart,
|
|
@@ -101,10 +171,11 @@ export const calculateTaxTotals = (
|
|
|
101
171
|
};
|
|
102
172
|
};
|
|
103
173
|
|
|
104
|
-
export const
|
|
174
|
+
export const calculateTaxedPriceFromRate = (
|
|
105
175
|
amount: number,
|
|
106
176
|
currencyCode: string,
|
|
107
177
|
taxRate?: TaxRate,
|
|
178
|
+
roundingMode: RoundingMode = "HalfEven",
|
|
108
179
|
): TaxedItemPrice | undefined => {
|
|
109
180
|
if (!taxRate) {
|
|
110
181
|
return undefined;
|
|
@@ -123,13 +194,20 @@ export const buildTaxedPriceFromRate = (
|
|
|
123
194
|
|
|
124
195
|
if (taxRate.includedInPrice) {
|
|
125
196
|
grossAmount = amount;
|
|
126
|
-
taxAmount =
|
|
127
|
-
(grossAmount
|
|
197
|
+
taxAmount = roundCents(
|
|
198
|
+
new Decimal(grossAmount)
|
|
199
|
+
.mul(taxRate.amount)
|
|
200
|
+
.div(1 + taxRate.amount)
|
|
201
|
+
.toNumber(),
|
|
202
|
+
roundingMode,
|
|
128
203
|
);
|
|
129
204
|
netAmount = grossAmount - taxAmount;
|
|
130
205
|
} else {
|
|
131
206
|
netAmount = amount;
|
|
132
|
-
taxAmount =
|
|
207
|
+
taxAmount = roundCents(
|
|
208
|
+
new Decimal(netAmount).mul(taxRate.amount).toNumber(),
|
|
209
|
+
roundingMode,
|
|
210
|
+
);
|
|
133
211
|
grossAmount = netAmount + taxAmount;
|
|
134
212
|
}
|
|
135
213
|
|
|
@@ -150,18 +228,12 @@ export const buildTaxedPriceFromRate = (
|
|
|
150
228
|
};
|
|
151
229
|
};
|
|
152
230
|
|
|
153
|
-
export const calculateTaxedPriceFromRate = (
|
|
154
|
-
amount: number,
|
|
155
|
-
currencyCode: string,
|
|
156
|
-
taxRate?: TaxRate,
|
|
157
|
-
): TaxedItemPrice | undefined =>
|
|
158
|
-
buildTaxedPriceFromRate(amount, currencyCode, taxRate);
|
|
159
|
-
|
|
160
231
|
export const calculateTaxedPrice = (
|
|
161
232
|
amount: number,
|
|
162
233
|
taxCategory: TaxCategory | undefined,
|
|
163
234
|
currency: string,
|
|
164
235
|
country: string | undefined,
|
|
236
|
+
roundingMode: RoundingMode = "HalfEven",
|
|
165
237
|
): TaxedPrice | undefined => {
|
|
166
238
|
if (!taxCategory || !taxCategory.rates.length) {
|
|
167
239
|
return undefined;
|
|
@@ -172,7 +244,12 @@ export const calculateTaxedPrice = (
|
|
|
172
244
|
(rate) => !rate.country || rate.country === country,
|
|
173
245
|
) || taxCategory.rates[0];
|
|
174
246
|
|
|
175
|
-
const taxedItemPrice =
|
|
247
|
+
const taxedItemPrice = calculateTaxedPriceFromRate(
|
|
248
|
+
amount,
|
|
249
|
+
currency,
|
|
250
|
+
taxRate,
|
|
251
|
+
roundingMode,
|
|
252
|
+
);
|
|
176
253
|
if (!taxedItemPrice) {
|
|
177
254
|
return undefined;
|
|
178
255
|
}
|