@labdigital/commercetools-mock 2.45.1 → 2.47.0
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/index.cjs +614 -250
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +788 -59
- package/dist/index.d.ts +788 -59
- package/dist/index.js +602 -238
- package/dist/index.js.map +1 -1
- package/package.json +41 -48
- package/src/ctMock.ts +11 -13
- package/src/index.test.ts +5 -5
- package/src/lib/predicateParser.test.ts +91 -60
- package/src/lib/predicateParser.ts +38 -42
- package/src/lib/productSearchFilter.test.ts +18 -0
- package/src/lib/productSearchFilter.ts +7 -0
- package/src/lib/projectionSearchFilter.test.ts +17 -17
- package/src/lib/projectionSearchFilter.ts +2 -3
- package/src/oauth/server.test.ts +1 -1
- package/src/oauth/server.ts +11 -11
- package/src/priceSelector.ts +1 -1
- package/src/product-projection-search.ts +18 -19
- package/src/product-search.ts +48 -8
- package/src/repositories/business-unit.ts +17 -16
- package/src/repositories/cart/actions.ts +32 -32
- package/src/repositories/cart/helpers.ts +1 -1
- package/src/repositories/cart/index.ts +25 -8
- package/src/repositories/cart-discount/actions.ts +1 -4
- package/src/repositories/category/actions.ts +2 -6
- package/src/repositories/custom-object.ts +20 -21
- package/src/repositories/customer/actions.ts +4 -4
- package/src/repositories/errors.ts +1 -1
- package/src/repositories/extension.ts +2 -1
- package/src/repositories/helpers.ts +27 -27
- package/src/repositories/index.ts +17 -17
- package/src/repositories/my-customer.ts +1 -1
- package/src/repositories/my-order.ts +2 -2
- package/src/repositories/order/index.ts +1 -1
- package/src/repositories/product/actions.ts +1 -1
- package/src/repositories/quote/actions.ts +83 -0
- package/src/repositories/quote/index.ts +54 -0
- package/src/repositories/quote-request/actions.ts +84 -0
- package/src/repositories/quote-request/index.test.ts +167 -0
- package/src/repositories/quote-request/index.ts +67 -0
- package/src/repositories/quote-staged/actions.ts +84 -0
- package/src/repositories/quote-staged/index.ts +47 -0
- package/src/repositories/review.ts +4 -4
- package/src/repositories/shipping-method/actions.ts +17 -17
- package/src/repositories/shipping-method/index.ts +6 -6
- package/src/repositories/shopping-list/actions.ts +1 -1
- package/src/repositories/shopping-list/index.ts +9 -1
- package/src/repositories/subscription.ts +2 -4
- package/src/server.ts +3 -2
- package/src/services/abstract.ts +7 -7
- package/src/services/as-associate-order.test.ts +1 -1
- package/src/services/cart-discount.test.ts +1 -1
- package/src/services/cart.test.ts +40 -15
- package/src/services/category.test.ts +1 -1
- package/src/services/customer.test.ts +16 -55
- package/src/services/customer.ts +1 -1
- package/src/services/index.ts +20 -14
- package/src/services/inventory-entry.test.ts +5 -5
- package/src/services/my-cart.test.ts +2 -2
- package/src/services/my-customer.test.ts +2 -2
- package/src/services/order.test.ts +8 -8
- package/src/services/product-projection.test.ts +5 -5
- package/src/services/product-projection.ts +12 -14
- package/src/services/product.test.ts +155 -71
- package/src/services/quote-request.test.ts +59 -0
- package/src/services/quote-request.ts +16 -0
- package/src/services/quote-staged.ts +16 -0
- package/src/services/quote.ts +16 -0
- package/src/services/standalone-price.test.ts +4 -4
- package/src/services/state.test.ts +1 -1
- package/src/services/store.test.ts +2 -2
- package/src/services/tax-category.test.ts +1 -1
- package/src/shipping.ts +3 -3
- package/src/storage/in-memory.ts +55 -63
- package/src/testing/customer.ts +40 -0
- package/src/types.ts +51 -31
- package/src/repositories/quote-request.ts +0 -17
- package/src/repositories/quote.ts +0 -14
- package/src/repositories/staged-quote.ts +0 -17
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* See https://docs.commercetools.com/api/predicates/query
|
|
6
6
|
*/
|
|
7
7
|
import { haversineDistance } from "./haversine";
|
|
8
|
-
import { Lexer, Parser
|
|
8
|
+
import { type ITokenPosition, Lexer, Parser } from "./parser";
|
|
9
9
|
|
|
10
10
|
export class PredicateError {
|
|
11
11
|
message: string;
|
|
@@ -34,10 +34,9 @@ export const matchesPredicate = (
|
|
|
34
34
|
const func = generateMatchFunc(item);
|
|
35
35
|
return func(target, variables ?? {});
|
|
36
36
|
});
|
|
37
|
-
} else {
|
|
38
|
-
const func = generateMatchFunc(predicate);
|
|
39
|
-
return func(target, variables ?? {});
|
|
40
37
|
}
|
|
38
|
+
const func = generateMatchFunc(predicate);
|
|
39
|
+
return func(target, variables ?? {});
|
|
41
40
|
};
|
|
42
41
|
|
|
43
42
|
export const parseQueryExpression = (
|
|
@@ -47,9 +46,8 @@ export const parseQueryExpression = (
|
|
|
47
46
|
const callbacks = predicate.map((item) => generateMatchFunc(item));
|
|
48
47
|
return (target: any, variables: VariableMap) =>
|
|
49
48
|
callbacks.every((callback) => callback(target, variables));
|
|
50
|
-
} else {
|
|
51
|
-
return generateMatchFunc(predicate);
|
|
52
49
|
}
|
|
50
|
+
return generateMatchFunc(predicate);
|
|
53
51
|
};
|
|
54
52
|
|
|
55
53
|
type TypeSymbol = {
|
|
@@ -124,6 +122,12 @@ const getLexer = (value: string) =>
|
|
|
124
122
|
.token("IS", /is(?![-_a-z0-9]+)/i)
|
|
125
123
|
.token("DEFINED", /defined(?![-_a-z0-9]+)/i)
|
|
126
124
|
|
|
125
|
+
// Special case for UUID identifiers,
|
|
126
|
+
// since they otherwise would get matched as INT, when starting with a digit
|
|
127
|
+
.token(
|
|
128
|
+
"IDENTIFIER",
|
|
129
|
+
/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/,
|
|
130
|
+
)
|
|
127
131
|
.token("FLOAT", /\d+\.\d+/)
|
|
128
132
|
.token("INT", /\d+/)
|
|
129
133
|
.token("VARIABLE", /:([-_A-Za-z0-9]+)/)
|
|
@@ -171,7 +175,7 @@ const generateMatchFunc = (predicate: string): MatchFunc => {
|
|
|
171
175
|
(t) =>
|
|
172
176
|
({
|
|
173
177
|
type: "boolean",
|
|
174
|
-
value: t.token.match === "true"
|
|
178
|
+
value: t.token.match === "true",
|
|
175
179
|
pos: t.token.strpos(),
|
|
176
180
|
}) as TypeSymbol,
|
|
177
181
|
)
|
|
@@ -203,7 +207,7 @@ const generateMatchFunc = (predicate: string): MatchFunc => {
|
|
|
203
207
|
(t) =>
|
|
204
208
|
({
|
|
205
209
|
type: "int",
|
|
206
|
-
value: parseInt(t.token.match, 10),
|
|
210
|
+
value: Number.parseInt(t.token.match, 10),
|
|
207
211
|
pos: t.token.strpos(),
|
|
208
212
|
}) as TypeSymbol,
|
|
209
213
|
)
|
|
@@ -213,7 +217,7 @@ const generateMatchFunc = (predicate: string): MatchFunc => {
|
|
|
213
217
|
(t) =>
|
|
214
218
|
({
|
|
215
219
|
type: "float",
|
|
216
|
-
value: parseFloat(t.token.match),
|
|
220
|
+
value: Number.parseFloat(t.token.match),
|
|
217
221
|
pos: t.token.strpos(),
|
|
218
222
|
}) as TypeSymbol,
|
|
219
223
|
)
|
|
@@ -236,9 +240,8 @@ const generateMatchFunc = (predicate: string): MatchFunc => {
|
|
|
236
240
|
const expr: any = parser.parse({ terminals: [bp - 1] });
|
|
237
241
|
if (Array.isArray(expr)) {
|
|
238
242
|
return [left, ...expr];
|
|
239
|
-
} else {
|
|
240
|
-
return [left, expr];
|
|
241
243
|
}
|
|
244
|
+
return [left, expr];
|
|
242
245
|
})
|
|
243
246
|
.nud("(", 100, (t) => {
|
|
244
247
|
const expr: any = parser.parse({ terminals: [")"] });
|
|
@@ -256,17 +259,16 @@ const generateMatchFunc = (predicate: string): MatchFunc => {
|
|
|
256
259
|
}
|
|
257
260
|
return false;
|
|
258
261
|
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return expr(value, vars);
|
|
262
|
+
}
|
|
263
|
+
const value = resolveValue(obj, left);
|
|
264
|
+
if (value) {
|
|
265
|
+
if (Array.isArray(value)) {
|
|
266
|
+
return value.some((item) => expr(item, vars));
|
|
267
267
|
}
|
|
268
|
-
|
|
268
|
+
|
|
269
|
+
return expr(value, vars);
|
|
269
270
|
}
|
|
271
|
+
return false;
|
|
270
272
|
};
|
|
271
273
|
})
|
|
272
274
|
.bp(")", 0)
|
|
@@ -284,14 +286,13 @@ const generateMatchFunc = (predicate: string): MatchFunc => {
|
|
|
284
286
|
}
|
|
285
287
|
return value === other;
|
|
286
288
|
});
|
|
287
|
-
} else {
|
|
288
|
-
const resolvedValue = resolveValue(obj, left);
|
|
289
|
-
const resolvedSymbol = resolveSymbol(expr, vars);
|
|
290
|
-
if (Array.isArray(resolvedValue)) {
|
|
291
|
-
return !!resolvedValue.some((elem) => elem === resolvedSymbol);
|
|
292
|
-
}
|
|
293
|
-
return resolvedValue === resolvedSymbol;
|
|
294
289
|
}
|
|
290
|
+
const resolvedValue = resolveValue(obj, left);
|
|
291
|
+
const resolvedSymbol = resolveSymbol(expr, vars);
|
|
292
|
+
if (Array.isArray(resolvedValue)) {
|
|
293
|
+
return !!resolvedValue.some((elem) => elem === resolvedSymbol);
|
|
294
|
+
}
|
|
295
|
+
return resolvedValue === resolvedSymbol;
|
|
295
296
|
};
|
|
296
297
|
})
|
|
297
298
|
.led("!=", 20, ({ left, bp }) => {
|
|
@@ -347,12 +348,11 @@ const generateMatchFunc = (predicate: string): MatchFunc => {
|
|
|
347
348
|
const val = resolveValue(obj, left);
|
|
348
349
|
return val.length === 0;
|
|
349
350
|
};
|
|
350
|
-
} else {
|
|
351
|
-
return (obj: any, vars: VariableMap) => {
|
|
352
|
-
const val = resolveValue(obj, left);
|
|
353
|
-
return val.length !== 0;
|
|
354
|
-
};
|
|
355
351
|
}
|
|
352
|
+
return (obj: any, vars: VariableMap) => {
|
|
353
|
+
const val = resolveValue(obj, left);
|
|
354
|
+
return val.length !== 0;
|
|
355
|
+
};
|
|
356
356
|
}
|
|
357
357
|
case "defined": {
|
|
358
358
|
if (!invert) {
|
|
@@ -360,12 +360,11 @@ const generateMatchFunc = (predicate: string): MatchFunc => {
|
|
|
360
360
|
const val = resolveValue(obj, left);
|
|
361
361
|
return val !== undefined;
|
|
362
362
|
};
|
|
363
|
-
} else {
|
|
364
|
-
return (obj: any, vars: VariableMap) => {
|
|
365
|
-
const val = resolveValue(obj, left);
|
|
366
|
-
return val === undefined;
|
|
367
|
-
};
|
|
368
363
|
}
|
|
364
|
+
return (obj: any, vars: VariableMap) => {
|
|
365
|
+
const val = resolveValue(obj, left);
|
|
366
|
+
return val === undefined;
|
|
367
|
+
};
|
|
369
368
|
}
|
|
370
369
|
default: {
|
|
371
370
|
throw new Error("Unexpected");
|
|
@@ -457,9 +456,8 @@ const generateMatchFunc = (predicate: string): MatchFunc => {
|
|
|
457
456
|
const array = expr.map((item: TypeSymbol) => resolveSymbol(item, vars));
|
|
458
457
|
if (keyword.type === "ALL") {
|
|
459
458
|
return array.every((item: any) => value.includes(item));
|
|
460
|
-
} else {
|
|
461
|
-
return array.some((item: any) => value.includes(item));
|
|
462
459
|
}
|
|
460
|
+
return array.some((item: any) => value.includes(item));
|
|
463
461
|
};
|
|
464
462
|
})
|
|
465
463
|
|
|
@@ -472,9 +470,7 @@ const generateMatchFunc = (predicate: string): MatchFunc => {
|
|
|
472
470
|
const column = lines[lines.length - 1].length;
|
|
473
471
|
|
|
474
472
|
throw new PredicateError(
|
|
475
|
-
`Unexpected end of input, expected SphereIdentifierChar, comparison
|
|
476
|
-
`operator, not, in, contains, is, within or matches` +
|
|
477
|
-
` (line ${lines.length}, column ${column})`,
|
|
473
|
+
`Unexpected end of input, expected SphereIdentifierChar, comparison operator, not, in, contains, is, within or matches (line ${lines.length}, column ${column})`,
|
|
478
474
|
);
|
|
479
475
|
}
|
|
480
476
|
return result;
|
|
@@ -138,6 +138,24 @@ describe("Product search filter", () => {
|
|
|
138
138
|
},
|
|
139
139
|
}).isMatch,
|
|
140
140
|
).toBeTruthy();
|
|
141
|
+
|
|
142
|
+
expect(
|
|
143
|
+
match({
|
|
144
|
+
exact: {
|
|
145
|
+
field: "variants.sku",
|
|
146
|
+
values: ["MYSKU", "OTHER"],
|
|
147
|
+
},
|
|
148
|
+
}).isMatch,
|
|
149
|
+
).toBeTruthy();
|
|
150
|
+
|
|
151
|
+
expect(
|
|
152
|
+
match({
|
|
153
|
+
exact: {
|
|
154
|
+
field: "variants.sku",
|
|
155
|
+
values: ["OTHER"],
|
|
156
|
+
},
|
|
157
|
+
}).isMatch,
|
|
158
|
+
).toBeFalsy();
|
|
141
159
|
});
|
|
142
160
|
|
|
143
161
|
test("by attribute value", async () => {
|
|
@@ -98,6 +98,13 @@ export const parseSearchQuery = (
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
if (isSearchExactExpression(searchQuery)) {
|
|
101
|
+
if (Array.isArray(searchQuery.exact.values)) {
|
|
102
|
+
return generateFieldMatchFunc(
|
|
103
|
+
(value: any) => (searchQuery.exact.values ?? []).includes(value),
|
|
104
|
+
searchQuery.exact,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
101
108
|
return generateFieldMatchFunc(
|
|
102
109
|
(value: any) => value === searchQuery.exact.value,
|
|
103
110
|
searchQuery.exact,
|
|
@@ -68,8 +68,8 @@ describe("Search filter", () => {
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
test("by product key", async () => {
|
|
71
|
-
expect(match(
|
|
72
|
-
expect(match(
|
|
71
|
+
expect(match("key:exists").isMatch).toBeTruthy();
|
|
72
|
+
expect(match("key:missing").isMatch).toBeFalsy();
|
|
73
73
|
expect(match(`key:"test-product"`).isMatch).toBeTruthy();
|
|
74
74
|
});
|
|
75
75
|
|
|
@@ -80,29 +80,29 @@ describe("Search filter", () => {
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
test("by SKU", async () => {
|
|
83
|
-
expect(match(
|
|
84
|
-
expect(match(
|
|
83
|
+
expect(match("variants.sku:exists").isMatch).toBeTruthy();
|
|
84
|
+
expect(match("variants.sku:missing").isMatch).toBeFalsy();
|
|
85
85
|
expect(match(`variants.sku:"MYSKU"`).isMatch).toBeTruthy();
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
test("by attribute value", async () => {
|
|
89
|
-
expect(match(
|
|
90
|
-
expect(match(
|
|
91
|
-
expect(match(
|
|
92
|
-
expect(match(
|
|
89
|
+
expect(match("variants.attributes.number:4").isMatch).toBeTruthy();
|
|
90
|
+
expect(match("variants.attributes.number:3,4").isMatch).toBeTruthy();
|
|
91
|
+
expect(match("variants.attributes.number:3,4,5").isMatch).toBeTruthy();
|
|
92
|
+
expect(match("variants.attributes.number:1,2,3,5").isMatch).toBeFalsy();
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
test("by attribute range", async () => {
|
|
96
96
|
expect(
|
|
97
|
-
match(
|
|
97
|
+
match("variants.attributes.number:range (0 TO 5)").isMatch,
|
|
98
98
|
).toBeTruthy();
|
|
99
99
|
|
|
100
100
|
expect(
|
|
101
|
-
match(
|
|
101
|
+
match("variants.attributes.number:range (* TO 5)").isMatch,
|
|
102
102
|
).toBeTruthy();
|
|
103
103
|
|
|
104
104
|
expect(
|
|
105
|
-
match(
|
|
105
|
+
match("variants.attributes.number:range (* TO *)").isMatch,
|
|
106
106
|
).toBeTruthy();
|
|
107
107
|
});
|
|
108
108
|
|
|
@@ -118,14 +118,14 @@ describe("Search filter", () => {
|
|
|
118
118
|
|
|
119
119
|
test("by price range", async () => {
|
|
120
120
|
expect(
|
|
121
|
-
match(
|
|
121
|
+
match("variants.price.centAmount:range (1500 TO 2000)").isMatch,
|
|
122
122
|
).toBeTruthy();
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
test("by price range - or", async () => {
|
|
126
126
|
expect(
|
|
127
127
|
match(
|
|
128
|
-
|
|
128
|
+
"variants.price.centAmount:range (2 TO 1500 ), (1500 TO 3000), (3000 TO 6000)",
|
|
129
129
|
).isMatch,
|
|
130
130
|
).toBeTruthy();
|
|
131
131
|
});
|
|
@@ -136,7 +136,7 @@ describe("Search filter", () => {
|
|
|
136
136
|
|
|
137
137
|
// No currency given
|
|
138
138
|
result = match(
|
|
139
|
-
|
|
139
|
+
"variants.scopedPrice.value.centAmount:range (1500 TO 2000)",
|
|
140
140
|
);
|
|
141
141
|
expect(result.isMatch).toBeFalsy();
|
|
142
142
|
|
|
@@ -145,7 +145,7 @@ describe("Search filter", () => {
|
|
|
145
145
|
applyPriceSelector(products, { currency: "EUR" });
|
|
146
146
|
|
|
147
147
|
result = match(
|
|
148
|
-
|
|
148
|
+
"variants.scopedPrice.value.centAmount:range (1500 TO 2000)",
|
|
149
149
|
products[0],
|
|
150
150
|
);
|
|
151
151
|
expect(result.isMatch).toBeTruthy();
|
|
@@ -161,7 +161,7 @@ describe("Search filter", () => {
|
|
|
161
161
|
applyPriceSelector(products, { currency: "USD" });
|
|
162
162
|
|
|
163
163
|
result = match(
|
|
164
|
-
|
|
164
|
+
"variants.scopedPrice.value.centAmount:range (1500 TO 2000)",
|
|
165
165
|
products[0],
|
|
166
166
|
);
|
|
167
167
|
expect(result.isMatch).toBeFalsy();
|
|
@@ -170,7 +170,7 @@ describe("Search filter", () => {
|
|
|
170
170
|
products = [cloneObject(exampleProduct)];
|
|
171
171
|
applyPriceSelector(products, { currency: "EUR", country: "NL" });
|
|
172
172
|
result = match(
|
|
173
|
-
|
|
173
|
+
"variants.scopedPrice.value.centAmount:range (1500 TO 2000)",
|
|
174
174
|
products[0],
|
|
175
175
|
);
|
|
176
176
|
expect(result.isMatch).toBeFalsy();
|
|
@@ -154,7 +154,7 @@ const parseFilter = (filter: string): ExpressionSet => {
|
|
|
154
154
|
({
|
|
155
155
|
type: "Symbol",
|
|
156
156
|
kind: "int",
|
|
157
|
-
value: parseInt(t.token.match, 10),
|
|
157
|
+
value: Number.parseInt(t.token.match, 10),
|
|
158
158
|
}) as TypeSymbol,
|
|
159
159
|
)
|
|
160
160
|
.nud("STAR", 5, (_) => ({
|
|
@@ -184,9 +184,8 @@ const parseFilter = (filter: string): ExpressionSet => {
|
|
|
184
184
|
const expr: any = parser.parse({ terminals: [bp - 1] });
|
|
185
185
|
if (Array.isArray(expr)) {
|
|
186
186
|
return [left, ...expr];
|
|
187
|
-
} else {
|
|
188
|
-
return [left, expr];
|
|
189
187
|
}
|
|
188
|
+
return [left, expr];
|
|
190
189
|
})
|
|
191
190
|
.nud("(", 100, (t) => {
|
|
192
191
|
const expr: any = parser.parse({ terminals: [")"] });
|
package/src/oauth/server.test.ts
CHANGED
|
@@ -55,7 +55,7 @@ describe("OAuth2Server", () => {
|
|
|
55
55
|
|
|
56
56
|
it("should refresh a token", async () => {
|
|
57
57
|
const createResponse = await supertest(app)
|
|
58
|
-
.post(
|
|
58
|
+
.post("/my-project/anonymous/token")
|
|
59
59
|
.auth("validClientId", "validClientSecret")
|
|
60
60
|
.query({ grant_type: "client_credentials" })
|
|
61
61
|
.send();
|
package/src/oauth/server.ts
CHANGED
|
@@ -180,7 +180,8 @@ export class OAuth2Server {
|
|
|
180
180
|
request.query.scope?.toString(),
|
|
181
181
|
);
|
|
182
182
|
return response.status(200).send(token);
|
|
183
|
-
}
|
|
183
|
+
}
|
|
184
|
+
if (grantType === "refresh_token") {
|
|
184
185
|
const refreshToken =
|
|
185
186
|
request.query.refresh_token?.toString() || request.body.refresh_token;
|
|
186
187
|
if (!refreshToken) {
|
|
@@ -214,17 +215,16 @@ export class OAuth2Server {
|
|
|
214
215
|
);
|
|
215
216
|
}
|
|
216
217
|
return response.status(200).send(token);
|
|
217
|
-
} else {
|
|
218
|
-
return next(
|
|
219
|
-
new CommercetoolsError<UnsupportedGrantType>(
|
|
220
|
-
{
|
|
221
|
-
code: "unsupported_grant_type",
|
|
222
|
-
message: `Invalid parameter: grant_type: Invalid grant type: ${grantType}`,
|
|
223
|
-
},
|
|
224
|
-
400,
|
|
225
|
-
),
|
|
226
|
-
);
|
|
227
218
|
}
|
|
219
|
+
return next(
|
|
220
|
+
new CommercetoolsError<UnsupportedGrantType>(
|
|
221
|
+
{
|
|
222
|
+
code: "unsupported_grant_type",
|
|
223
|
+
message: `Invalid parameter: grant_type: Invalid grant type: ${grantType}`,
|
|
224
|
+
},
|
|
225
|
+
400,
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
async customerTokenHandler(
|
package/src/priceSelector.ts
CHANGED
|
@@ -27,25 +27,25 @@ import type { AbstractStorage } from "./storage";
|
|
|
27
27
|
import type { Writable } from "./types";
|
|
28
28
|
|
|
29
29
|
export type ProductProjectionSearchParams = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
fuzzy?: boolean;
|
|
31
|
+
fuzzyLevel?: number;
|
|
32
|
+
markMatchingVariants?: boolean;
|
|
33
|
+
staged?: boolean;
|
|
34
|
+
filter?: string[];
|
|
35
35
|
"filter.facets"?: string[];
|
|
36
36
|
"filter.query"?: string[];
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
facet?: string | string[];
|
|
38
|
+
sort?: string | string[];
|
|
39
|
+
limit?: number;
|
|
40
|
+
offset?: number;
|
|
41
|
+
withTotal?: boolean;
|
|
42
|
+
priceCurrency?: string;
|
|
43
|
+
priceCountry?: string;
|
|
44
|
+
priceCustomerGroup?: string;
|
|
45
|
+
priceChannel?: string;
|
|
46
|
+
localeProjection?: string;
|
|
47
|
+
storeProjection?: string;
|
|
48
|
+
expand?: string | string[];
|
|
49
49
|
[key: string]: QueryParam;
|
|
50
50
|
};
|
|
51
51
|
|
|
@@ -342,9 +342,8 @@ export class ProductProjectionSearch {
|
|
|
342
342
|
max: numValues > 0 ? Math.max(...values) : 0,
|
|
343
343
|
mean: numValues > 0 ? mean(values) : 0,
|
|
344
344
|
};
|
|
345
|
-
} else {
|
|
346
|
-
throw new Error("not supported");
|
|
347
345
|
}
|
|
346
|
+
throw new Error("not supported");
|
|
348
347
|
}) || [];
|
|
349
348
|
const data: RangeFacetResult = {
|
|
350
349
|
type: "range",
|
package/src/product-search.ts
CHANGED
|
@@ -13,6 +13,12 @@ import { validateSearchQuery } from "./lib/searchQueryTypeChecker";
|
|
|
13
13
|
import { applyPriceSelector } from "./priceSelector";
|
|
14
14
|
import type { AbstractStorage } from "./storage";
|
|
15
15
|
|
|
16
|
+
interface ProductSearchVariantAvailability {
|
|
17
|
+
isOnStock: boolean;
|
|
18
|
+
availableQuantity: number;
|
|
19
|
+
isOnStockForChannel: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
export class ProductSearch {
|
|
17
23
|
protected _storage: AbstractStorage;
|
|
18
24
|
|
|
@@ -24,10 +30,32 @@ export class ProductSearch {
|
|
|
24
30
|
projectKey: string,
|
|
25
31
|
params: ProductSearchRequest,
|
|
26
32
|
): ProductPagedSearchResponse {
|
|
27
|
-
|
|
33
|
+
const availabilityBySku = this._storage
|
|
34
|
+
.all(projectKey, "inventory-entry")
|
|
35
|
+
.reduce((acc, entry) => {
|
|
36
|
+
const existingEntry = acc.get(entry.sku);
|
|
37
|
+
|
|
38
|
+
acc.set(entry.sku, {
|
|
39
|
+
isOnStock: existingEntry?.isOnStock || entry.quantityOnStock > 0,
|
|
40
|
+
availableQuantity:
|
|
41
|
+
existingEntry?.availableQuantity ?? 0 + entry.quantityOnStock,
|
|
42
|
+
// NOTE: This doesn't handle inventory entries for multiple channels,
|
|
43
|
+
// so it doesn't exactly replicate the behavior of the commercetools api.
|
|
44
|
+
isOnStockForChannel:
|
|
45
|
+
existingEntry?.isOnStockForChannel ?? entry.supplyChannel?.id,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return acc;
|
|
49
|
+
}, new Map<string, ProductSearchVariantAvailability>());
|
|
50
|
+
|
|
51
|
+
let productResources = this._storage
|
|
28
52
|
.all(projectKey, "product")
|
|
29
53
|
.map((r) =>
|
|
30
|
-
this.
|
|
54
|
+
this.transformProduct(
|
|
55
|
+
r,
|
|
56
|
+
params.productProjectionParameters?.staged ?? false,
|
|
57
|
+
availabilityBySku,
|
|
58
|
+
),
|
|
31
59
|
)
|
|
32
60
|
.filter((p) => {
|
|
33
61
|
if (!(params.productProjectionParameters?.staged ?? false)) {
|
|
@@ -46,7 +74,7 @@ export class ProductSearch {
|
|
|
46
74
|
const matchFunc = parseSearchQuery(params.query);
|
|
47
75
|
|
|
48
76
|
// Filters can modify the output. So clone the resources first.
|
|
49
|
-
|
|
77
|
+
productResources = productResources.filter((resource) =>
|
|
50
78
|
matchFunc(resource, markMatchingVariant),
|
|
51
79
|
);
|
|
52
80
|
} catch (err) {
|
|
@@ -63,7 +91,7 @@ export class ProductSearch {
|
|
|
63
91
|
|
|
64
92
|
// Apply the priceSelector
|
|
65
93
|
if (params.productProjectionParameters) {
|
|
66
|
-
applyPriceSelector(
|
|
94
|
+
applyPriceSelector(productResources, {
|
|
67
95
|
country: params.productProjectionParameters.priceCountry,
|
|
68
96
|
channel: params.productProjectionParameters.priceChannel,
|
|
69
97
|
customerGroup: params.productProjectionParameters.priceCustomerGroup,
|
|
@@ -76,7 +104,10 @@ export class ProductSearch {
|
|
|
76
104
|
|
|
77
105
|
const offset = params.offset || 0;
|
|
78
106
|
const limit = params.limit || 20;
|
|
79
|
-
const productProjectionsResult =
|
|
107
|
+
const productProjectionsResult = productResources.slice(
|
|
108
|
+
offset,
|
|
109
|
+
offset + limit,
|
|
110
|
+
);
|
|
80
111
|
|
|
81
112
|
/**
|
|
82
113
|
* Do not supply productProjection if productProjectionParameters are not given
|
|
@@ -100,7 +131,7 @@ export class ProductSearch {
|
|
|
100
131
|
);
|
|
101
132
|
|
|
102
133
|
return {
|
|
103
|
-
total:
|
|
134
|
+
total: productResources.length,
|
|
104
135
|
offset: offset,
|
|
105
136
|
limit: limit,
|
|
106
137
|
results: results,
|
|
@@ -108,7 +139,11 @@ export class ProductSearch {
|
|
|
108
139
|
};
|
|
109
140
|
}
|
|
110
141
|
|
|
111
|
-
|
|
142
|
+
transformProduct(
|
|
143
|
+
product: Product,
|
|
144
|
+
staged: boolean,
|
|
145
|
+
availabilityBySku: Map<string, ProductSearchVariantAvailability>,
|
|
146
|
+
): ProductProjection {
|
|
112
147
|
const obj = !staged
|
|
113
148
|
? product.masterData.current
|
|
114
149
|
: product.masterData.staged;
|
|
@@ -125,7 +160,12 @@ export class ProductSearch {
|
|
|
125
160
|
slug: obj.slug,
|
|
126
161
|
categories: obj.categories,
|
|
127
162
|
masterVariant: obj.masterVariant,
|
|
128
|
-
variants: obj.variants
|
|
163
|
+
variants: obj.variants.map((variant) => ({
|
|
164
|
+
...variant,
|
|
165
|
+
availability: variant.sku
|
|
166
|
+
? availabilityBySku.get(variant.sku)
|
|
167
|
+
: { isOnStock: false, availableQuantity: 0, isOnStockForChannel: [] },
|
|
168
|
+
})),
|
|
129
169
|
productType: product.productType,
|
|
130
170
|
hasStagedChanges: product.masterData.hasStagedChanges,
|
|
131
171
|
published: product.masterData.published,
|
|
@@ -7,21 +7,21 @@ import type {
|
|
|
7
7
|
CompanyDraft,
|
|
8
8
|
DivisionDraft,
|
|
9
9
|
} from "@commercetools/platform-sdk";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
import type {
|
|
11
|
+
Associate,
|
|
12
|
+
BusinessUnit,
|
|
13
|
+
BusinessUnitAddAddressAction,
|
|
14
|
+
BusinessUnitAddAssociateAction,
|
|
15
|
+
BusinessUnitAddStoreAction,
|
|
16
|
+
BusinessUnitChangeAddressAction,
|
|
17
|
+
BusinessUnitChangeNameAction,
|
|
18
|
+
BusinessUnitChangeParentUnitAction,
|
|
19
|
+
BusinessUnitDraft,
|
|
20
|
+
BusinessUnitSetAssociatesAction,
|
|
21
|
+
BusinessUnitSetContactEmailAction,
|
|
22
|
+
BusinessUnitSetStoreModeAction,
|
|
23
|
+
Company,
|
|
24
|
+
Division,
|
|
25
25
|
} from "@commercetools/platform-sdk";
|
|
26
26
|
import type { Config } from "~src/config";
|
|
27
27
|
import { generateRandomString, getBaseResourceProperties } from "../helpers";
|
|
@@ -110,7 +110,8 @@ export class BusinessUnitRepository extends AbstractResourceRepository<"business
|
|
|
110
110
|
|
|
111
111
|
this.saveNew(context, division);
|
|
112
112
|
return division;
|
|
113
|
-
}
|
|
113
|
+
}
|
|
114
|
+
if (this._isCompanyDraft(draft)) {
|
|
114
115
|
const company = resource as Company;
|
|
115
116
|
|
|
116
117
|
this.saveNew(context, company);
|