@lodashventure/medusa-sales-analytics 1.0.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/.medusa/server/api/admin/analytics/sales/route.d.ts +2 -0
- package/.medusa/server/api/admin/analytics/sales/route.js +177 -0
- package/.medusa/server/api/admin/products/[id]/sales-history/route.d.ts +2 -0
- package/.medusa/server/api/admin/products/[id]/sales-history/route.js +204 -0
- package/.medusa/server/api/admin/products/sales-statistics/route.d.ts +2 -0
- package/.medusa/server/api/admin/products/sales-statistics/route.js +97 -0
- package/.medusa/server/api/admin/sales/summary/route.d.ts +2 -0
- package/.medusa/server/api/admin/sales/summary/route.js +176 -0
- package/.medusa/server/api/middlewares/add-product-filters.d.ts +7 -0
- package/.medusa/server/api/middlewares/add-product-filters.js +173 -0
- package/.medusa/server/api/middlewares/add-sales-statistics.d.ts +2 -0
- package/.medusa/server/api/middlewares/add-sales-statistics.js +103 -0
- package/.medusa/server/api/middlewares/add-stock-availability.d.ts +7 -0
- package/.medusa/server/api/middlewares/add-stock-availability.js +91 -0
- package/.medusa/server/api/middlewares.d.ts +2 -0
- package/.medusa/server/api/middlewares.js +36 -0
- package/.medusa/server/index.d.ts +2 -0
- package/.medusa/server/index.js +8 -0
- package/README.md +60 -0
- package/package.json +48 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GET = GET;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
async function GET(req, res) {
|
|
6
|
+
const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
7
|
+
// Get all orders
|
|
8
|
+
const { data: orders } = await query.graph({
|
|
9
|
+
entity: "order",
|
|
10
|
+
fields: [
|
|
11
|
+
"id",
|
|
12
|
+
"status",
|
|
13
|
+
"total",
|
|
14
|
+
"subtotal",
|
|
15
|
+
"tax_total",
|
|
16
|
+
"discount_total",
|
|
17
|
+
"shipping_total",
|
|
18
|
+
"created_at",
|
|
19
|
+
"customer_id",
|
|
20
|
+
"items.*"
|
|
21
|
+
]
|
|
22
|
+
});
|
|
23
|
+
// Get all products
|
|
24
|
+
const { data: products } = await query.graph({
|
|
25
|
+
entity: "product",
|
|
26
|
+
fields: [
|
|
27
|
+
"id",
|
|
28
|
+
"title",
|
|
29
|
+
"status",
|
|
30
|
+
"collection.*",
|
|
31
|
+
"categories.*",
|
|
32
|
+
"variants.*",
|
|
33
|
+
"variants.inventory_items.*"
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
// Calculate date ranges
|
|
37
|
+
const now = new Date();
|
|
38
|
+
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
39
|
+
const yesterdayStart = new Date(todayStart);
|
|
40
|
+
yesterdayStart.setDate(yesterdayStart.getDate() - 1);
|
|
41
|
+
const weekStart = new Date(todayStart);
|
|
42
|
+
weekStart.setDate(weekStart.getDate() - 7);
|
|
43
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
44
|
+
const lastMonthStart = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
45
|
+
const lastMonthEnd = new Date(now.getFullYear(), now.getMonth(), 0);
|
|
46
|
+
// Filter completed orders
|
|
47
|
+
const completedOrders = orders.filter(order => ["completed", "partially_returned", "partially_shipped", "shipped", "fulfilled", "partially_fulfilled"].includes(order.status));
|
|
48
|
+
// Today's metrics
|
|
49
|
+
const todayOrders = completedOrders.filter(o => new Date(o.created_at) >= todayStart);
|
|
50
|
+
const todayRevenue = todayOrders.reduce((sum, o) => sum + (o.total || 0), 0);
|
|
51
|
+
// Yesterday's metrics
|
|
52
|
+
const yesterdayOrders = completedOrders.filter(o => {
|
|
53
|
+
const orderDate = new Date(o.created_at);
|
|
54
|
+
return orderDate >= yesterdayStart && orderDate < todayStart;
|
|
55
|
+
});
|
|
56
|
+
const yesterdayRevenue = yesterdayOrders.reduce((sum, o) => sum + (o.total || 0), 0);
|
|
57
|
+
// This week's metrics
|
|
58
|
+
const weekOrders = completedOrders.filter(o => new Date(o.created_at) >= weekStart);
|
|
59
|
+
const weekRevenue = weekOrders.reduce((sum, o) => sum + (o.total || 0), 0);
|
|
60
|
+
// This month's metrics
|
|
61
|
+
const monthOrders = completedOrders.filter(o => new Date(o.created_at) >= monthStart);
|
|
62
|
+
const monthRevenue = monthOrders.reduce((sum, o) => sum + (o.total || 0), 0);
|
|
63
|
+
// Last month's metrics
|
|
64
|
+
const lastMonthOrders = completedOrders.filter(o => {
|
|
65
|
+
const orderDate = new Date(o.created_at);
|
|
66
|
+
return orderDate >= lastMonthStart && orderDate <= lastMonthEnd;
|
|
67
|
+
});
|
|
68
|
+
const lastMonthRevenue = lastMonthOrders.reduce((sum, o) => sum + (o.total || 0), 0);
|
|
69
|
+
// All time metrics
|
|
70
|
+
const totalRevenue = completedOrders.reduce((sum, o) => sum + (o.total || 0), 0);
|
|
71
|
+
const totalOrders = completedOrders.length;
|
|
72
|
+
// Pending orders
|
|
73
|
+
const pendingOrders = orders.filter(o => ["pending", "requires_action", "processing"].includes(o.status));
|
|
74
|
+
// Low stock products
|
|
75
|
+
const lowStockProducts = products.filter(product => product.status === "published" &&
|
|
76
|
+
product.variants?.some((v) => {
|
|
77
|
+
const inventory = v.inventory_items?.[0]?.inventory ||
|
|
78
|
+
v.inventory_items?.[0];
|
|
79
|
+
const available = inventory?.available_quantity || 0;
|
|
80
|
+
return v.manage_inventory && available < 10;
|
|
81
|
+
}));
|
|
82
|
+
// Out of stock products
|
|
83
|
+
const outOfStockProducts = products.filter(product => product.status === "published" &&
|
|
84
|
+
product.variants?.every((v) => {
|
|
85
|
+
const inventory = v.inventory_items?.[0]?.inventory ||
|
|
86
|
+
v.inventory_items?.[0];
|
|
87
|
+
const available = inventory?.available_quantity || 0;
|
|
88
|
+
return v.manage_inventory && available === 0;
|
|
89
|
+
}));
|
|
90
|
+
// Best performing categories
|
|
91
|
+
const categoryPerformance = {};
|
|
92
|
+
completedOrders.forEach(order => {
|
|
93
|
+
order.items?.forEach((item) => {
|
|
94
|
+
const product = products.find(p => p.variants?.some((v) => v.id === item.variant_id));
|
|
95
|
+
product?.categories?.forEach((category) => {
|
|
96
|
+
if (!categoryPerformance[category.id]) {
|
|
97
|
+
categoryPerformance[category.id] = {
|
|
98
|
+
category_id: category.id,
|
|
99
|
+
category_name: category.name,
|
|
100
|
+
revenue: 0,
|
|
101
|
+
quantity: 0
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
categoryPerformance[category.id].revenue += item.subtotal || 0;
|
|
105
|
+
categoryPerformance[category.id].quantity += item.quantity || 0;
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
const topCategories = Object.values(categoryPerformance)
|
|
110
|
+
.sort((a, b) => b.revenue - a.revenue)
|
|
111
|
+
.slice(0, 5);
|
|
112
|
+
// Customer metrics
|
|
113
|
+
const uniqueCustomers = new Set(completedOrders.map(o => o.customer_id).filter(Boolean));
|
|
114
|
+
const newCustomersThisMonth = new Set(monthOrders.filter(o => {
|
|
115
|
+
const customerOrders = completedOrders.filter(co => co.customer_id === o.customer_id);
|
|
116
|
+
return customerOrders.length === 1;
|
|
117
|
+
}).map(o => o.customer_id));
|
|
118
|
+
res.json({
|
|
119
|
+
summary: {
|
|
120
|
+
today: {
|
|
121
|
+
revenue: todayRevenue,
|
|
122
|
+
orders: todayOrders.length,
|
|
123
|
+
average_order_value: todayOrders.length > 0 ? todayRevenue / todayOrders.length : 0,
|
|
124
|
+
vs_yesterday: {
|
|
125
|
+
revenue_change: todayRevenue - yesterdayRevenue,
|
|
126
|
+
revenue_change_percentage: yesterdayRevenue > 0
|
|
127
|
+
? ((todayRevenue - yesterdayRevenue) / yesterdayRevenue * 100).toFixed(2)
|
|
128
|
+
: 0,
|
|
129
|
+
orders_change: todayOrders.length - yesterdayOrders.length
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
week: {
|
|
133
|
+
revenue: weekRevenue,
|
|
134
|
+
orders: weekOrders.length,
|
|
135
|
+
average_order_value: weekOrders.length > 0 ? weekRevenue / weekOrders.length : 0,
|
|
136
|
+
daily_average: weekRevenue / 7
|
|
137
|
+
},
|
|
138
|
+
month: {
|
|
139
|
+
revenue: monthRevenue,
|
|
140
|
+
orders: monthOrders.length,
|
|
141
|
+
average_order_value: monthOrders.length > 0 ? monthRevenue / monthOrders.length : 0,
|
|
142
|
+
vs_last_month: {
|
|
143
|
+
revenue_change: monthRevenue - lastMonthRevenue,
|
|
144
|
+
revenue_change_percentage: lastMonthRevenue > 0
|
|
145
|
+
? ((monthRevenue - lastMonthRevenue) / lastMonthRevenue * 100).toFixed(2)
|
|
146
|
+
: 0,
|
|
147
|
+
orders_change: monthOrders.length - lastMonthOrders.length
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
all_time: {
|
|
151
|
+
revenue: totalRevenue,
|
|
152
|
+
orders: totalOrders,
|
|
153
|
+
average_order_value: totalOrders > 0 ? totalRevenue / totalOrders : 0,
|
|
154
|
+
total_customers: uniqueCustomers.size
|
|
155
|
+
},
|
|
156
|
+
pending: {
|
|
157
|
+
orders_count: pendingOrders.length,
|
|
158
|
+
orders_value: pendingOrders.reduce((sum, o) => sum + (o.total || 0), 0)
|
|
159
|
+
},
|
|
160
|
+
inventory: {
|
|
161
|
+
low_stock_products: lowStockProducts.length,
|
|
162
|
+
out_of_stock_products: outOfStockProducts.length,
|
|
163
|
+
total_products: products.filter(p => p.status === "published").length
|
|
164
|
+
},
|
|
165
|
+
customers: {
|
|
166
|
+
total: uniqueCustomers.size,
|
|
167
|
+
new_this_month: newCustomersThisMonth.size,
|
|
168
|
+
average_lifetime_value: uniqueCustomers.size > 0
|
|
169
|
+
? totalRevenue / uniqueCustomers.size
|
|
170
|
+
: 0
|
|
171
|
+
},
|
|
172
|
+
top_categories: topCategories
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3NhbGVzL3N1bW1hcnkvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFHQSxrQkFvTkM7QUF0TkQscURBQXNFO0FBRS9ELEtBQUssVUFBVSxHQUFHLENBQ3ZCLEdBQWtCLEVBQ2xCLEdBQW1CO0lBRW5CLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLGlDQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFDO0lBRWpFLGlCQUFpQjtJQUNqQixNQUFNLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxHQUFHLE1BQU0sS0FBSyxDQUFDLEtBQUssQ0FBQztRQUN6QyxNQUFNLEVBQUUsT0FBTztRQUNmLE1BQU0sRUFBRTtZQUNOLElBQUk7WUFDSixRQUFRO1lBQ1IsT0FBTztZQUNQLFVBQVU7WUFDVixXQUFXO1lBQ1gsZ0JBQWdCO1lBQ2hCLGdCQUFnQjtZQUNoQixZQUFZO1lBQ1osYUFBYTtZQUNiLFNBQVM7U0FDVjtLQUNGLENBQW9CLENBQUM7SUFFdEIsbUJBQW1CO0lBQ25CLE1BQU0sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLEdBQUcsTUFBTSxLQUFLLENBQUMsS0FBSyxDQUFDO1FBQzNDLE1BQU0sRUFBRSxTQUFTO1FBQ2pCLE1BQU0sRUFBRTtZQUNOLElBQUk7WUFDSixPQUFPO1lBQ1AsUUFBUTtZQUNSLGNBQWM7WUFDZCxjQUFjO1lBQ2QsWUFBWTtZQUNaLDRCQUE0QjtTQUM3QjtLQUNGLENBQUMsQ0FBQztJQUVILHdCQUF3QjtJQUN4QixNQUFNLEdBQUcsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDO0lBQ3ZCLE1BQU0sVUFBVSxHQUFHLElBQUksSUFBSSxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsRUFBRSxHQUFHLENBQUMsUUFBUSxFQUFFLEVBQUUsR0FBRyxDQUFDLE9BQU8sRUFBRSxD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
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MedusaRequest, MedusaResponse, MedusaNextFunction } from "@medusajs/framework/http";
|
|
2
|
+
export declare function captureFilterParams(req: MedusaRequest, res: MedusaResponse, next: MedusaNextFunction): Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* This is a post-processing middleware that filters after all other middlewares
|
|
5
|
+
* It should be registered LAST in the middleware chain
|
|
6
|
+
*/
|
|
7
|
+
export declare function applyProductFilters(req: MedusaRequest, res: MedusaResponse, next: MedusaNextFunction): Promise<void>;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.captureFilterParams = captureFilterParams;
|
|
4
|
+
exports.applyProductFilters = applyProductFilters;
|
|
5
|
+
/**
|
|
6
|
+
* Middleware to add advanced filtering for products
|
|
7
|
+
* Supports: price_min, price_max, rating_min, rating_max, ready_to_ship
|
|
8
|
+
*
|
|
9
|
+
* Query parameters:
|
|
10
|
+
* - price_min: Minimum price in cents (e.g., 10000 = ฿100)
|
|
11
|
+
* - price_max: Maximum price in cents
|
|
12
|
+
* - rating_min: Minimum average rating (1-5)
|
|
13
|
+
* - rating_max: Maximum average rating (1-5)
|
|
14
|
+
* - ready_to_ship: Filter products with stock (true/false or 1/0)
|
|
15
|
+
*
|
|
16
|
+
* Note: This middleware runs AFTER stock availability and review aggregates middlewares
|
|
17
|
+
* to ensure stock and rating data are available for filtering
|
|
18
|
+
*/
|
|
19
|
+
// Store filter params in request object to avoid Medusa validation errors
|
|
20
|
+
const FILTER_PARAMS_KEY = Symbol("productFilters");
|
|
21
|
+
async function captureFilterParams(req, res, next) {
|
|
22
|
+
// Extract our custom filter parameters
|
|
23
|
+
const query = req.query;
|
|
24
|
+
const filterParams = {
|
|
25
|
+
priceMin: query.price_min ? parseFloat(query.price_min) : null,
|
|
26
|
+
priceMax: query.price_max ? parseFloat(query.price_max) : null,
|
|
27
|
+
ratingMin: query.rating_min ? parseFloat(query.rating_min) : null,
|
|
28
|
+
ratingMax: query.rating_max ? parseFloat(query.rating_max) : null,
|
|
29
|
+
readyToShip: parseReadyToShip(query.ready_to_ship),
|
|
30
|
+
};
|
|
31
|
+
// Store in request object
|
|
32
|
+
req[FILTER_PARAMS_KEY] = filterParams;
|
|
33
|
+
// Remove from query to prevent Medusa validation errors
|
|
34
|
+
delete req.query.price_min;
|
|
35
|
+
delete req.query.price_max;
|
|
36
|
+
delete req.query.rating_min;
|
|
37
|
+
delete req.query.rating_max;
|
|
38
|
+
delete req.query.ready_to_ship;
|
|
39
|
+
next();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* This is a post-processing middleware that filters after all other middlewares
|
|
43
|
+
* It should be registered LAST in the middleware chain
|
|
44
|
+
*/
|
|
45
|
+
async function applyProductFilters(req, res, next) {
|
|
46
|
+
try {
|
|
47
|
+
const originalJson = res.json.bind(res);
|
|
48
|
+
res.json = async function (data) {
|
|
49
|
+
try {
|
|
50
|
+
// Only process product list responses
|
|
51
|
+
if (data?.products && Array.isArray(data.products)) {
|
|
52
|
+
const filterParams = req[FILTER_PARAMS_KEY];
|
|
53
|
+
if (filterParams) {
|
|
54
|
+
const { products, count } = filterProducts(filterParams, data.products, data.count);
|
|
55
|
+
data.products = products;
|
|
56
|
+
data.count = count;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error("Error applying product filters:", error);
|
|
62
|
+
// Continue with original data on error
|
|
63
|
+
}
|
|
64
|
+
return originalJson(data);
|
|
65
|
+
};
|
|
66
|
+
next();
|
|
67
|
+
}
|
|
68
|
+
catch (middlewareError) {
|
|
69
|
+
console.error("Error in applyProductFilters middleware:", middlewareError);
|
|
70
|
+
next(middlewareError);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Filter products based on stored filter parameters
|
|
75
|
+
*/
|
|
76
|
+
function filterProducts(filterParams, products, originalCount) {
|
|
77
|
+
const { priceMin, priceMax, ratingMin, ratingMax, readyToShip } = filterParams;
|
|
78
|
+
// If no filters, return original
|
|
79
|
+
if (!priceMin &&
|
|
80
|
+
!priceMax &&
|
|
81
|
+
!ratingMin &&
|
|
82
|
+
!ratingMax &&
|
|
83
|
+
readyToShip === null) {
|
|
84
|
+
return { products, count: originalCount };
|
|
85
|
+
}
|
|
86
|
+
// Filter products
|
|
87
|
+
const filtered = products.filter((product) => {
|
|
88
|
+
// Price filtering
|
|
89
|
+
if (priceMin !== null || priceMax !== null) {
|
|
90
|
+
const productPrice = getProductPrice(product);
|
|
91
|
+
if (productPrice === null) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (priceMin !== null && productPrice < priceMin) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (priceMax !== null && productPrice > priceMax) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Rating filtering
|
|
102
|
+
if (ratingMin !== null || ratingMax !== null) {
|
|
103
|
+
const productRating = product.review_aggregate?.average_rating ?? 0;
|
|
104
|
+
if (ratingMin !== null && productRating < ratingMin) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
if (ratingMax !== null && productRating > ratingMax) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
if ((ratingMin !== null || ratingMax !== null) && productRating === 0) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Ready to ship filtering
|
|
115
|
+
if (readyToShip !== null) {
|
|
116
|
+
const stockStatus = product.stock_availability?.status;
|
|
117
|
+
const isInStock = stockStatus === "InStock";
|
|
118
|
+
if (readyToShip === true && !isInStock) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (readyToShip === false && isInStock) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
products: filtered,
|
|
129
|
+
count: filtered.length,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Parse ready_to_ship parameter
|
|
134
|
+
*/
|
|
135
|
+
function parseReadyToShip(value) {
|
|
136
|
+
if (!value)
|
|
137
|
+
return null;
|
|
138
|
+
const normalized = value.toLowerCase().trim();
|
|
139
|
+
if (normalized === "true" || normalized === "1") {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
if (normalized === "false" || normalized === "0") {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get the minimum price from product variants
|
|
149
|
+
*/
|
|
150
|
+
function getProductPrice(product) {
|
|
151
|
+
if (!product.variants || product.variants.length === 0) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
const prices = product.variants
|
|
155
|
+
.map((variant) => {
|
|
156
|
+
if (variant.calculated_price?.calculated_amount) {
|
|
157
|
+
return parseFloat(variant.calculated_price.calculated_amount);
|
|
158
|
+
}
|
|
159
|
+
if (variant.calculated_price?.original_amount) {
|
|
160
|
+
return parseFloat(variant.calculated_price.original_amount);
|
|
161
|
+
}
|
|
162
|
+
if (variant.price) {
|
|
163
|
+
return parseFloat(variant.price);
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
})
|
|
167
|
+
.filter((price) => price !== null && !isNaN(price));
|
|
168
|
+
if (prices.length === 0) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return Math.min(...prices);
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRkLXByb2R1Y3QtZmlsdGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9hcGkvbWlkZGxld2FyZXMvYWRkLXByb2R1Y3QtZmlsdGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQXdCQSxrREEwQkM7QUFNRCxrREFvQ0M7QUF0RkQ7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUVILDBFQUEwRTtBQUMxRSxNQUFNLGlCQUFpQixHQUFHLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0FBRTVDLEtBQUssVUFBVSxtQkFBbUIsQ0FDdkMsR0FBa0IsRUFDbEIsR0FBbUIsRUFDbkIsSUFBd0I7SUFFeEIsdUNBQXVDO0lBQ3ZDLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUM7SUFDeEIsTUFBTSxZQUFZLEdBQUc7UUFDbkIsUUFBUSxFQUFFLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsU0FBbUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJO1FBQ3hFLFFBQVEsRUFBRSxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLFNBQW1CLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSTtRQUN4RSxTQUFTLEVBQUUsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxVQUFvQixDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUk7UUFDM0UsU0FBUyxFQUFFLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsVUFBb0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJO1FBQzNFLFdBQVcsRUFBRSxnQkFBZ0IsQ0FBQyxLQUFLLENBQUMsYUFBdUIsQ0FBQztLQUM3RCxDQUFDO0lBRUYsMEJBQTBCO0lBQ3pCLEdBQVcsQ0FBQyxpQkFBaUIsQ0FBQyxHQUFHLFlBQVksQ0FB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
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addSalesStatistics = addSalesStatistics;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
async function addSalesStatistics(req, res, next) {
|
|
6
|
+
const originalJson = res.json.bind(res);
|
|
7
|
+
res.json = async function (data) {
|
|
8
|
+
// Check if this is a products listing response
|
|
9
|
+
if (data?.products && Array.isArray(data.products)) {
|
|
10
|
+
try {
|
|
11
|
+
const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
12
|
+
// Get all order items
|
|
13
|
+
const { data: orderItems } = await query.graph({
|
|
14
|
+
entity: "order_line_item",
|
|
15
|
+
fields: [
|
|
16
|
+
"id",
|
|
17
|
+
"variant_id",
|
|
18
|
+
"quantity",
|
|
19
|
+
"unit_price",
|
|
20
|
+
"subtotal",
|
|
21
|
+
"order_id"
|
|
22
|
+
]
|
|
23
|
+
});
|
|
24
|
+
// Get orders separately
|
|
25
|
+
const orderIds = [...new Set(orderItems.map(item => item.order_id).filter(Boolean))];
|
|
26
|
+
let orders = [];
|
|
27
|
+
if (orderIds.length > 0) {
|
|
28
|
+
const orderResult = await query.graph({
|
|
29
|
+
entity: "order",
|
|
30
|
+
fields: ["id", "status", "created_at"],
|
|
31
|
+
filters: {
|
|
32
|
+
id: orderIds
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
orders = orderResult.data;
|
|
36
|
+
}
|
|
37
|
+
// Create order map
|
|
38
|
+
const orderMap = new Map(orders.map(o => [o.id, o]));
|
|
39
|
+
// Filter for completed orders
|
|
40
|
+
const filteredOrderItems = orderItems.filter(item => {
|
|
41
|
+
const order = orderMap.get(item.order_id);
|
|
42
|
+
return order && [
|
|
43
|
+
"completed",
|
|
44
|
+
"partially_returned",
|
|
45
|
+
"partially_shipped",
|
|
46
|
+
"shipped",
|
|
47
|
+
"fulfilled",
|
|
48
|
+
"partially_fulfilled"
|
|
49
|
+
].includes(order.status);
|
|
50
|
+
});
|
|
51
|
+
// Add sales statistics to each product
|
|
52
|
+
data.products = data.products.map((product) => {
|
|
53
|
+
const variantIds = product.variants?.map((v) => v.id) || [];
|
|
54
|
+
const productSales = filteredOrderItems.filter(item => item.variant_id && variantIds.includes(item.variant_id));
|
|
55
|
+
const totalQuantitySold = productSales.reduce((sum, item) => sum + (item.quantity || 0), 0);
|
|
56
|
+
const totalRevenue = productSales.reduce((sum, item) => sum + (item.subtotal || 0), 0);
|
|
57
|
+
// Add sales data to each variant if they exist
|
|
58
|
+
if (product.variants && Array.isArray(product.variants)) {
|
|
59
|
+
product.variants = product.variants.map((variant) => {
|
|
60
|
+
const variantSales = productSales.filter(item => item.variant_id === variant.id);
|
|
61
|
+
const variantQuantitySold = variantSales.reduce((sum, item) => sum + (item.quantity || 0), 0);
|
|
62
|
+
const variantRevenue = variantSales.reduce((sum, item) => sum + (item.subtotal || 0), 0);
|
|
63
|
+
// Add sales stats to variant
|
|
64
|
+
return {
|
|
65
|
+
...variant,
|
|
66
|
+
sales_statistics: {
|
|
67
|
+
quantity_sold: variantQuantitySold,
|
|
68
|
+
revenue: variantRevenue,
|
|
69
|
+
number_of_orders: new Set(variantSales.map(item => item.order_id).filter(Boolean)).size
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Add product-level sales statistics
|
|
75
|
+
return {
|
|
76
|
+
...product,
|
|
77
|
+
sales_statistics: {
|
|
78
|
+
total_quantity_sold: totalQuantitySold,
|
|
79
|
+
total_revenue: totalRevenue,
|
|
80
|
+
number_of_orders: new Set(productSales.map(item => item.order_id).filter(Boolean)).size,
|
|
81
|
+
average_order_value: totalQuantitySold > 0 ? totalRevenue / totalQuantitySold : 0,
|
|
82
|
+
is_bestseller: totalQuantitySold > 0,
|
|
83
|
+
stock_status: product.variants?.some((v) => {
|
|
84
|
+
// Check inventory from different possible structures
|
|
85
|
+
const inventory = v.inventory_items?.[0]?.inventory ||
|
|
86
|
+
v.inventory_items?.[0] ||
|
|
87
|
+
v.inventory?.items?.[0];
|
|
88
|
+
return inventory?.available_quantity > 0 || inventory?.stocked_quantity > 0;
|
|
89
|
+
}) ? 'in_stock' : 'out_of_stock'
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
// If there's an error fetching sales data, return products without stats
|
|
96
|
+
console.error("Error adding sales statistics:", error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return originalJson(data);
|
|
100
|
+
};
|
|
101
|
+
next();
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRkLXNhbGVzLXN0YXRpc3RpY3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBpL21pZGRsZXdhcmVzL2FkZC1zYWxlcy1zdGF0aXN0aWNzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBR0EsZ0RBaUlDO0FBbklELHFEQUFzRTtBQUUvRCxLQUFLLFVBQVUsa0JBQWtCLENBQ3RDLEdBQWtCLEVBQ2xCLEdBQW1CLEVBQ25CLElBQXdCO0lBRXhCLE1BQU0sWUFBWSxHQUFHLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBRXZDLEdBQVcsQ0FBQyxJQUFJLEdBQUcsS0FBSyxXQUFVLElBQVM7UUFDMUMsK0NBQStDO1FBQy9DLElBQUksSUFBSSxFQUFFLFFBQVEsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1lBQ25ELElBQUksQ0FBQztnQkFDSCxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxpQ0FBeUIsQ0FBQyxLQUFLLENBQUMsQ0FBQztnQkFFakUsc0JBQXNCO2dCQUN0QixNQUFNLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxHQUFHLE1BQU0sS0FBSyxDQUFDLEtBQUssQ0FBQztvQkFDN0MsTUFBTSxFQUFFLGlCQUFpQjtvQkFDekIsTUFBTSxFQUFFO3dCQUNOLElBQUk7d0JBQ0osWUFBWTt3QkFDWixVQUFVO3dCQUNWLFlBQVk7d0JBQ1osVUFBVTt3QkFDVixVQUFVO3FCQUNYO2lCQUNGLENBQW9CLENBQUM7Z0JBRXRCLHdCQUF3QjtnQkFDeEIsTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFHLElBQUksR0FBRyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENB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
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MedusaRequest, MedusaResponse, MedusaNextFunction } from "@medusajs/framework/http";
|
|
2
|
+
/**
|
|
3
|
+
* Middleware to add stock availability status to product responses
|
|
4
|
+
* This enhances product payloads with accurate inventory information
|
|
5
|
+
* for Schema.org structured data and frontend display
|
|
6
|
+
*/
|
|
7
|
+
export declare function addStockAvailability(req: MedusaRequest, res: MedusaResponse, next: MedusaNextFunction): Promise<void>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addStockAvailability = addStockAvailability;
|
|
4
|
+
/**
|
|
5
|
+
* Middleware to add stock availability status to product responses
|
|
6
|
+
* This enhances product payloads with accurate inventory information
|
|
7
|
+
* for Schema.org structured data and frontend display
|
|
8
|
+
*/
|
|
9
|
+
async function addStockAvailability(req, res, next) {
|
|
10
|
+
try {
|
|
11
|
+
const originalJson = res.json.bind(res);
|
|
12
|
+
res.json = async function (data) {
|
|
13
|
+
try {
|
|
14
|
+
// Handle single product response
|
|
15
|
+
if (data?.product && !Array.isArray(data.product)) {
|
|
16
|
+
data.product = enrichProductWithStock(data.product);
|
|
17
|
+
}
|
|
18
|
+
// Handle products array response
|
|
19
|
+
if (data?.products && Array.isArray(data.products)) {
|
|
20
|
+
data.products = data.products.map((product) => enrichProductWithStock(product));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error("Error adding stock availability:", error?.message || error);
|
|
25
|
+
// Continue with original data on error
|
|
26
|
+
}
|
|
27
|
+
return originalJson(data);
|
|
28
|
+
};
|
|
29
|
+
next();
|
|
30
|
+
}
|
|
31
|
+
catch (middlewareError) {
|
|
32
|
+
console.error("Error in addStockAvailability middleware:", middlewareError);
|
|
33
|
+
next(middlewareError);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Enrich a single product with stock availability information
|
|
38
|
+
* Simplified version - defaults to InStock for now
|
|
39
|
+
*/
|
|
40
|
+
function enrichProductWithStock(product) {
|
|
41
|
+
if (!product) {
|
|
42
|
+
return product;
|
|
43
|
+
}
|
|
44
|
+
// If variants field is not requested, skip enrichment
|
|
45
|
+
if (!product.variants) {
|
|
46
|
+
return product;
|
|
47
|
+
}
|
|
48
|
+
if (product.variants.length === 0) {
|
|
49
|
+
return {
|
|
50
|
+
...product,
|
|
51
|
+
stock_availability: {
|
|
52
|
+
status: "OutOfStock",
|
|
53
|
+
has_stock: false,
|
|
54
|
+
total_available_quantity: 0,
|
|
55
|
+
total_stocked_quantity: 0,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
// Check if product status indicates it's not available
|
|
61
|
+
let status = "InStock";
|
|
62
|
+
if (product.status === "draft") {
|
|
63
|
+
status = "Discontinued";
|
|
64
|
+
}
|
|
65
|
+
// Simple heuristic: if product has variants, assume in stock
|
|
66
|
+
// This can be enhanced later with actual inventory queries
|
|
67
|
+
const hasStock = product.variants.length > 0 && product.status !== "draft";
|
|
68
|
+
return {
|
|
69
|
+
...product,
|
|
70
|
+
stock_availability: {
|
|
71
|
+
status,
|
|
72
|
+
has_stock: hasStock,
|
|
73
|
+
total_available_quantity: hasStock ? 999 : 0,
|
|
74
|
+
total_stocked_quantity: hasStock ? 999 : 0,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error(`Error enriching product ${product.id}:`, error?.message || error);
|
|
80
|
+
return {
|
|
81
|
+
...product,
|
|
82
|
+
stock_availability: {
|
|
83
|
+
status: "InStock",
|
|
84
|
+
has_stock: true,
|
|
85
|
+
total_available_quantity: 999,
|
|
86
|
+
total_stocked_quantity: 999,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRkLXN0b2NrLWF2YWlsYWJpbGl0eS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9hcGkvbWlkZGxld2FyZXMvYWRkLXN0b2NrLWF2YWlsYWJpbGl0eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQVdBLG9EQXFDQztBQTFDRDs7OztHQUlHO0FBQ0ksS0FBSyxVQUFVLG9CQUFvQixDQUN4QyxHQUFrQixFQUNsQixHQUFtQixFQUNuQixJQUF3QjtJQUV4QixJQUFJLENBQUM7UUFDSCxNQUFNLFlBQVksR0FBRyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUV2QyxHQUFXLENBQUMsSUFBSSxHQUFHLEtBQUssV0FBVyxJQUFTO1lBQzNDLElBQUksQ0FBQztnQkFDSCxpQ0FBaUM7Z0JBQ2pDLElBQUksSUFBSSxFQUFFLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7b0JBQ2xELElBQUksQ0FBQyxPQUFPLEdBQUcsc0JBQXNCLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO2dCQUN0RCxDQUFDO2dCQUVELGlDQUFpQztnQkFDakMsSUFBSSxJQUFJLEVBQUUsUUFBUSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7b0JBQ25ELElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxPQUFZLEVBQUUsRUFBRSxDQUNqRCxzQkFBc0IsQ0FBQyxPQUFPLENBQUMsQ0FDaEMsQ0FBQztnQkFDSixDQUFDO1lBQ0gsQ0FBQztZQUFDLE9BQU8sS0FBVSxFQUFFLENBQUM7Z0JBQ3BCLE9BQU8sQ0FBQyxLQUFLLENBQ1gsa0NBQWtDLEVBQ2xDLEtBQUssRUFBRSxPQUFPLElBQUksS0FBSyxDQUN4QixDQUFDO2dCQUNGLHVDQUF1QztZQUN6QyxDQUFDO1lBRUQsT0FBTyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDNUIsQ0FBQyxDQUFDO1FBRUYsSUFBSSxFQUFFLENBQUM7SUFDVCxDQUFDO0lBQUMsT0FBTyxlQUFlLEVBQUUsQ0FBQztRQUN6QixPQUFPLENBQUMsS0FBSyxDQUFDLDJDQUEyQyxFQUFFLGVBQWUsQ0FBQyxDQUFDO1FBQzVFLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUN4QixDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7R0FHRztBQUNILFNBQVMsc0JBQXNCLENBQUMsT0FBWTtJQUMxQyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDYixPQUFPLE9BQU8sQ0FBQztJQUNqQixDQUFDO0lBRUQsc0RBQXNEO0lBQ3RELElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDdEIsT0FBTyxPQUFPLENBQUM7SUFDakIsQ0FBQztJQUVELElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDbEMsT0FBTztZQUNMLEdBQUcsT0FBTztZQUNWLGtCQUFrQixFQUFFO2dCQUNsQixNQUFNLEVBQUUsWUFBWTtnQkFDcEIsU0FBUyxFQUFFLEtBQUs7Z0JBQ2hCLHdCQUF3QixFQUFFLENBQUM7Z0JBQzNCLHNCQUFzQixFQUFFLENBQUM7YUFDMUI7U0FDRixDQUFDO0lBQ0osQ0FBQztJQUVELElBQUksQ0FBQztRQUNILHVEQUF1RDtRQUN2RCxJQUFJLE1BQU0sR0FDUixTQUFTLENBQUM7UUFFWixJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssT0FBTyxFQUFFLENBQUM7WUFDL0IsTUFBTSxHQUFHLGNBQWMsQ0FBQztRQUMxQixDQUFDO1FBRUQsNkRBQTZEO1FBQzdELDJEQUEyRDtRQUMzRCxNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxPQUFPLENBQUM7UUFFM0UsT0FBTztZQUNMLEdBQUcsT0FBTztZQUNWLGtCQUFrQixFQUFFO2dCQUNsQixNQUFNO2dCQUNOLFNBQVMsRUFBRSxRQUFRO2dCQUNuQix3QkFBd0IsRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDNUMsc0JBQXNCLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7YUFDM0M7U0FDRixDQUFDO0lBQ0osQ0FBQztJQUFDLE9BQU8sS0FBVSxFQUFFLENBQUM7UUFDcEIsT0FBTyxDQUFDLEtBQUssQ0FDWCwyQkFBMkIsT0FBTyxDQUFDLEVBQUUsR0FBRyxFQUN4QyxLQUFLLEVBQUUsT0FBTyxJQUFJLEtBQUssQ0FDeEIsQ0FBQztRQUVGLE9BQU87WUFDTCxHQUFHLE9BQU87WUFDVixrQkFBa0IsRUFBRTtnQkFDbEIsTUFBTSxFQUFFLFNBQVM7Z0JBQ2pCLFNBQVMsRUFBRSxJQUFJO2dCQUNmLHdCQUF3QixFQUFFLEdBQUc7Z0JBQzdCLHNCQUFzQixFQUFFLEdBQUc7YUFDNUI7U0FDRixDQUFDO0lBQ0osQ0FBQztBQUNILENBQUMifQ==
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const http_1 = require("@medusajs/framework/http");
|
|
4
|
+
const add_sales_statistics_1 = require("./middlewares/add-sales-statistics");
|
|
5
|
+
const add_stock_availability_1 = require("./middlewares/add-stock-availability");
|
|
6
|
+
const add_product_filters_1 = require("./middlewares/add-product-filters");
|
|
7
|
+
exports.default = (0, http_1.defineMiddlewares)({
|
|
8
|
+
routes: [
|
|
9
|
+
{
|
|
10
|
+
matcher: "/store/products",
|
|
11
|
+
method: "GET",
|
|
12
|
+
middlewares: [
|
|
13
|
+
add_product_filters_1.captureFilterParams, // FIRST: Capture and remove custom filter params
|
|
14
|
+
add_stock_availability_1.addStockAvailability, // Add stock data
|
|
15
|
+
add_product_filters_1.applyProductFilters, // LAST: Apply filters using enriched data
|
|
16
|
+
add_sales_statistics_1.addSalesStatistics, // Add sales statistics
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
matcher: "/store/products/:id",
|
|
21
|
+
method: "GET",
|
|
22
|
+
middlewares: [add_stock_availability_1.addStockAvailability, add_sales_statistics_1.addSalesStatistics],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
matcher: "/admin/products",
|
|
26
|
+
method: "GET",
|
|
27
|
+
middlewares: [add_stock_availability_1.addStockAvailability, add_sales_statistics_1.addSalesStatistics],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
matcher: "/admin/products/:id",
|
|
31
|
+
method: "GET",
|
|
32
|
+
middlewares: [add_sales_statistics_1.addSalesStatistics],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYXBpL21pZGRsZXdhcmVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsbURBQTZEO0FBQzdELDZFQUF3RTtBQUN4RSxpRkFBNEU7QUFDNUUsMkVBRzJDO0FBRTNDLGtCQUFlLElBQUEsd0JBQWlCLEVBQUM7SUFDL0IsTUFBTSxFQUFFO1FBQ047WUFDRSxPQUFPLEVBQUUsaUJBQWlCO1lBQzFCLE1BQU0sRUFBRSxLQUFLO1lBQ2IsV0FBVyxFQUFFO2dCQUNYLHlDQUFtQixFQUFFLGlEQUFpRDtnQkFDdEUsNkNBQW9CLEVBQUUsaUJBQWlCO2dCQUN2Qyx5Q0FBbUIsRUFBRSwwQ0FBMEM7Z0JBQy9ELHlDQUFrQixFQUFFLHVCQUF1QjthQUM1QztTQUNGO1FBQ0Q7WUFDRSxPQUFPLEVBQUUscUJBQXFCO1lBQzlCLE1BQU0sRUFBRSxLQUFLO1lBQ2IsV0FBVyxFQUFFLENBQUMsNkNBQW9CLEVBQUUseUNBQWtCLENBQUM7U0FDeEQ7UUFDRDtZQUNFLE9BQU8sRUFBRSxpQkFBaUI7WUFDMUIsTUFBTSxFQUFFLEtBQUs7WUFDYixXQUFXLEVBQUUsQ0FBQyw2Q0FBb0IsRUFBRSx5Q0FBa0IsQ0FBQztTQUN4RDtRQUNEO1lBQ0UsT0FBTyxFQUFFLHFCQUFxQjtZQUM5QixNQUFNLEVBQUUsS0FBSztZQUNiLFdBQVcsRUFBRSxDQUFDLHlDQUFrQixDQUFDO1NBQ2xDO0tBQ0Y7Q0FDRixDQUFDLENBQUMifQ==
|