medusa-product-helper 0.0.13 → 0.0.16

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.
Files changed (22) hide show
  1. package/.medusa/server/src/api/store/product-helper/products/route.js +76 -0
  2. package/.medusa/server/src/api/store/product-helper/products/validators.js +2 -1
  3. package/.medusa/server/src/config/product-helper-options.js +15 -1
  4. package/.medusa/server/src/index.js +101 -0
  5. package/.medusa/server/src/providers/filter-providers/availability-provider.js +96 -0
  6. package/.medusa/server/src/providers/filter-providers/base-filter-provider.js +32 -0
  7. package/.medusa/server/src/providers/filter-providers/base-product-provider.js +122 -0
  8. package/.medusa/server/src/providers/filter-providers/category-provider.js +55 -0
  9. package/.medusa/server/src/providers/filter-providers/collection-provider.js +53 -0
  10. package/.medusa/server/src/providers/filter-providers/index.js +94 -0
  11. package/.medusa/server/src/providers/filter-providers/metadata-provider.js +88 -0
  12. package/.medusa/server/src/providers/filter-providers/price-range-provider.js +108 -0
  13. package/.medusa/server/src/providers/filter-providers/promotion-provider.js +197 -0
  14. package/.medusa/server/src/providers/filter-providers/promotion-window-provider.js +125 -0
  15. package/.medusa/server/src/providers/filter-providers/rating-provider.js +92 -0
  16. package/.medusa/server/src/services/dynamic-filter-service.js +814 -0
  17. package/.medusa/server/src/services/filter-provider-loader.js +155 -0
  18. package/.medusa/server/src/services/filter-provider-registry.js +142 -0
  19. package/.medusa/server/src/services/product-filter-service.js +230 -0
  20. package/.medusa/server/src/utils/query-parser.js +103 -0
  21. package/README.md +89 -0
  22. package/package.json +3 -3
@@ -0,0 +1,814 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DynamicFilterService = void 0;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const filter_provider_registry_1 = require("./filter-provider-registry");
6
+ /**
7
+ * Service that orchestrates filter application using filter providers.
8
+ *
9
+ * This service coordinates between filter providers and Medusa's RemoteQuery
10
+ * to build and execute product queries with dynamic filters. It handles:
11
+ * - Resolving providers for filter identifiers
12
+ * - Validating filter values
13
+ * - Applying filters to build query filters
14
+ * - Executing queries with pagination and sorting
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const service = new DynamicFilterService(container)
19
+ * const result = await service.applyFilters({
20
+ * filterParams: {
21
+ * category_id: ["cat_123"],
22
+ * price_range: { min: 10, max: 100 },
23
+ * },
24
+ * options: pluginOptions,
25
+ * pagination: { limit: 20, offset: 0 },
26
+ * })
27
+ * ```
28
+ */
29
+ class DynamicFilterService {
30
+ constructor(container) {
31
+ // Try to resolve registry, create if not found
32
+ try {
33
+ this.registry = container.resolve("filterProviderRegistry");
34
+ }
35
+ catch {
36
+ // Registry not registered, create new one and register built-in providers
37
+ // This allows the service to work even if registry wasn't pre-registered
38
+ this.registry = new filter_provider_registry_1.FilterProviderRegistry();
39
+ // Auto-register built-in providers
40
+ try {
41
+ const { registerBuiltInProviders } = require("../providers/filter-providers");
42
+ registerBuiltInProviders(this.registry);
43
+ }
44
+ catch (error) {
45
+ // If registration fails, log but continue
46
+ console.warn("[DynamicFilterService] Failed to auto-register built-in providers:", error instanceof Error ? error.message : String(error));
47
+ }
48
+ }
49
+ this.query = container.resolve(utils_1.ContainerRegistrationKeys.QUERY);
50
+ }
51
+ /**
52
+ * Get the filter provider registry.
53
+ * Useful for registering providers programmatically.
54
+ */
55
+ getRegistry() {
56
+ return this.registry;
57
+ }
58
+ /**
59
+ * Apply filters to a product query and return results.
60
+ *
61
+ * This method:
62
+ * 1. Iterates through filter parameters
63
+ * 2. Resolves providers for each filter identifier
64
+ * 3. Validates filter values (if provider has validate method)
65
+ * 4. Applies filters to build the query filters object
66
+ * 5. Executes the query with pagination and sorting
67
+ * 6. Returns products and count
68
+ *
69
+ * @param options - Options for applying filters
70
+ * @returns Products and count matching the filters
71
+ * @throws Error if provider validation fails or provider application fails (fail-fast)
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * const result = await service.applyFilters({
76
+ * filterParams: {
77
+ * category_id: ["cat_123", "cat_456"],
78
+ * price_range: { min: 10, max: 100 },
79
+ * },
80
+ * options: pluginOptions,
81
+ * pagination: { limit: 20, offset: 0 },
82
+ * projection: { fields: ["id", "title", "handle"] },
83
+ * })
84
+ * ```
85
+ */
86
+ async applyFilters(options) {
87
+ const { filterParams, options: pluginOptions, pagination, projection, context = {}, } = options;
88
+ // Build filter context
89
+ const filterContext = {
90
+ ...context,
91
+ options: pluginOptions,
92
+ };
93
+ // Start with empty filters object
94
+ let queryFilters = {};
95
+ let priceRangeFilter;
96
+ let promotionFilter;
97
+ // Apply each filter using its provider
98
+ for (const [identifier, value] of Object.entries(filterParams)) {
99
+ // Skip undefined/null values
100
+ if (value === undefined || value === null) {
101
+ continue;
102
+ }
103
+ const provider = this.registry.get(identifier);
104
+ if (!provider) {
105
+ // Log warning but don't fail - allow unknown filters to be ignored
106
+ // This enables custom providers to be added without breaking existing code
107
+ console.warn(`[DynamicFilterService] No filter provider found for identifier: "${identifier}". ` +
108
+ `Filter will be ignored. Available providers: ${this.registry.getIdentifiers().join(", ")}`);
109
+ continue;
110
+ }
111
+ try {
112
+ // Validate if provider has validation method
113
+ if (provider.validate) {
114
+ const validationResult = provider.validate(value);
115
+ if (validationResult && Array.isArray(validationResult)) {
116
+ // Provider returned validation errors
117
+ const errors = validationResult
118
+ .map((err) => `${err.path ? `${err.path}: ` : ""}${err.message}`)
119
+ .join(", ");
120
+ throw new Error(`Filter validation failed for "${identifier}": ${errors}`);
121
+ }
122
+ // If validate() throws, it will be caught below
123
+ }
124
+ // Apply filter
125
+ const result = provider.apply(queryFilters, value, filterContext);
126
+ queryFilters = await Promise.resolve(result);
127
+ // Extract price range filter for post-query filtering
128
+ // Price filtering requires post-query filtering because Medusa's query API
129
+ // doesn't support price range filtering directly
130
+ if (queryFilters.__price_range_filter__) {
131
+ priceRangeFilter = queryFilters.__price_range_filter__;
132
+ // Remove from queryFilters so it doesn't get passed to the query
133
+ delete queryFilters.__price_range_filter__;
134
+ }
135
+ // Extract promotion filter for post-query filtering
136
+ // Promotion filtering requires querying Medusa's promotion module and
137
+ // matching products via promotion rules/targets, which can't be done directly
138
+ if (queryFilters.__promotion_filter__) {
139
+ promotionFilter = queryFilters.__promotion_filter__;
140
+ // Remove from queryFilters so it doesn't get passed to the query
141
+ delete queryFilters.__promotion_filter__;
142
+ }
143
+ }
144
+ catch (error) {
145
+ // Fail-fast: throw immediately on provider errors
146
+ const errorMessage = error instanceof Error ? error.message : String(error);
147
+ throw new Error(`Error applying filter "${identifier}": ${errorMessage}`);
148
+ }
149
+ }
150
+ // Build query options for Query.graph()
151
+ // Only include filters if they're not empty
152
+ // Important: Check if fields array is empty or undefined, not just truthy
153
+ let fields = (projection?.fields && projection.fields.length > 0)
154
+ ? [...projection.fields]
155
+ : ["*"];
156
+ // Ensure essential relations are included to match Medusa's official format
157
+ // Even when using ["*"], we need to explicitly ensure relations are included
158
+ // Medusa's RemoteQuery may not automatically include nested relations with "*"
159
+ if (fields.includes("*")) {
160
+ // When using "*", explicitly add relations to ensure they're included
161
+ // This ensures variants, options, images, collection, and type are returned
162
+ fields = [
163
+ "*",
164
+ "variants.*",
165
+ "variants.prices.*",
166
+ "options.*",
167
+ "options.values.*",
168
+ "images.*",
169
+ "collection.*",
170
+ "type.*",
171
+ ];
172
+ }
173
+ else {
174
+ // If specific fields are requested, ensure essential relations are included
175
+ const hasVariants = fields.some(f => f.startsWith("variants"));
176
+ const hasOptions = fields.some(f => f.startsWith("options"));
177
+ const hasImages = fields.some(f => f.startsWith("images"));
178
+ const hasCollection = fields.includes("collection") || fields.some(f => f.startsWith("collection"));
179
+ const hasType = fields.includes("type") || fields.some(f => f.startsWith("type"));
180
+ // Add missing essential relations
181
+ if (!hasVariants) {
182
+ fields.push("variants.*", "variants.prices.*");
183
+ }
184
+ if (!hasOptions) {
185
+ fields.push("options.*", "options.values.*");
186
+ }
187
+ if (!hasImages)
188
+ fields.push("images.*");
189
+ if (!hasCollection)
190
+ fields.push("collection.*");
191
+ if (!hasType)
192
+ fields.push("type.*");
193
+ }
194
+ // Build query context for pricing (similar to Medusa's official route)
195
+ const queryContext = {};
196
+ // Add pricing context if available
197
+ if (context && typeof context === "object" && "pricingContext" in context) {
198
+ const pricingContext = context.pricingContext;
199
+ if (pricingContext) {
200
+ queryContext.variants = {
201
+ calculated_price: (0, utils_1.QueryContext)(pricingContext),
202
+ };
203
+ }
204
+ }
205
+ const queryOptions = {
206
+ entity: "product",
207
+ fields,
208
+ ...(Object.keys(queryFilters).length > 0 && { filters: queryFilters }),
209
+ ...(pagination && {
210
+ pagination: {
211
+ ...(pagination.limit !== undefined && { take: pagination.limit }),
212
+ ...(pagination.offset !== undefined && { skip: pagination.offset }),
213
+ },
214
+ }),
215
+ ...(Object.keys(queryContext).length > 0 && { context: queryContext }),
216
+ };
217
+ // Debug: Log query structure when metadata filters are present
218
+ if (Object.keys(queryFilters).some((k) => k === "metadata")) {
219
+ console.log("[DynamicFilterService] Query filters:", JSON.stringify(queryFilters, null, 2));
220
+ console.log("[DynamicFilterService] Query fields:", fields);
221
+ console.log("[DynamicFilterService] Query context:", JSON.stringify(queryContext, null, 2));
222
+ }
223
+ // Execute query with pagination
224
+ // Note: query.graph() returns { data: Product[], metadata: { count, skip, take } }
225
+ const result = await this.query.graph(queryOptions);
226
+ // Extract products from result.data
227
+ // Following the pattern used in other Medusa routes (e.g., medusa-review-rating)
228
+ // query.graph() returns { data: Product[], metadata: { count, skip, take } }
229
+ const { data: productsRaw = [], metadata } = result;
230
+ // Ensure products is always an array and filter out null/undefined
231
+ let products = [];
232
+ if (Array.isArray(productsRaw)) {
233
+ products = productsRaw.filter((p) => p !== null && p !== undefined);
234
+ }
235
+ else {
236
+ // Log unexpected structure for debugging
237
+ console.warn("[DynamicFilterService] Unexpected data structure from query.graph():", {
238
+ dataType: typeof productsRaw,
239
+ isArray: Array.isArray(productsRaw),
240
+ hasData: !!productsRaw,
241
+ sample: productsRaw,
242
+ });
243
+ }
244
+ // Post-query filtering for price range
245
+ // This is done after the query because Medusa's query API doesn't support
246
+ // price range filtering directly - we need to filter based on variant prices
247
+ if (priceRangeFilter && products.length > 0) {
248
+ products = products.filter((product) => {
249
+ // A product matches if at least one variant has a price within the range
250
+ if (!product.variants || !Array.isArray(product.variants) || product.variants.length === 0) {
251
+ return false;
252
+ }
253
+ // Check each variant to see if it has a price within the range
254
+ return product.variants.some((variant) => {
255
+ // Try calculated_price first (if pricing context was provided)
256
+ let price;
257
+ let currencyCode;
258
+ if (variant.calculated_price?.calculated_amount !== undefined) {
259
+ price = variant.calculated_price.calculated_amount;
260
+ currencyCode = variant.calculated_price.currency_code;
261
+ }
262
+ else if (variant.prices && Array.isArray(variant.prices) && variant.prices.length > 0) {
263
+ // Fall back to first price in prices array
264
+ const firstPrice = variant.prices[0];
265
+ price = firstPrice.amount;
266
+ currencyCode = firstPrice.currency_code;
267
+ }
268
+ // If no price found, exclude this variant
269
+ if (price === undefined) {
270
+ return false;
271
+ }
272
+ // If currency_code is specified in filter, check it matches
273
+ if (priceRangeFilter.currency_code && currencyCode !== priceRangeFilter.currency_code) {
274
+ return false;
275
+ }
276
+ // Check if price is within range
277
+ const min = priceRangeFilter.min;
278
+ const max = priceRangeFilter.max;
279
+ if (min !== undefined && price < min) {
280
+ return false;
281
+ }
282
+ if (max !== undefined && price > max) {
283
+ return false;
284
+ }
285
+ return true;
286
+ });
287
+ });
288
+ // Update metadata count after filtering
289
+ if (metadata) {
290
+ metadata.count = products.length;
291
+ }
292
+ }
293
+ // Post-query filtering for promotions
294
+ // This is done after the query because promotion-product relationships
295
+ // are complex and require querying the promotion module
296
+ if (promotionFilter && products.length > 0) {
297
+ try {
298
+ // Query all promotions (we'll filter in memory)
299
+ // This ensures we don't miss promotions due to query filter limitations
300
+ const promotionQueryOptions = {
301
+ entity: "promotion",
302
+ fields: [
303
+ "id",
304
+ "code",
305
+ "type",
306
+ "application_method.*",
307
+ "campaign.*",
308
+ "rules.*",
309
+ "rules.values.*",
310
+ "starts_at",
311
+ "ends_at",
312
+ "status",
313
+ ],
314
+ };
315
+ // Add basic filters if specified
316
+ const promotionQueryFilters = {};
317
+ if (promotionFilter.status) {
318
+ promotionQueryFilters.status = promotionFilter.status;
319
+ }
320
+ if (promotionFilter.promotion_type) {
321
+ promotionQueryFilters.type = promotionFilter.promotion_type;
322
+ }
323
+ if (Object.keys(promotionQueryFilters).length > 0) {
324
+ promotionQueryOptions.filters = promotionQueryFilters;
325
+ }
326
+ const promotionResult = await this.query.graph(promotionQueryOptions);
327
+ let allPromotions = Array.isArray(promotionResult.data)
328
+ ? promotionResult.data
329
+ : [];
330
+ // Debug: Log all promotions found
331
+ console.log(`[DynamicFilterService] Found ${allPromotions.length} total promotions`);
332
+ // Filter promotions by date range in memory
333
+ const now = new Date();
334
+ const promotions = allPromotions.filter((promotion) => {
335
+ // Filter by date range if specified
336
+ if (promotionFilter.starts_at || promotionFilter.ends_at) {
337
+ const startsAt = promotion.starts_at
338
+ ? new Date(promotion.starts_at)
339
+ : null;
340
+ const endsAt = promotion.ends_at ? new Date(promotion.ends_at) : null;
341
+ if (promotionFilter.starts_at) {
342
+ const filterStart = new Date(promotionFilter.starts_at);
343
+ if (endsAt && endsAt < filterStart) {
344
+ return false; // Promotion ends before filter start
345
+ }
346
+ }
347
+ if (promotionFilter.ends_at) {
348
+ const filterEnd = new Date(promotionFilter.ends_at);
349
+ if (startsAt && startsAt > filterEnd) {
350
+ return false; // Promotion starts after filter end
351
+ }
352
+ }
353
+ }
354
+ else {
355
+ // Default: only include promotions active now
356
+ const startsAt = promotion.starts_at
357
+ ? new Date(promotion.starts_at)
358
+ : null;
359
+ const endsAt = promotion.ends_at ? new Date(promotion.ends_at) : null;
360
+ if (startsAt && startsAt > now) {
361
+ return false; // Promotion hasn't started yet
362
+ }
363
+ if (endsAt && endsAt < now) {
364
+ return false; // Promotion has ended
365
+ }
366
+ // Include open-ended promotions if include_open_ended is true
367
+ if (!endsAt &&
368
+ promotionFilter.include_open_ended === false) {
369
+ return false; // Exclude open-ended promotions
370
+ }
371
+ }
372
+ return true;
373
+ });
374
+ // Debug: Log filtered promotions
375
+ console.log(`[DynamicFilterService] Found ${promotions.length} promotions after date filtering`);
376
+ if (promotions.length > 0) {
377
+ console.log(`[DynamicFilterService] Sample promotion:`, JSON.stringify({
378
+ id: promotions[0].id,
379
+ code: promotions[0].code,
380
+ type: promotions[0].type,
381
+ application_method: promotions[0].application_method,
382
+ rules_count: promotions[0].rules?.length || 0,
383
+ rules: promotions[0].rules,
384
+ }, null, 2));
385
+ }
386
+ // Initialize product tracking sets before we start processing
387
+ const productIdsWithPromotions = new Set();
388
+ const promotionDiscountMap = new Map(); // productId -> max discount percentage
389
+ // If promotions have no rules, try querying promotion rules separately
390
+ // In Medusa v2, rules might be stored as a separate entity or relationship
391
+ const promotionsWithoutRules = promotions.filter((p) => !p.rules || p.rules.length === 0);
392
+ if (promotionsWithoutRules.length > 0) {
393
+ console.log(`[DynamicFilterService] Found ${promotionsWithoutRules.length} promotions without rules, querying rules separately`);
394
+ for (const promotion of promotionsWithoutRules) {
395
+ try {
396
+ // Try querying promotion_rule entity
397
+ const ruleQueryResult = await this.query.graph({
398
+ entity: "promotion_rule",
399
+ fields: ["id", "attribute", "values", "operator", "promotion_id"],
400
+ filters: {
401
+ promotion_id: [promotion.id],
402
+ },
403
+ });
404
+ const rules = Array.isArray(ruleQueryResult.data)
405
+ ? ruleQueryResult.data
406
+ : [];
407
+ if (rules.length > 0) {
408
+ console.log(`[DynamicFilterService] Found ${rules.length} rules for promotion ${promotion.id}:`, JSON.stringify(rules, null, 2));
409
+ // Attach rules to promotion object
410
+ promotion.rules = rules;
411
+ }
412
+ else {
413
+ // Try querying with different entity names or structures
414
+ console.log(`[DynamicFilterService] No promotion_rule found for ${promotion.id}, trying alternative queries`);
415
+ // Try querying promotion_rule_value entity
416
+ try {
417
+ const ruleValueQueryResult = await this.query.graph({
418
+ entity: "promotion_rule_value",
419
+ fields: ["id", "value", "promotion_rule_id"],
420
+ filters: {
421
+ promotion_rule: {
422
+ promotion_id: [promotion.id],
423
+ },
424
+ },
425
+ });
426
+ const ruleValues = Array.isArray(ruleValueQueryResult.data)
427
+ ? ruleValueQueryResult.data
428
+ : [];
429
+ if (ruleValues.length > 0) {
430
+ console.log(`[DynamicFilterService] Found ${ruleValues.length} rule values for promotion ${promotion.id}`);
431
+ // Extract product IDs from rule values
432
+ for (const ruleValue of ruleValues) {
433
+ if (ruleValue && typeof ruleValue === "object" && "value" in ruleValue) {
434
+ const value = ruleValue.value;
435
+ if (typeof value === "string") {
436
+ // This might be a product ID
437
+ productIdsWithPromotions.add(value);
438
+ }
439
+ }
440
+ }
441
+ }
442
+ }
443
+ catch (error) {
444
+ console.log(`[DynamicFilterService] Could not query promotion_rule_value:`, error instanceof Error ? error.message : String(error));
445
+ }
446
+ // Try querying the promotion again with deeper expansion
447
+ try {
448
+ const deepPromotionQuery = await this.query.graph({
449
+ entity: "promotion",
450
+ fields: [
451
+ "id",
452
+ "rules.*",
453
+ "rules.values.*",
454
+ "rules.batch_rules.*",
455
+ "rules.batch_rules.values.*",
456
+ ],
457
+ filters: {
458
+ id: [promotion.id],
459
+ },
460
+ });
461
+ const deepPromotions = Array.isArray(deepPromotionQuery.data)
462
+ ? deepPromotionQuery.data
463
+ : [];
464
+ if (deepPromotions.length > 0 && deepPromotions[0].rules) {
465
+ console.log(`[DynamicFilterService] Found rules with deep expansion:`, JSON.stringify(deepPromotions[0].rules, null, 2));
466
+ promotion.rules = deepPromotions[0].rules;
467
+ }
468
+ }
469
+ catch (error) {
470
+ console.log(`[DynamicFilterService] Could not query promotion with deep expansion:`, error instanceof Error ? error.message : String(error));
471
+ }
472
+ }
473
+ }
474
+ catch (error) {
475
+ // Entity might not exist or have different name - that's okay
476
+ console.log(`[DynamicFilterService] Could not query promotion_rule entity (this is normal if rules are embedded):`, error instanceof Error ? error.message : String(error));
477
+ }
478
+ }
479
+ }
480
+ // Extract product IDs from promotion rules/targets
481
+ // (productIdsWithPromotions and promotionDiscountMap are already declared above)
482
+ for (const promotion of promotions) {
483
+ // Check if promotion is active now (if date range not specified)
484
+ if (!promotionFilter.starts_at && !promotionFilter.ends_at) {
485
+ const startsAt = promotion.starts_at
486
+ ? new Date(promotion.starts_at)
487
+ : null;
488
+ const endsAt = promotion.ends_at ? new Date(promotion.ends_at) : null;
489
+ const now = new Date();
490
+ if (startsAt && startsAt > now) {
491
+ continue; // Promotion hasn't started yet
492
+ }
493
+ if (endsAt && endsAt < now) {
494
+ continue; // Promotion has ended
495
+ }
496
+ }
497
+ // Calculate discount percentage based on application_method type
498
+ // In Medusa, the discount type is stored in application_method.type, not promotion.type
499
+ let discountPercentage;
500
+ const applicationMethod = promotion.application_method;
501
+ if (applicationMethod &&
502
+ typeof applicationMethod === "object" &&
503
+ "type" in applicationMethod) {
504
+ const appMethodType = applicationMethod.type;
505
+ if (appMethodType === "fixed" &&
506
+ "target_type" in applicationMethod &&
507
+ applicationMethod.target_type === "items") {
508
+ // Fixed amount discount on items (amount_off_product)
509
+ // Will calculate percentage per product later
510
+ discountPercentage = undefined;
511
+ }
512
+ else if (appMethodType === "percentage" &&
513
+ "target_type" in applicationMethod &&
514
+ applicationMethod.target_type === "items") {
515
+ // Percentage discount on items (percentage_off_product)
516
+ if ("value" in applicationMethod) {
517
+ discountPercentage =
518
+ typeof applicationMethod.value === "number"
519
+ ? applicationMethod.value
520
+ : Number(applicationMethod.value);
521
+ }
522
+ }
523
+ else if (appMethodType === "percentage" &&
524
+ "target_type" in applicationMethod &&
525
+ applicationMethod.target_type === "order") {
526
+ // Order-level percentage discount
527
+ if ("value" in applicationMethod) {
528
+ discountPercentage =
529
+ typeof applicationMethod.value === "number"
530
+ ? applicationMethod.value
531
+ : Number(applicationMethod.value);
532
+ }
533
+ }
534
+ else if (appMethodType === "free_shipping") {
535
+ // Free shipping - consider as 0% for product filtering
536
+ discountPercentage = 0;
537
+ }
538
+ }
539
+ // Fallback: Check promotion.type for backward compatibility
540
+ if (discountPercentage === undefined && promotion.type) {
541
+ if (promotion.type === "percentage_off_product") {
542
+ if (applicationMethod &&
543
+ typeof applicationMethod === "object" &&
544
+ "value" in applicationMethod) {
545
+ discountPercentage =
546
+ typeof applicationMethod.value === "number"
547
+ ? applicationMethod.value
548
+ : Number(applicationMethod.value);
549
+ }
550
+ }
551
+ else if (promotion.type === "amount_off_product") {
552
+ // Will calculate per product
553
+ discountPercentage = undefined;
554
+ }
555
+ else if (promotion.type === "percentage_off_order") {
556
+ if (applicationMethod &&
557
+ typeof applicationMethod === "object" &&
558
+ "value" in applicationMethod) {
559
+ discountPercentage =
560
+ typeof applicationMethod.value === "number"
561
+ ? applicationMethod.value
562
+ : Number(applicationMethod.value);
563
+ }
564
+ }
565
+ }
566
+ // Extract product IDs from promotion rules
567
+ // Promotions use rules to target products (e.g., items, product_id, product_ids, collection_ids, etc.)
568
+ if (promotion.rules && Array.isArray(promotion.rules) && promotion.rules.length > 0) {
569
+ for (const rule of promotion.rules) {
570
+ // Check rule attribute - Medusa uses various attribute names
571
+ // Common ones: "items", "product_id", "product_ids", "product_collection_id", etc.
572
+ const ruleAttribute = rule.attribute;
573
+ if (ruleAttribute === "items" ||
574
+ ruleAttribute === "product_id" ||
575
+ ruleAttribute === "product_ids") {
576
+ // Direct product targeting
577
+ const productIds = Array.isArray(rule.values)
578
+ ? rule.values
579
+ : rule.values
580
+ ? [rule.values]
581
+ : [];
582
+ for (const productId of productIds) {
583
+ if (typeof productId === "string") {
584
+ productIdsWithPromotions.add(productId);
585
+ // Track maximum discount percentage for this product
586
+ if (discountPercentage !== undefined) {
587
+ const currentMax = promotionDiscountMap.get(productId) || 0;
588
+ promotionDiscountMap.set(productId, Math.max(currentMax, discountPercentage));
589
+ }
590
+ }
591
+ }
592
+ }
593
+ else if (ruleAttribute === "product_collection_id" ||
594
+ ruleAttribute === "product_collection_ids" ||
595
+ ruleAttribute === "collection_id" ||
596
+ ruleAttribute === "collection_ids") {
597
+ // Collection-based targeting - we'll need to get products from collections
598
+ // For now, we'll query products by collection_id
599
+ const collectionIds = Array.isArray(rule.values)
600
+ ? rule.values
601
+ : rule.values
602
+ ? [rule.values]
603
+ : [];
604
+ // Query products in these collections
605
+ for (const collectionId of collectionIds) {
606
+ if (typeof collectionId === "string") {
607
+ const collectionProductsResult = await this.query.graph({
608
+ entity: "product",
609
+ fields: ["id"],
610
+ filters: {
611
+ collection_id: [collectionId],
612
+ },
613
+ });
614
+ const collectionProducts = Array.isArray(collectionProductsResult.data)
615
+ ? collectionProductsResult.data
616
+ : [];
617
+ for (const product of collectionProducts) {
618
+ if (product && typeof product === "object" && "id" in product) {
619
+ const productId = product.id;
620
+ productIdsWithPromotions.add(productId);
621
+ if (discountPercentage !== undefined) {
622
+ const currentMax = promotionDiscountMap.get(productId) || 0;
623
+ promotionDiscountMap.set(productId, Math.max(currentMax, discountPercentage));
624
+ }
625
+ }
626
+ }
627
+ }
628
+ }
629
+ }
630
+ }
631
+ }
632
+ else {
633
+ // No rules found - if target_type is "items", the promotion might apply to all products
634
+ // OR the products might be stored in a different way (e.g., join table, metadata, etc.)
635
+ const applicationMethod = promotion.application_method;
636
+ if (applicationMethod &&
637
+ typeof applicationMethod === "object" &&
638
+ "target_type" in applicationMethod &&
639
+ applicationMethod.target_type === "items") {
640
+ console.log(`[DynamicFilterService] Promotion ${promotion.id} targets items but has no rules`);
641
+ // Try to find products associated with this promotion through alternative methods
642
+ // Option 1: Check if products are stored in promotion metadata
643
+ if (promotion.metadata && typeof promotion.metadata === "object") {
644
+ const metadata = promotion.metadata;
645
+ if (metadata.product_ids && Array.isArray(metadata.product_ids)) {
646
+ console.log(`[DynamicFilterService] Found product_ids in promotion metadata`);
647
+ const productIds = metadata.product_ids;
648
+ for (const productId of productIds) {
649
+ if (typeof productId === "string") {
650
+ productIdsWithPromotions.add(productId);
651
+ if (discountPercentage !== undefined) {
652
+ const currentMax = promotionDiscountMap.get(productId) || 0;
653
+ promotionDiscountMap.set(productId, Math.max(currentMax, discountPercentage));
654
+ }
655
+ }
656
+ }
657
+ }
658
+ }
659
+ // Option 2: If no rules and no metadata product_ids,
660
+ // and the promotion is active, we might need to include all products
661
+ // BUT this is risky - only do it if explicitly configured
662
+ // For now, we'll log and skip products for promotions without rules
663
+ console.log(`[DynamicFilterService] Skipping products for promotion ${promotion.id} - no rules found and no product_ids in metadata`);
664
+ }
665
+ }
666
+ }
667
+ // Debug: Log product IDs found with promotions
668
+ console.log(`[DynamicFilterService] Found ${productIdsWithPromotions.size} products with promotions`);
669
+ // FALLBACK: If no products found but we have promotions targeting items without rules,
670
+ // include all products (this is a workaround for when rules aren't queryable)
671
+ if (productIdsWithPromotions.size === 0 && promotions.length > 0) {
672
+ const itemsTargetingPromotions = promotions.filter((p) => {
673
+ const appMethod = p.application_method;
674
+ return (appMethod &&
675
+ typeof appMethod === "object" &&
676
+ "target_type" in appMethod &&
677
+ appMethod.target_type === "items" &&
678
+ (!p.rules || p.rules.length === 0));
679
+ });
680
+ if (itemsTargetingPromotions.length > 0) {
681
+ console.log(`[DynamicFilterService] FALLBACK: Found ${itemsTargetingPromotions.length} promotions targeting items with no rules. ` +
682
+ `Including all products as fallback (promotion rules may be stored in a way we can't query yet).`);
683
+ // Include all current products and set their discount percentages
684
+ for (const product of products) {
685
+ if (product && typeof product === "object" && "id" in product) {
686
+ const productId = product.id;
687
+ productIdsWithPromotions.add(productId);
688
+ // Calculate and set discount percentage for each promotion
689
+ for (const promo of itemsTargetingPromotions) {
690
+ const appMethod = promo.application_method;
691
+ if (appMethod &&
692
+ typeof appMethod === "object" &&
693
+ "type" in appMethod &&
694
+ appMethod.type === "percentage" &&
695
+ "value" in appMethod) {
696
+ const discount = typeof appMethod.value === "number"
697
+ ? appMethod.value
698
+ : Number(appMethod.value);
699
+ if (!Number.isNaN(discount)) {
700
+ const currentMax = promotionDiscountMap.get(productId) || 0;
701
+ promotionDiscountMap.set(productId, Math.max(currentMax, discount));
702
+ }
703
+ }
704
+ }
705
+ }
706
+ }
707
+ console.log(`[DynamicFilterService] FALLBACK applied: ${productIdsWithPromotions.size} products now included`);
708
+ }
709
+ }
710
+ if (productIdsWithPromotions.size > 0) {
711
+ const sampleProductIds = Array.from(productIdsWithPromotions).slice(0, 5);
712
+ console.log(`[DynamicFilterService] Sample product IDs with promotions:`, sampleProductIds);
713
+ }
714
+ // Filter products to only include those with matching promotions
715
+ products = products.filter((product) => {
716
+ const productId = product?.id;
717
+ if (!productId || typeof productId !== "string") {
718
+ return false;
719
+ }
720
+ // Check if product has a matching promotion
721
+ if (!productIdsWithPromotions.has(productId)) {
722
+ return false;
723
+ }
724
+ // If min_discount_percentage is specified, check discount
725
+ if (promotionFilter.min_discount_percentage !== undefined) {
726
+ const productDiscount = promotionDiscountMap.get(productId);
727
+ // For amount_off_product (fixed amount discount), calculate percentage from product price
728
+ if (productDiscount === undefined && product.variants) {
729
+ // Try to find promotion with fixed amount discount on items
730
+ // and calculate percentage from product price
731
+ const productPrice = product.variants?.[0]?.prices?.[0]?.amount;
732
+ if (productPrice && typeof productPrice === "number") {
733
+ // Find matching promotion with amount discount
734
+ for (const promotion of promotions) {
735
+ const applicationMethod = promotion.application_method;
736
+ const isAmountDiscount = (applicationMethod &&
737
+ typeof applicationMethod === "object" &&
738
+ "type" in applicationMethod &&
739
+ applicationMethod.type === "fixed" &&
740
+ "target_type" in applicationMethod &&
741
+ applicationMethod.target_type === "items") ||
742
+ promotion.type === "amount_off_product";
743
+ if (isAmountDiscount) {
744
+ if (applicationMethod &&
745
+ typeof applicationMethod === "object" &&
746
+ "value" in applicationMethod) {
747
+ const discountAmount = typeof applicationMethod.value === "number"
748
+ ? applicationMethod.value
749
+ : Number(applicationMethod.value);
750
+ if (!Number.isNaN(discountAmount) && productPrice > 0) {
751
+ const calculatedPercentage = (discountAmount / productPrice) * 100;
752
+ const currentMax = promotionDiscountMap.get(productId) || 0;
753
+ promotionDiscountMap.set(productId, Math.max(currentMax, calculatedPercentage));
754
+ }
755
+ }
756
+ }
757
+ }
758
+ }
759
+ }
760
+ const finalDiscount = promotionDiscountMap.get(productId) || 0;
761
+ // Check if discount meets minimum threshold
762
+ if (finalDiscount < promotionFilter.min_discount_percentage) {
763
+ return false;
764
+ }
765
+ }
766
+ return true;
767
+ });
768
+ // Update metadata count after filtering
769
+ if (metadata) {
770
+ metadata.count = products.length;
771
+ }
772
+ }
773
+ catch (error) {
774
+ // Log error but don't fail - return products without promotion filtering
775
+ console.warn("[DynamicFilterService] Error filtering by promotions:", error instanceof Error ? error.message : String(error));
776
+ }
777
+ }
778
+ // Get count from metadata if available, otherwise query separately
779
+ let count = metadata?.count;
780
+ if (count === undefined) {
781
+ // Query for count without pagination
782
+ const countQueryOptions = {
783
+ entity: "product",
784
+ fields: ["id"],
785
+ ...(Object.keys(queryFilters).length > 0 && { filters: queryFilters }),
786
+ };
787
+ const countResult = await this.query.graph(countQueryOptions);
788
+ // Extract count from result - following same pattern as main query
789
+ const { data: countData = [], metadata: countMetadata } = countResult;
790
+ if (countMetadata?.count !== undefined) {
791
+ count = countMetadata.count;
792
+ }
793
+ else if (Array.isArray(countData)) {
794
+ count = countData.length;
795
+ }
796
+ else {
797
+ // Fallback: use products array length if count query also fails
798
+ console.warn("[DynamicFilterService] Could not determine count from query result, using products array length");
799
+ count = products.length;
800
+ }
801
+ }
802
+ return {
803
+ products,
804
+ count: typeof count === "number" ? count : products.length,
805
+ metadata: {
806
+ count: typeof count === "number" ? count : products.length,
807
+ skip: pagination?.offset,
808
+ take: pagination?.limit,
809
+ },
810
+ };
811
+ }
812
+ }
813
+ exports.DynamicFilterService = DynamicFilterService;
814
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHluYW1pYy1maWx0ZXItc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9zZXJ2aWNlcy9keW5hbWljLWZpbHRlci1zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUNBLHFEQUFtRjtBQUVuRix5RUFBbUU7QUE2RG5FOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBc0JHO0FBQ0gsTUFBYSxvQkFBb0I7SUFJL0IsWUFBWSxTQUEwQjtRQUNwQywrQ0FBK0M7UUFDL0MsSUFBSSxDQUFDO1lBQ0gsSUFBSSxDQUFDLFFBQVEsR0FBRyxTQUFTLENBQUMsT0FBTyxDQUMvQix3QkFBd0IsQ0FDekIsQ0FBQTtRQUNILENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCwwRUFBMEU7WUFDMUUseUVBQXlFO1lBQ3pFLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxpREFBc0IsRUFBRSxDQUFBO1lBRTVDLG1DQUFtQztZQUNuQyxJQUFJLENBQUM7Z0JBQ0gsTUFBTSxFQUFFLHdCQUF3QixFQUFFLEdBQUcsT0FBTyxDQUFDLCtCQUErQixDQUFDLENBQUE7Z0JBQzdFLHdCQUF3QixDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQTtZQUN6QyxDQUFDO1lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztnQkFDZiwwQ0FBMEM7Z0JBQzFDLE9BQU8sQ0FBQyxJQUFJLENBQ1Ysb0VBQW9FLEVBQ3BFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FDdkQsQ0FBQTtZQUNILENBQUM7UUFDSCxDQUFDO1FBQ0QsSUFBSSxDQUFDLEtBQUssR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFRLGlDQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFBO0lBQ3hFLENBQUM7SUFFRDs7O09BR0c7SUFDSCxXQUFXO1FBQ1QsT0FBTyxJQUFJLENBQUMsUUFB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