@unchainedshop/core-orders 4.4.0 → 4.6.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/README.md +2 -2
- package/lib/db/OrdersCollection.d.ts +6 -3
- package/lib/db/OrdersCollection.js +2 -1
- package/lib/module/buildFindSelector.d.ts +1 -1
- package/lib/module/buildFindSelector.js +5 -2
- package/lib/module/configureOrderPaymentsModule.d.ts +1 -7
- package/lib/module/configureOrderPaymentsModule.js +2 -23
- package/lib/module/configureOrderPositionsModule.d.ts +8 -0
- package/lib/module/configureOrderPositionsModule.js +47 -0
- package/lib/module/configureOrdersModule-mutations.d.ts +5 -0
- package/lib/module/configureOrdersModule-mutations.js +21 -0
- package/lib/module/configureOrdersModule-queries.d.ts +26 -1
- package/lib/module/configureOrdersModule-queries.js +124 -0
- package/lib/module/configureOrdersModule.d.ts +21 -7
- package/lib/module/configureOrdersModule.js +6 -8
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ await ordersModule.checkout(orderId, { paymentContext: {} });
|
|
|
103
103
|
|
|
104
104
|
| Export | Description |
|
|
105
105
|
|--------|-------------|
|
|
106
|
-
| `OrderStatus` | Order status values (OPEN, PENDING, CONFIRMED,
|
|
106
|
+
| `OrderStatus` | Order status values (OPEN, PENDING, CONFIRMED, FULFILLED, REJECTED) |
|
|
107
107
|
|
|
108
108
|
### Settings
|
|
109
109
|
|
|
@@ -132,7 +132,7 @@ await ordersModule.checkout(orderId, { paymentContext: {} });
|
|
|
132
132
|
| `ORDER_CHECKOUT` | Order checked out |
|
|
133
133
|
| `ORDER_CONFIRMED` | Order confirmed |
|
|
134
134
|
| `ORDER_REJECTED` | Order rejected |
|
|
135
|
-
| `
|
|
135
|
+
| `ORDER_FULFILLED` | Order fulfilled |
|
|
136
136
|
|
|
137
137
|
## License
|
|
138
138
|
|
|
@@ -3,7 +3,7 @@ import type { DateFilterInput } from '@unchainedshop/utils';
|
|
|
3
3
|
export declare const OrderStatus: {
|
|
4
4
|
readonly PENDING: "PENDING";
|
|
5
5
|
readonly CONFIRMED: "CONFIRMED";
|
|
6
|
-
readonly
|
|
6
|
+
readonly FULFILLED: "FULFILLED";
|
|
7
7
|
readonly REJECTED: "REJECTED";
|
|
8
8
|
};
|
|
9
9
|
export type OrderStatus = (typeof OrderStatus)[keyof typeof OrderStatus];
|
|
@@ -18,7 +18,7 @@ export type Order = {
|
|
|
18
18
|
countryCode: string;
|
|
19
19
|
currencyCode: string;
|
|
20
20
|
deliveryId?: string;
|
|
21
|
-
|
|
21
|
+
fulfilled?: Date;
|
|
22
22
|
ordered?: Date;
|
|
23
23
|
orderNumber?: string;
|
|
24
24
|
originEnrollmentId?: string;
|
|
@@ -26,7 +26,7 @@ export type Order = {
|
|
|
26
26
|
status: OrderStatus | null;
|
|
27
27
|
userId: string;
|
|
28
28
|
} & LogFields & TimestampFields;
|
|
29
|
-
export interface OrderQuery
|
|
29
|
+
export interface OrderQuery {
|
|
30
30
|
includeCarts?: boolean;
|
|
31
31
|
queryString?: string;
|
|
32
32
|
status?: OrderStatus[];
|
|
@@ -34,5 +34,8 @@ export interface OrderQuery extends mongodb.Filter<Order> {
|
|
|
34
34
|
deliveryIds?: string[];
|
|
35
35
|
paymentIds?: string[];
|
|
36
36
|
dateRange?: DateFilterInput;
|
|
37
|
+
orderIds?: string[];
|
|
38
|
+
paymentProviderIds?: string[];
|
|
39
|
+
deliveryProviderIds?: string[];
|
|
37
40
|
}
|
|
38
41
|
export declare const OrdersCollection: (db: mongodb.Db) => Promise<mongodb.Collection<Order>>;
|
|
@@ -2,7 +2,7 @@ import { mongodb, buildDbIndexes, isDocumentDBCompatModeEnabled, } from '@unchai
|
|
|
2
2
|
export const OrderStatus = {
|
|
3
3
|
PENDING: 'PENDING',
|
|
4
4
|
CONFIRMED: 'CONFIRMED',
|
|
5
|
-
|
|
5
|
+
FULFILLED: 'FULFILLED',
|
|
6
6
|
REJECTED: 'REJECTED',
|
|
7
7
|
};
|
|
8
8
|
export const OrdersCollection = async (db) => {
|
|
@@ -33,6 +33,7 @@ export const OrdersCollection = async (db) => {
|
|
|
33
33
|
]);
|
|
34
34
|
}
|
|
35
35
|
await buildDbIndexes(Orders, [
|
|
36
|
+
{ index: { deleted: 1 } },
|
|
36
37
|
{ index: { userId: 1 } },
|
|
37
38
|
{ index: { status: 1 } },
|
|
38
39
|
{ index: { orderNumber: 1 } },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { OrderQuery, Order } from '../db/OrdersCollection.ts';
|
|
2
2
|
import { mongodb } from '@unchainedshop/mongodb';
|
|
3
|
-
export declare const buildFindSelector: ({ includeCarts, status, userId, queryString, paymentIds, deliveryIds, dateRange,
|
|
3
|
+
export declare const buildFindSelector: ({ includeCarts, status, userId, queryString, paymentIds, deliveryIds, dateRange, orderIds, }: OrderQuery) => mongodb.Filter<Order>;
|
|
4
4
|
export default buildFindSelector;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { assertDocumentDBCompatMode, mongodb } from '@unchainedshop/mongodb';
|
|
2
|
-
export const buildFindSelector = ({ includeCarts, status, userId, queryString, paymentIds, deliveryIds, dateRange,
|
|
3
|
-
const selector = {
|
|
2
|
+
export const buildFindSelector = ({ includeCarts, status, userId, queryString, paymentIds, deliveryIds, dateRange, orderIds, }) => {
|
|
3
|
+
const selector = {};
|
|
4
4
|
if (userId) {
|
|
5
5
|
selector.userId = userId;
|
|
6
6
|
}
|
|
7
|
+
if (orderIds) {
|
|
8
|
+
selector._id = { $in: orderIds };
|
|
9
|
+
}
|
|
7
10
|
if (dateRange) {
|
|
8
11
|
const dateFilter = {};
|
|
9
12
|
if (dateRange.start)
|
|
@@ -2,7 +2,6 @@ import { mongodb } from '@unchainedshop/mongodb';
|
|
|
2
2
|
import type { PricingCalculation } from '@unchainedshop/utils';
|
|
3
3
|
import { type OrderPayment, OrderPaymentStatus } from '../db/OrderPaymentsCollection.ts';
|
|
4
4
|
export declare const buildFindOrderPaymentByIdSelector: (orderPaymentId: string) => mongodb.Filter<OrderPayment>;
|
|
5
|
-
export declare const buildFindByContextDataSelector: (context: any) => mongodb.Filter<OrderPayment> | null;
|
|
6
5
|
export declare const configureOrderPaymentsModule: ({ OrderPayments, }: {
|
|
7
6
|
OrderPayments: mongodb.Collection<OrderPayment>;
|
|
8
7
|
}) => {
|
|
@@ -12,12 +11,7 @@ export declare const configureOrderPaymentsModule: ({ OrderPayments, }: {
|
|
|
12
11
|
findOrderPaymentsByProviderIds: ({ paymentProviderIds, }: {
|
|
13
12
|
paymentProviderIds: string[];
|
|
14
13
|
}, options?: mongodb.FindOptions) => Promise<OrderPayment[]>;
|
|
15
|
-
|
|
16
|
-
context: any;
|
|
17
|
-
}, options?: mongodb.FindOptions) => Promise<OrderPayment | null>;
|
|
18
|
-
countOrderPaymentsByContextData: ({ context, }: {
|
|
19
|
-
context: any;
|
|
20
|
-
}, options?: mongodb.FindOptions) => Promise<number>;
|
|
14
|
+
findOrderPaymentByTransactionId: (transactionId: string) => Promise<mongodb.WithId<OrderPayment> | null>;
|
|
21
15
|
normalizedStatus: (orderPayment: OrderPayment) => OrderPaymentStatus;
|
|
22
16
|
create: (doc: Omit<OrderPayment, "_id" | "created"> & Pick<Partial<OrderPayment>, "_id" | "created">) => Promise<OrderPayment>;
|
|
23
17
|
logEvent: (orderPaymentId: string, event: any) => Promise<boolean>;
|
|
@@ -3,18 +3,6 @@ import { generateDbFilterById, generateDbObjectId, mongodb } from '@unchainedsho
|
|
|
3
3
|
import { OrderPaymentStatus } from "../db/OrderPaymentsCollection.js";
|
|
4
4
|
const ORDER_PAYMENT_EVENTS = ['ORDER_UPDATE_PAYMENT', 'ORDER_SIGN_PAYMENT', 'ORDER_PAY'];
|
|
5
5
|
export const buildFindOrderPaymentByIdSelector = (orderPaymentId) => generateDbFilterById(orderPaymentId);
|
|
6
|
-
export const buildFindByContextDataSelector = (context) => {
|
|
7
|
-
const contextKeys = Object.keys(context);
|
|
8
|
-
if (contextKeys.length === 0)
|
|
9
|
-
return null;
|
|
10
|
-
const selector = contextKeys.reduce((currentSelector, key) => context[key] !== undefined
|
|
11
|
-
? {
|
|
12
|
-
...currentSelector,
|
|
13
|
-
[`context.${key}`]: context[key],
|
|
14
|
-
}
|
|
15
|
-
: currentSelector, {});
|
|
16
|
-
return selector;
|
|
17
|
-
};
|
|
18
6
|
export const configureOrderPaymentsModule = ({ OrderPayments, }) => {
|
|
19
7
|
registerEvents(ORDER_PAYMENT_EVENTS);
|
|
20
8
|
const normalizedStatus = (orderPayment) => {
|
|
@@ -52,17 +40,8 @@ export const configureOrderPaymentsModule = ({ OrderPayments, }) => {
|
|
|
52
40
|
return [];
|
|
53
41
|
return OrderPayments.find({ paymentProviderId: { $in: paymentProviderIds } }, options).toArray();
|
|
54
42
|
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!selector)
|
|
58
|
-
return null;
|
|
59
|
-
return OrderPayments.findOne(selector, options);
|
|
60
|
-
},
|
|
61
|
-
countOrderPaymentsByContextData: async ({ context, }, options) => {
|
|
62
|
-
const selector = buildFindByContextDataSelector(context);
|
|
63
|
-
if (!selector)
|
|
64
|
-
return 0;
|
|
65
|
-
return OrderPayments.countDocuments(selector, options);
|
|
43
|
+
findOrderPaymentByTransactionId: async (transactionId) => {
|
|
44
|
+
return OrderPayments.findOne({ transactionId });
|
|
66
45
|
},
|
|
67
46
|
normalizedStatus,
|
|
68
47
|
create: async (doc) => {
|
|
@@ -11,6 +11,11 @@ export interface OrderPositionAggregateParams {
|
|
|
11
11
|
limit?: number;
|
|
12
12
|
pipeline?: mongodb.Document[];
|
|
13
13
|
}
|
|
14
|
+
export interface TopProductRecord {
|
|
15
|
+
productId: string;
|
|
16
|
+
totalSold: number;
|
|
17
|
+
totalRevenue: number;
|
|
18
|
+
}
|
|
14
19
|
export declare const buildFindOrderPositionByIdSelector: (orderPositionId: string, orderId?: string) => mongodb.Filter<OrderPosition>;
|
|
15
20
|
export declare const configureOrderPositionsModule: ({ OrderPositions, }: {
|
|
16
21
|
OrderPositions: mongodb.Collection<OrderPosition>;
|
|
@@ -50,5 +55,8 @@ export declare const configureOrderPositionsModule: ({ OrderPositions, }: {
|
|
|
50
55
|
}) => Promise<OrderPosition>;
|
|
51
56
|
deleteOrderPositions: (orderId: string) => Promise<number>;
|
|
52
57
|
aggregatePositions: ({ match, project, group, addFields, sort, limit, pipeline, }: OrderPositionAggregateParams) => Promise<mongodb.Document[]>;
|
|
58
|
+
getTopProducts: (orderIds: string[], options?: {
|
|
59
|
+
limit?: number;
|
|
60
|
+
}) => Promise<TopProductRecord[]>;
|
|
53
61
|
};
|
|
54
62
|
export type OrderPositionsModule = ReturnType<typeof configureOrderPositionsModule>;
|
|
@@ -151,5 +151,52 @@ export const configureOrderPositionsModule = ({ OrderPositions, }) => {
|
|
|
151
151
|
stages.push({ $limit: limit });
|
|
152
152
|
return OrderPositions.aggregate(stages).toArray();
|
|
153
153
|
},
|
|
154
|
+
getTopProducts: async (orderIds, options) => {
|
|
155
|
+
const limit = options?.limit || 10;
|
|
156
|
+
const pipeline = [
|
|
157
|
+
{ $match: { orderId: { $in: orderIds } } },
|
|
158
|
+
{
|
|
159
|
+
$project: {
|
|
160
|
+
productId: 1,
|
|
161
|
+
quantity: 1,
|
|
162
|
+
itemAmount: {
|
|
163
|
+
$let: {
|
|
164
|
+
vars: {
|
|
165
|
+
item: {
|
|
166
|
+
$first: {
|
|
167
|
+
$filter: {
|
|
168
|
+
input: '$calculation',
|
|
169
|
+
as: 'c',
|
|
170
|
+
cond: { $eq: ['$$c.category', 'ITEM'] },
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
in: '$$item.amount',
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
$group: {
|
|
182
|
+
_id: '$productId',
|
|
183
|
+
totalSold: { $sum: '$quantity' },
|
|
184
|
+
totalRevenue: { $sum: '$itemAmount' },
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{ $match: { totalSold: { $gt: 0 } } },
|
|
188
|
+
{ $sort: { totalSold: -1 } },
|
|
189
|
+
{ $limit: limit },
|
|
190
|
+
{
|
|
191
|
+
$project: {
|
|
192
|
+
_id: 0,
|
|
193
|
+
productId: '$_id',
|
|
194
|
+
totalSold: 1,
|
|
195
|
+
totalRevenue: 1,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
return OrderPositions.aggregate(pipeline, { allowDiskUse: true }).toArray();
|
|
200
|
+
},
|
|
154
201
|
};
|
|
155
202
|
};
|
|
@@ -28,5 +28,10 @@ export declare const configureOrderModuleMutations: ({ Orders, OrderPositions, }
|
|
|
28
28
|
updateContact: (orderId: string, contact: Contact) => Promise<mongodb.WithId<Order> | null>;
|
|
29
29
|
updateCalculationSheet: (orderId: string, calculation: any) => Promise<mongodb.WithId<Order> | null>;
|
|
30
30
|
updateContext: (orderId: string, context: any) => Promise<mongodb.WithId<Order> | null>;
|
|
31
|
+
updateCartFields: (orderId: string, updates: {
|
|
32
|
+
meta?: any;
|
|
33
|
+
billingAddress?: Address;
|
|
34
|
+
contact?: Contact;
|
|
35
|
+
}) => Promise<Order | null>;
|
|
31
36
|
};
|
|
32
37
|
export type OrderMutations = ReturnType<typeof configureOrderModuleMutations>;
|
|
@@ -107,5 +107,26 @@ export const configureOrderModuleMutations = ({ Orders, OrderPositions, }) => {
|
|
|
107
107
|
await emit('ORDER_UPDATE', { order, field: 'context' });
|
|
108
108
|
return order;
|
|
109
109
|
},
|
|
110
|
+
updateCartFields: async (orderId, updates) => {
|
|
111
|
+
const $set = { updated: new Date() };
|
|
112
|
+
if (updates.billingAddress) {
|
|
113
|
+
$set.billingAddress = updates.billingAddress;
|
|
114
|
+
}
|
|
115
|
+
if (updates.contact) {
|
|
116
|
+
$set.contact = updates.contact;
|
|
117
|
+
}
|
|
118
|
+
if (updates.meta && Object.keys(updates.meta).length > 0) {
|
|
119
|
+
const contextSetters = Object.fromEntries(Object.entries(updates.meta).map(([key, value]) => [`context.${key}`, value]));
|
|
120
|
+
Object.assign($set, contextSetters);
|
|
121
|
+
}
|
|
122
|
+
if (Object.keys($set).length === 1) {
|
|
123
|
+
return Orders.findOne(generateDbFilterById(orderId), {});
|
|
124
|
+
}
|
|
125
|
+
const order = await Orders.findOneAndUpdate(generateDbFilterById(orderId), { $set }, { returnDocument: 'after' });
|
|
126
|
+
if (!order)
|
|
127
|
+
return null;
|
|
128
|
+
await emit('ORDER_UPDATE', { order, field: 'cartFields' });
|
|
129
|
+
return order;
|
|
130
|
+
},
|
|
110
131
|
};
|
|
111
132
|
};
|
|
@@ -8,13 +8,27 @@ export interface OrderReport {
|
|
|
8
8
|
confirmCount: number;
|
|
9
9
|
fulfillCount: number;
|
|
10
10
|
}
|
|
11
|
+
export interface TopCustomerRecord {
|
|
12
|
+
userId: string;
|
|
13
|
+
currencyCode: string;
|
|
14
|
+
totalSpent: number;
|
|
15
|
+
orderCount: number;
|
|
16
|
+
lastOrderDate: Date;
|
|
17
|
+
averageOrderValue: number;
|
|
18
|
+
}
|
|
11
19
|
export interface OrderStatisticsRecord {
|
|
12
20
|
date: string;
|
|
21
|
+
count: number;
|
|
13
22
|
total: {
|
|
14
23
|
amount: number;
|
|
15
|
-
|
|
24
|
+
currencyCode: string;
|
|
16
25
|
};
|
|
17
26
|
}
|
|
27
|
+
export interface DateRange {
|
|
28
|
+
start?: string;
|
|
29
|
+
end?: string;
|
|
30
|
+
}
|
|
31
|
+
export type StatisticsDateField = 'created' | 'ordered' | 'rejected' | 'confirmed' | 'fulfilled';
|
|
18
32
|
export interface OrderAggregateParams {
|
|
19
33
|
match?: Record<string, any>;
|
|
20
34
|
matchAfterGroup?: Record<string, any>;
|
|
@@ -49,5 +63,16 @@ export declare const configureOrdersModuleQueries: ({ Orders }: {
|
|
|
49
63
|
orderId: string;
|
|
50
64
|
}) => Promise<boolean>;
|
|
51
65
|
aggregateOrders: ({ match, project, group, sort, limit, addFields, pipeline, }: OrderAggregateParams) => Promise<mongodb.Document[]>;
|
|
66
|
+
statistics: {
|
|
67
|
+
countByDateField(dateField: StatisticsDateField, dateRange?: DateRange, options?: {
|
|
68
|
+
includeCarts?: boolean;
|
|
69
|
+
}): Promise<number>;
|
|
70
|
+
aggregateByDateField(dateField: StatisticsDateField, dateRange?: DateRange, options?: {
|
|
71
|
+
includeCarts?: boolean;
|
|
72
|
+
}): Promise<OrderStatisticsRecord[]>;
|
|
73
|
+
getTopCustomers(orderIds: string[], options?: {
|
|
74
|
+
limit?: number;
|
|
75
|
+
}): Promise<TopCustomerRecord[]>;
|
|
76
|
+
};
|
|
52
77
|
};
|
|
53
78
|
export type OrderQueries = ReturnType<typeof configureOrdersModuleQueries>;
|
|
@@ -2,6 +2,16 @@ import { SortDirection } from '@unchainedshop/utils';
|
|
|
2
2
|
import { generateDbFilterById, buildSortOptions, mongodb } from '@unchainedshop/mongodb';
|
|
3
3
|
import buildFindSelector from "./buildFindSelector.js";
|
|
4
4
|
import {} from "../db/OrdersCollection.js";
|
|
5
|
+
function buildDateMatch(dateField, dateRange) {
|
|
6
|
+
if (!dateRange?.start && !dateRange?.end)
|
|
7
|
+
return { [dateField]: { $exists: true } };
|
|
8
|
+
const rangeMatch = {};
|
|
9
|
+
if (dateRange?.start)
|
|
10
|
+
rangeMatch.$gte = new Date(dateRange.start);
|
|
11
|
+
if (dateRange?.end)
|
|
12
|
+
rangeMatch.$lte = new Date(dateRange.end);
|
|
13
|
+
return { [dateField]: rangeMatch };
|
|
14
|
+
}
|
|
5
15
|
export const configureOrdersModuleQueries = ({ Orders }) => {
|
|
6
16
|
return {
|
|
7
17
|
isCart: (order) => {
|
|
@@ -82,5 +92,119 @@ export const configureOrdersModuleQueries = ({ Orders }) => {
|
|
|
82
92
|
stages.push({ $limit: limit });
|
|
83
93
|
return Orders.aggregate(stages, { allowDiskUse: true }).toArray();
|
|
84
94
|
},
|
|
95
|
+
statistics: {
|
|
96
|
+
async countByDateField(dateField, dateRange, options) {
|
|
97
|
+
const match = buildDateMatch(dateField, dateRange);
|
|
98
|
+
if (options?.includeCarts) {
|
|
99
|
+
match.status = null;
|
|
100
|
+
match.orderNumber = null;
|
|
101
|
+
}
|
|
102
|
+
const pipeline = [{ $match: match }, { $count: 'count' }];
|
|
103
|
+
const result = await Orders.aggregate(pipeline).toArray();
|
|
104
|
+
return result[0]?.count ?? 0;
|
|
105
|
+
},
|
|
106
|
+
async aggregateByDateField(dateField, dateRange, options) {
|
|
107
|
+
const match = buildDateMatch(dateField, dateRange);
|
|
108
|
+
if (options?.includeCarts) {
|
|
109
|
+
match.status = null;
|
|
110
|
+
match.orderNumber = null;
|
|
111
|
+
}
|
|
112
|
+
const pipeline = [
|
|
113
|
+
{ $match: match },
|
|
114
|
+
{
|
|
115
|
+
$addFields: {
|
|
116
|
+
orderTotal: {
|
|
117
|
+
$reduce: {
|
|
118
|
+
input: { $ifNull: ['$calculation', []] },
|
|
119
|
+
initialValue: 0,
|
|
120
|
+
in: { $add: ['$$value', { $ifNull: ['$$this.amount', 0] }] },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
$group: {
|
|
127
|
+
_id: {
|
|
128
|
+
date: { $dateToString: { format: '%Y-%m-%d', date: `$${dateField}` } },
|
|
129
|
+
currency: '$currencyCode',
|
|
130
|
+
},
|
|
131
|
+
totalAmount: { $sum: '$orderTotal' },
|
|
132
|
+
count: { $sum: 1 },
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
$project: {
|
|
137
|
+
_id: 0,
|
|
138
|
+
date: '$_id.date',
|
|
139
|
+
total: { amount: '$totalAmount', currencyCode: '$_id.currency' },
|
|
140
|
+
count: 1,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{ $sort: { date: 1 } },
|
|
144
|
+
];
|
|
145
|
+
return Orders.aggregate(pipeline).toArray();
|
|
146
|
+
},
|
|
147
|
+
async getTopCustomers(orderIds, options) {
|
|
148
|
+
const limit = options?.limit || 10;
|
|
149
|
+
const pipeline = [
|
|
150
|
+
{ $match: { _id: { $in: orderIds } } },
|
|
151
|
+
{
|
|
152
|
+
$project: {
|
|
153
|
+
userId: 1,
|
|
154
|
+
created: 1,
|
|
155
|
+
currencyCode: 1,
|
|
156
|
+
itemAmount: {
|
|
157
|
+
$let: {
|
|
158
|
+
vars: {
|
|
159
|
+
item: {
|
|
160
|
+
$first: {
|
|
161
|
+
$filter: {
|
|
162
|
+
input: '$calculation',
|
|
163
|
+
as: 'c',
|
|
164
|
+
cond: { $eq: ['$$c.category', 'ITEMS'] },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
in: '$$item.amount',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
$group: {
|
|
176
|
+
_id: { userId: '$userId', currencyCode: '$currencyCode' },
|
|
177
|
+
totalSpent: { $sum: '$itemAmount' },
|
|
178
|
+
orderCount: { $sum: 1 },
|
|
179
|
+
lastOrderDate: { $max: '$created' },
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{ $match: { totalSpent: { $gt: 0 } } },
|
|
183
|
+
{
|
|
184
|
+
$addFields: {
|
|
185
|
+
averageOrderValue: {
|
|
186
|
+
$cond: [{ $eq: ['$orderCount', 0] }, 0, { $divide: ['$totalSpent', '$orderCount'] }],
|
|
187
|
+
},
|
|
188
|
+
currencyCode: '$_id.currencyCode',
|
|
189
|
+
userId: '$_id.userId',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{ $sort: { totalSpent: -1 } },
|
|
193
|
+
{ $limit: limit },
|
|
194
|
+
{
|
|
195
|
+
$project: {
|
|
196
|
+
_id: 0,
|
|
197
|
+
userId: 1,
|
|
198
|
+
currencyCode: 1,
|
|
199
|
+
totalSpent: 1,
|
|
200
|
+
orderCount: 1,
|
|
201
|
+
lastOrderDate: 1,
|
|
202
|
+
averageOrderValue: 1,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
return Orders.aggregate(pipeline, { allowDiskUse: true }).toArray();
|
|
207
|
+
},
|
|
208
|
+
},
|
|
85
209
|
};
|
|
86
210
|
};
|
|
@@ -76,6 +76,9 @@ export declare const configureOrdersModule: ({ db, migrationRepository, options:
|
|
|
76
76
|
}) => Promise<import("../db/OrderPositionsCollection.ts").OrderPosition>;
|
|
77
77
|
deleteOrderPositions: (orderId: string) => Promise<number>;
|
|
78
78
|
aggregatePositions: ({ match, project, group, addFields, sort, limit, pipeline, }: import("./configureOrderPositionsModule.ts").OrderPositionAggregateParams) => Promise<mongodb.Document[]>;
|
|
79
|
+
getTopProducts: (orderIds: string[], options?: {
|
|
80
|
+
limit?: number;
|
|
81
|
+
}) => Promise<import("./configureOrderPositionsModule.ts").TopProductRecord[]>;
|
|
79
82
|
};
|
|
80
83
|
payments: {
|
|
81
84
|
findOrderPayment: ({ orderPaymentId, }: {
|
|
@@ -84,12 +87,7 @@ export declare const configureOrdersModule: ({ db, migrationRepository, options:
|
|
|
84
87
|
findOrderPaymentsByProviderIds: ({ paymentProviderIds, }: {
|
|
85
88
|
paymentProviderIds: string[];
|
|
86
89
|
}, options?: mongodb.FindOptions) => Promise<import("../db/OrderPaymentsCollection.ts").OrderPayment[]>;
|
|
87
|
-
|
|
88
|
-
context: any;
|
|
89
|
-
}, options?: mongodb.FindOptions) => Promise<import("../db/OrderPaymentsCollection.ts").OrderPayment | null>;
|
|
90
|
-
countOrderPaymentsByContextData: ({ context, }: {
|
|
91
|
-
context: any;
|
|
92
|
-
}, options?: mongodb.FindOptions) => Promise<number>;
|
|
90
|
+
findOrderPaymentByTransactionId: (transactionId: string) => Promise<mongodb.WithId<import("../db/OrderPaymentsCollection.ts").OrderPayment> | null>;
|
|
93
91
|
normalizedStatus: (orderPayment: import("../db/OrderPaymentsCollection.ts").OrderPayment) => import("../db/OrderPaymentsCollection.ts").OrderPaymentStatus;
|
|
94
92
|
create: (doc: Omit<import("../db/OrderPaymentsCollection.ts").OrderPayment, "_id" | "created"> & Pick<Partial<import("../db/OrderPaymentsCollection.ts").OrderPayment>, "_id" | "created">) => Promise<import("../db/OrderPaymentsCollection.ts").OrderPayment>;
|
|
95
93
|
logEvent: (orderPaymentId: string, event: any) => Promise<boolean>;
|
|
@@ -107,7 +105,7 @@ export declare const configureOrdersModule: ({ db, migrationRepository, options:
|
|
|
107
105
|
status: OrderStatus | null;
|
|
108
106
|
info?: string;
|
|
109
107
|
}) => Promise<mongodb.WithId<Order> | null>;
|
|
110
|
-
acquireLock: (orderId: string, identifier: string, timeout?: number) => Promise<
|
|
108
|
+
acquireLock: (orderId: string, identifier: string, timeout?: number) => Promise<import("@kontsedal/locco").Lock>;
|
|
111
109
|
setDeliveryProvider: (orderId: string, deliveryProviderId: string) => Promise<mongodb.WithId<Order> | null>;
|
|
112
110
|
setPaymentProvider: (orderId: string, paymentProviderId: string) => Promise<mongodb.WithId<Order> | null>;
|
|
113
111
|
create: ({ userId, orderNumber, currencyCode, countryCode, billingAddress, contact, context, }: {
|
|
@@ -133,6 +131,11 @@ export declare const configureOrdersModule: ({ db, migrationRepository, options:
|
|
|
133
131
|
updateContact: (orderId: string, contact: import("@unchainedshop/mongodb").Contact) => Promise<mongodb.WithId<Order> | null>;
|
|
134
132
|
updateCalculationSheet: (orderId: string, calculation: any) => Promise<mongodb.WithId<Order> | null>;
|
|
135
133
|
updateContext: (orderId: string, context: any) => Promise<mongodb.WithId<Order> | null>;
|
|
134
|
+
updateCartFields: (orderId: string, updates: {
|
|
135
|
+
meta?: any;
|
|
136
|
+
billingAddress?: import("@unchainedshop/mongodb").Address;
|
|
137
|
+
contact?: import("@unchainedshop/mongodb").Contact;
|
|
138
|
+
}) => Promise<Order | null>;
|
|
136
139
|
isCart: (order: Order) => boolean;
|
|
137
140
|
cart: ({ orderNumber, countryCode, userId, }: {
|
|
138
141
|
countryCode?: string;
|
|
@@ -154,5 +157,16 @@ export declare const configureOrdersModule: ({ db, migrationRepository, options:
|
|
|
154
157
|
orderId: string;
|
|
155
158
|
}) => Promise<boolean>;
|
|
156
159
|
aggregateOrders: ({ match, project, group, sort, limit, addFields, pipeline, }: import("./configureOrdersModule-queries.ts").OrderAggregateParams) => Promise<mongodb.Document[]>;
|
|
160
|
+
statistics: {
|
|
161
|
+
countByDateField(dateField: import("./configureOrdersModule-queries.ts").StatisticsDateField, dateRange?: import("./configureOrdersModule-queries.ts").DateRange, options?: {
|
|
162
|
+
includeCarts?: boolean;
|
|
163
|
+
}): Promise<number>;
|
|
164
|
+
aggregateByDateField(dateField: import("./configureOrdersModule-queries.ts").StatisticsDateField, dateRange?: import("./configureOrdersModule-queries.ts").DateRange, options?: {
|
|
165
|
+
includeCarts?: boolean;
|
|
166
|
+
}): Promise<import("./configureOrdersModule-queries.ts").OrderStatisticsRecord[]>;
|
|
167
|
+
getTopCustomers(orderIds: string[], options?: {
|
|
168
|
+
limit?: number;
|
|
169
|
+
}): Promise<import("./configureOrdersModule-queries.ts").TopCustomerRecord[]>;
|
|
170
|
+
};
|
|
157
171
|
}>;
|
|
158
172
|
export type OrdersModule = Awaited<ReturnType<typeof configureOrdersModule>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { Locker, MongoAdapter } from '@kontsedal/locco';
|
|
1
2
|
import { generateDbFilterById, mongodb } from '@unchainedshop/mongodb';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
3
|
import { OrderDeliveriesCollection } from "../db/OrderDeliveriesCollection.js";
|
|
4
4
|
import { OrderDiscountsCollection } from "../db/OrderDiscountsCollection.js";
|
|
5
5
|
import { OrderPaymentsCollection } from "../db/OrderPaymentsCollection.js";
|
|
@@ -14,13 +14,11 @@ import { configureOrderModuleMutations } from "./configureOrdersModule-mutations
|
|
|
14
14
|
import { configureOrdersModuleQueries } from "./configureOrdersModule-queries.js";
|
|
15
15
|
import { emit, registerEvents } from '@unchainedshop/events';
|
|
16
16
|
import renameCurrencyCode from "../migrations/20250502111800-currency-code.js";
|
|
17
|
-
const require = createRequire(import.meta.url);
|
|
18
|
-
const { Locker, MongoAdapter } = require('@kontsedal/locco');
|
|
19
17
|
const ORDER_EVENTS = [
|
|
20
18
|
'ORDER_CHECKOUT',
|
|
21
19
|
'ORDER_CONFIRMED',
|
|
22
20
|
'ORDER_REJECTED',
|
|
23
|
-
'
|
|
21
|
+
'ORDER_FULFILLED',
|
|
24
22
|
];
|
|
25
23
|
export const configureOrdersModule = async ({ db, migrationRepository, options: orderOptions = {}, }) => {
|
|
26
24
|
renameCurrencyCode(migrationRepository);
|
|
@@ -84,8 +82,8 @@ export const configureOrdersModule = async ({ db, migrationRepository, options:
|
|
|
84
82
|
updated: new Date(),
|
|
85
83
|
};
|
|
86
84
|
switch (status) {
|
|
87
|
-
case OrderStatus.
|
|
88
|
-
$set.
|
|
85
|
+
case OrderStatus.FULFILLED:
|
|
86
|
+
$set.fulfilled = order.fulfilled || date;
|
|
89
87
|
case OrderStatus.REJECTED:
|
|
90
88
|
case OrderStatus.CONFIRMED:
|
|
91
89
|
if (status === OrderStatus.REJECTED) {
|
|
@@ -123,8 +121,8 @@ export const configureOrdersModule = async ({ db, migrationRepository, options:
|
|
|
123
121
|
await emit('ORDER_CHECKOUT', { order: modificationResult.value, oldStatus: order.status });
|
|
124
122
|
}
|
|
125
123
|
switch (status) {
|
|
126
|
-
case OrderStatus.
|
|
127
|
-
await emit('
|
|
124
|
+
case OrderStatus.FULFILLED:
|
|
125
|
+
await emit('ORDER_FULFILLED', { order: modificationResult.value, oldStatus: order.status });
|
|
128
126
|
break;
|
|
129
127
|
case OrderStatus.REJECTED:
|
|
130
128
|
await emit('ORDER_REJECTED', { order: modificationResult.value, oldStatus: order.status });
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/core-orders",
|
|
3
|
-
"
|
|
3
|
+
"description": "Order management module for the Unchained Engine",
|
|
4
|
+
"version": "4.6.0",
|
|
4
5
|
"main": "lib/orders-index.js",
|
|
5
6
|
"types": "lib/orders-index.d.ts",
|
|
6
7
|
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
7
9
|
"scripts": {
|
|
8
10
|
"clean": "tsc -b --clean",
|
|
9
11
|
"build": "tsc -b",
|
|
@@ -33,10 +35,10 @@
|
|
|
33
35
|
},
|
|
34
36
|
"homepage": "https://github.com/unchainedshop/unchained#readme",
|
|
35
37
|
"dependencies": {
|
|
36
|
-
"@kontsedal/locco": "
|
|
37
|
-
"@unchainedshop/events": "^4.
|
|
38
|
-
"@unchainedshop/logger": "^4.
|
|
39
|
-
"@unchainedshop/utils": "^4.
|
|
38
|
+
"@kontsedal/locco": "1.0.0",
|
|
39
|
+
"@unchainedshop/events": "^4.6.0",
|
|
40
|
+
"@unchainedshop/logger": "^4.6.0",
|
|
41
|
+
"@unchainedshop/utils": "^4.6.0"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@types/node": "^25.0.0",
|