@unchainedshop/core-orders 5.0.0-alpha.1 → 5.0.0-alpha.10
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 +7 -115
- package/lib/db/OrderDeliveriesCollection.js +1 -1
- package/lib/db/OrderDiscountsCollection.js +2 -2
- package/lib/db/OrderLocksCollection.d.ts +10 -0
- package/lib/db/OrderLocksCollection.js +40 -0
- package/lib/db/OrderPaymentsCollection.js +2 -3
- package/lib/db/OrderPositionsCollection.js +1 -1
- package/lib/db/OrdersCollection.js +25 -28
- package/lib/migrations/20260625120000-normalize-contact-phone.d.ts +2 -0
- package/lib/migrations/20260625120000-normalize-contact-phone.js +27 -0
- package/lib/migrations/20260907120000-order-calculation-net.d.ts +4 -0
- package/lib/migrations/20260907120000-order-calculation-net.js +125 -0
- package/lib/module/buildFindSelector.js +1 -2
- package/lib/module/configureOrderDeliveriesModule.d.ts +3 -0
- package/lib/module/configureOrderDeliveriesModule.js +5 -0
- package/lib/module/configureOrderDiscountsModule.d.ts +3 -1
- package/lib/module/configureOrderDiscountsModule.js +5 -2
- package/lib/module/configureOrderPaymentsModule.d.ts +3 -0
- package/lib/module/configureOrderPaymentsModule.js +6 -1
- package/lib/module/configureOrderPositionsModule.d.ts +3 -1
- package/lib/module/configureOrderPositionsModule.js +5 -2
- package/lib/module/configureOrdersModule-mutations.d.ts +1 -1
- package/lib/module/configureOrdersModule-mutations.js +25 -4
- package/lib/module/configureOrdersModule-queries.d.ts +5 -1
- package/lib/module/configureOrdersModule-queries.js +28 -14
- package/lib/module/configureOrdersModule.d.ts +21 -5
- package/lib/module/configureOrdersModule.js +7 -11
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -13,126 +13,18 @@ npm install @unchainedshop/core-orders
|
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
import { configureOrdersModule, OrderStatus } from '@unchainedshop/core-orders';
|
|
18
|
-
|
|
19
|
-
const ordersModule = await configureOrdersModule({ db });
|
|
20
|
-
|
|
21
|
-
// Create an order
|
|
22
|
-
const orderId = await ordersModule.create({
|
|
23
|
-
userId: 'user-123',
|
|
24
|
-
currency: 'CHF',
|
|
25
|
-
countryCode: 'CH',
|
|
26
|
-
});
|
|
16
|
+
The platform initializes this module as `platform.unchainedAPI.modules.orders`.
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
orderId,
|
|
31
|
-
productId: 'product-456',
|
|
32
|
-
quantity: 2,
|
|
33
|
-
});
|
|
18
|
+
```typescript
|
|
19
|
+
const { orders } = platform.unchainedAPI.modules;
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
await
|
|
21
|
+
const order = await orders.findOrder({ orderId: 'order-123' });
|
|
22
|
+
const userOrders = await orders.findOrders({ userId: 'user-123', limit: 20 });
|
|
37
23
|
```
|
|
38
24
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
### Module Configuration
|
|
42
|
-
|
|
43
|
-
| Export | Description |
|
|
44
|
-
|--------|-------------|
|
|
45
|
-
| `configureOrdersModule` | Configure and return the orders module |
|
|
46
|
-
|
|
47
|
-
### Queries
|
|
48
|
-
|
|
49
|
-
| Method | Description |
|
|
50
|
-
|--------|-------------|
|
|
51
|
-
| `findOrder` | Find order by ID or number |
|
|
52
|
-
| `findOrders` | Find orders with filtering and pagination |
|
|
53
|
-
| `count` | Count orders matching query |
|
|
54
|
-
| `orderExists` | Check if order exists |
|
|
55
|
-
|
|
56
|
-
### Mutations
|
|
57
|
-
|
|
58
|
-
| Method | Description |
|
|
59
|
-
|--------|-------------|
|
|
60
|
-
| `create` | Create a new order |
|
|
61
|
-
| `update` | Update order data |
|
|
62
|
-
| `delete` | Delete an order |
|
|
63
|
-
| `checkout` | Process order checkout |
|
|
64
|
-
| `confirm` | Confirm an order |
|
|
65
|
-
| `reject` | Reject an order |
|
|
66
|
-
| `setPaymentProvider` | Set payment provider |
|
|
67
|
-
| `setDeliveryProvider` | Set delivery provider |
|
|
68
|
-
|
|
69
|
-
### Submodules
|
|
70
|
-
|
|
71
|
-
#### Positions (`orders.positions`)
|
|
72
|
-
| Method | Description |
|
|
73
|
-
|--------|-------------|
|
|
74
|
-
| `findPositions` | Find order positions |
|
|
75
|
-
| `create` | Add position to order |
|
|
76
|
-
| `update` | Update position |
|
|
77
|
-
| `delete` | Remove position |
|
|
78
|
-
|
|
79
|
-
#### Payments (`orders.payments`)
|
|
80
|
-
| Method | Description |
|
|
81
|
-
|--------|-------------|
|
|
82
|
-
| `findPayment` | Find order payment |
|
|
83
|
-
| `create` | Create payment for order |
|
|
84
|
-
| `markPaid` | Mark payment as paid |
|
|
85
|
-
| `charge` | Charge the payment |
|
|
86
|
-
|
|
87
|
-
#### Deliveries (`orders.deliveries`)
|
|
88
|
-
| Method | Description |
|
|
89
|
-
|--------|-------------|
|
|
90
|
-
| `findDelivery` | Find order delivery |
|
|
91
|
-
| `create` | Create delivery for order |
|
|
92
|
-
| `markDelivered` | Mark as delivered |
|
|
93
|
-
| `send` | Trigger delivery |
|
|
94
|
-
|
|
95
|
-
#### Discounts (`orders.discounts`)
|
|
96
|
-
| Method | Description |
|
|
97
|
-
|--------|-------------|
|
|
98
|
-
| `findDiscounts` | Find order discounts |
|
|
99
|
-
| `create` | Add discount to order |
|
|
100
|
-
| `delete` | Remove discount |
|
|
101
|
-
|
|
102
|
-
### Constants
|
|
103
|
-
|
|
104
|
-
| Export | Description |
|
|
105
|
-
|--------|-------------|
|
|
106
|
-
| `OrderStatus` | Order status values (OPEN, PENDING, CONFIRMED, FULFILLED, REJECTED) |
|
|
107
|
-
|
|
108
|
-
### Settings
|
|
109
|
-
|
|
110
|
-
| Export | Description |
|
|
111
|
-
|--------|-------------|
|
|
112
|
-
| `ordersSettings` | Access order module settings |
|
|
113
|
-
|
|
114
|
-
### Types
|
|
115
|
-
|
|
116
|
-
| Export | Description |
|
|
117
|
-
|--------|-------------|
|
|
118
|
-
| `Order` | Order document type |
|
|
119
|
-
| `OrderPosition` | Position document type |
|
|
120
|
-
| `OrderPayment` | Payment document type |
|
|
121
|
-
| `OrderDelivery` | Delivery document type |
|
|
122
|
-
| `OrderDiscount` | Discount document type |
|
|
123
|
-
| `OrdersModule` | Module interface type |
|
|
124
|
-
|
|
125
|
-
## Events
|
|
25
|
+
Order positions, payments, deliveries, discounts, and statistics are exposed through the corresponding submodules. Use `unchainedAPI.services.orders` or the GraphQL API for cart operations and checkout so pricing, validation, payment, and delivery are coordinated.
|
|
126
26
|
|
|
127
|
-
|
|
128
|
-
|-------|-------------|
|
|
129
|
-
| `ORDER_CREATE` | Order created |
|
|
130
|
-
| `ORDER_UPDATE` | Order updated |
|
|
131
|
-
| `ORDER_REMOVE` | Order deleted |
|
|
132
|
-
| `ORDER_CHECKOUT` | Order checked out |
|
|
133
|
-
| `ORDER_CONFIRMED` | Order confirmed |
|
|
134
|
-
| `ORDER_REJECTED` | Order rejected |
|
|
135
|
-
| `ORDER_FULFILLED` | Order fulfilled |
|
|
27
|
+
See the [module guide](https://docs.unchained.shop/platform-configuration/modules/orders), [checkout guide](https://docs.unchained.shop/guides/checkout-implementation), [public exports](src/orders-index.ts), and [module implementation](src/module/configureOrdersModule.ts).
|
|
136
28
|
|
|
137
29
|
## License
|
|
138
30
|
|
|
@@ -8,7 +8,7 @@ export const OrderDeliveriesCollection = async (db) => {
|
|
|
8
8
|
const OrderDeliveries = db.collection('order_deliveries');
|
|
9
9
|
await buildDbIndexes(OrderDeliveries, [
|
|
10
10
|
{ index: { orderId: 1 } },
|
|
11
|
-
{ index: {
|
|
11
|
+
{ index: { deliveryProviderId: 1 } },
|
|
12
12
|
]);
|
|
13
13
|
return OrderDeliveries;
|
|
14
14
|
};
|
|
@@ -2,8 +2,8 @@ import { mongodb, buildDbIndexes } from '@unchainedshop/mongodb';
|
|
|
2
2
|
export const OrderDiscountsCollection = async (db) => {
|
|
3
3
|
const OrderDiscounts = db.collection('order_discounts');
|
|
4
4
|
await buildDbIndexes(OrderDiscounts, [
|
|
5
|
-
{ index: { orderId: 1 } },
|
|
6
|
-
{ index: {
|
|
5
|
+
{ index: { orderId: 1, created: 1 } },
|
|
6
|
+
{ index: { code: 1 }, options: { sparse: true } },
|
|
7
7
|
]);
|
|
8
8
|
return OrderDiscounts;
|
|
9
9
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { mongodb } from '@unchainedshop/mongodb';
|
|
2
|
+
export interface OrderLock {
|
|
3
|
+
key: string;
|
|
4
|
+
uniqueValue: string;
|
|
5
|
+
expireAt: Date;
|
|
6
|
+
}
|
|
7
|
+
export declare const OrderLocksCollection: (db: mongodb.Db) => Promise<mongodb.Collection<OrderLock>>;
|
|
8
|
+
export declare const acquireLock: (OrderLocks: mongodb.Collection<OrderLock>, key: string, ttl: number) => Promise<{
|
|
9
|
+
release: () => Promise<void>;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { mongodb, buildDbIndexes } from '@unchainedshop/mongodb';
|
|
2
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
3
|
+
const DUPLICATE_KEY = 11000;
|
|
4
|
+
const LOCK_RETRIES = 10;
|
|
5
|
+
const LOCK_RETRY_DELAY_MS = 200;
|
|
6
|
+
export const OrderLocksCollection = async (db) => {
|
|
7
|
+
const OrderLocks = db.collection('locco-locks');
|
|
8
|
+
const success = await buildDbIndexes(OrderLocks, [
|
|
9
|
+
{ index: { key: 1 }, options: { unique: true } },
|
|
10
|
+
{ index: { expireAt: 1 }, options: { expireAfterSeconds: 0 } },
|
|
11
|
+
], { rebuild: false });
|
|
12
|
+
if (!success) {
|
|
13
|
+
throw new Error('Could not ensure the unique index backing order locks, refusing to start');
|
|
14
|
+
}
|
|
15
|
+
return OrderLocks;
|
|
16
|
+
};
|
|
17
|
+
export const acquireLock = async (OrderLocks, key, ttl) => {
|
|
18
|
+
const uniqueValue = crypto.randomUUID();
|
|
19
|
+
for (let attempt = 0; attempt < LOCK_RETRIES; attempt += 1) {
|
|
20
|
+
try {
|
|
21
|
+
await OrderLocks.updateOne({ key, expireAt: { $lt: new Date() } }, { $set: { key, uniqueValue, expireAt: new Date(Date.now() + ttl) } }, { upsert: true });
|
|
22
|
+
return {
|
|
23
|
+
release: async () => {
|
|
24
|
+
try {
|
|
25
|
+
await OrderLocks.deleteOne({ key, uniqueValue, expireAt: { $gt: new Date() } });
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error?.code !== DUPLICATE_KEY)
|
|
34
|
+
throw error;
|
|
35
|
+
if (attempt < LOCK_RETRIES - 1)
|
|
36
|
+
await sleep(LOCK_RETRY_DELAY_MS);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`Could not acquire lock ${key}`);
|
|
40
|
+
};
|
|
@@ -8,9 +8,8 @@ export const OrderPaymentsCollection = async (db) => {
|
|
|
8
8
|
const OrderPayments = db.collection('order_payments');
|
|
9
9
|
await buildDbIndexes(OrderPayments, [
|
|
10
10
|
{ index: { orderId: 1 } },
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
},
|
|
11
|
+
{ index: { paymentProviderId: 1 } },
|
|
12
|
+
{ index: { transactionId: 1 }, options: { sparse: true } },
|
|
14
13
|
]);
|
|
15
14
|
return OrderPayments;
|
|
16
15
|
};
|
|
@@ -2,8 +2,8 @@ import { mongodb, buildDbIndexes } from '@unchainedshop/mongodb';
|
|
|
2
2
|
export const OrderPositionsCollection = async (db) => {
|
|
3
3
|
const OrderPositions = db.collection('order_positions');
|
|
4
4
|
await buildDbIndexes(OrderPositions, [
|
|
5
|
+
{ index: { orderId: 1, created: 1 } },
|
|
5
6
|
{ index: { productId: 1 } },
|
|
6
|
-
{ index: { orderId: 1 } },
|
|
7
7
|
]);
|
|
8
8
|
return OrderPositions;
|
|
9
9
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mongodb, buildDbIndexes,
|
|
1
|
+
import { mongodb, buildDbIndexes, } from '@unchainedshop/mongodb';
|
|
2
2
|
export const OrderStatus = {
|
|
3
3
|
PENDING: 'PENDING',
|
|
4
4
|
CONFIRMED: 'CONFIRMED',
|
|
@@ -7,36 +7,33 @@ export const OrderStatus = {
|
|
|
7
7
|
};
|
|
8
8
|
export const OrdersCollection = async (db) => {
|
|
9
9
|
const Orders = db.collection('orders');
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
status: 1,
|
|
29
|
-
},
|
|
30
|
-
name: 'order_fulltext_search',
|
|
10
|
+
await buildDbIndexes(Orders, [
|
|
11
|
+
{
|
|
12
|
+
index: {
|
|
13
|
+
_id: 'text',
|
|
14
|
+
userId: 'text',
|
|
15
|
+
orderNumber: 'text',
|
|
16
|
+
status: 'text',
|
|
17
|
+
'contact.emailAddress': 'text',
|
|
18
|
+
'contact.telNumber': 'text',
|
|
19
|
+
},
|
|
20
|
+
options: {
|
|
21
|
+
weights: {
|
|
22
|
+
_id: 8,
|
|
23
|
+
userId: 3,
|
|
24
|
+
orderNumber: 6,
|
|
25
|
+
'contact.telNumber': 5,
|
|
26
|
+
'contact.emailAddress': 4,
|
|
27
|
+
status: 1,
|
|
31
28
|
},
|
|
29
|
+
name: 'order_fulltext_search',
|
|
32
30
|
},
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
35
33
|
await buildDbIndexes(Orders, [
|
|
36
|
-
{ index: {
|
|
37
|
-
{ index: {
|
|
38
|
-
{ index: {
|
|
39
|
-
{ index: { orderNumber: 1 } },
|
|
34
|
+
{ index: { userId: 1, status: 1 } },
|
|
35
|
+
{ index: { status: 1, updated: 1 } },
|
|
36
|
+
{ index: { orderNumber: 1 }, options: { sparse: true } },
|
|
40
37
|
]);
|
|
41
38
|
return Orders;
|
|
42
39
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { normalizePhoneNumber } from '@unchainedshop/utils';
|
|
2
|
+
import { OrdersCollection } from "../orders-index.js";
|
|
3
|
+
export default function normalizeContactPhone(repository) {
|
|
4
|
+
repository?.register({
|
|
5
|
+
id: 20260625120000,
|
|
6
|
+
name: 'Normalize order.contact.telNumber to E.164 format',
|
|
7
|
+
up: async ({ logger }) => {
|
|
8
|
+
const Orders = await OrdersCollection(repository.db);
|
|
9
|
+
const orders = await Orders.find({ 'contact.telNumber': { $exists: true, $nin: [null, ''] } }, { projection: { _id: true, contact: true, billingAddress: true, countryCode: true } }).toArray();
|
|
10
|
+
let changed = 0;
|
|
11
|
+
let skipped = 0;
|
|
12
|
+
for (const order of orders) {
|
|
13
|
+
const current = order.contact?.telNumber;
|
|
14
|
+
const defaultCountry = order.billingAddress?.countryCode || order.countryCode;
|
|
15
|
+
const normalized = normalizePhoneNumber(current, defaultCountry);
|
|
16
|
+
if (!normalized || normalized === current) {
|
|
17
|
+
if (!normalized)
|
|
18
|
+
skipped += 1;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
await Orders.updateOne({ _id: order._id }, { $set: { 'contact.telNumber': normalized } });
|
|
22
|
+
changed += 1;
|
|
23
|
+
}
|
|
24
|
+
logger?.info(`Normalize order.contact.telNumber: ${changed} updated, ${skipped} left unchanged (unparseable)`);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MigrationRepository } from '@unchainedshop/mongodb';
|
|
2
|
+
import type { PricingCalculation } from '@unchainedshop/utils';
|
|
3
|
+
export declare const convertOrderCalculationToNet: (calculation: PricingCalculation[]) => PricingCalculation[];
|
|
4
|
+
export default function migrateOrderCalculationToNet(repository: MigrationRepository): void;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { OrdersCollection } from "../db/OrdersCollection.js";
|
|
2
|
+
const legacyCalculationSelector = {
|
|
3
|
+
'calculation.0': { $exists: true },
|
|
4
|
+
calculation: {
|
|
5
|
+
$not: { $elemMatch: { category: { $ne: 'TAXES' }, isNetPrice: true } },
|
|
6
|
+
},
|
|
7
|
+
};
|
|
8
|
+
export const convertOrderCalculationToNet = (calculation) => {
|
|
9
|
+
let previousBaseRow;
|
|
10
|
+
const attributed = calculation.map((row) => {
|
|
11
|
+
if (!row || !Number.isFinite(row.amount)) {
|
|
12
|
+
throw new Error('Calculation contains an invalid amount');
|
|
13
|
+
}
|
|
14
|
+
if (row.category !== 'TAXES') {
|
|
15
|
+
previousBaseRow = row;
|
|
16
|
+
return row;
|
|
17
|
+
}
|
|
18
|
+
const baseCategory = row.baseCategory || previousBaseRow?.category;
|
|
19
|
+
if (!baseCategory || baseCategory === 'TAXES') {
|
|
20
|
+
throw new Error('Cannot determine the base category of a tax row');
|
|
21
|
+
}
|
|
22
|
+
const discountId = row.discountId ??
|
|
23
|
+
(previousBaseRow?.category === baseCategory ? previousBaseRow.discountId : undefined);
|
|
24
|
+
return { ...row, baseCategory, discountId, isNetPrice: false };
|
|
25
|
+
});
|
|
26
|
+
const converted = attributed.flatMap((row) => {
|
|
27
|
+
if (row.category !== 'TAXES')
|
|
28
|
+
return [row];
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
category: row.baseCategory,
|
|
32
|
+
amount: -row.amount,
|
|
33
|
+
discountId: row.discountId,
|
|
34
|
+
isNetPrice: true,
|
|
35
|
+
meta: row.meta,
|
|
36
|
+
},
|
|
37
|
+
row,
|
|
38
|
+
];
|
|
39
|
+
});
|
|
40
|
+
const sum = (rows, category, discountId) => rows.reduce((total, row) => (category === undefined || row.category === category) &&
|
|
41
|
+
(discountId === undefined || row.discountId === discountId)
|
|
42
|
+
? total + row.amount
|
|
43
|
+
: total, 0);
|
|
44
|
+
const taxes = attributed.filter((row) => row.category === 'TAXES');
|
|
45
|
+
const taxSum = (category, discountId) => sum(category === undefined ? taxes : taxes.filter((row) => row.baseCategory === category), undefined, discountId);
|
|
46
|
+
const categories = [
|
|
47
|
+
...new Set(attributed.map((row) => (row.category === 'TAXES' ? row.baseCategory : row.category))),
|
|
48
|
+
];
|
|
49
|
+
const discountIds = [...new Set(attributed.map((row) => row.discountId))].filter((discountId) => discountId !== undefined);
|
|
50
|
+
let roundingCategory = 'ROUNDING';
|
|
51
|
+
while (categories.includes(roundingCategory))
|
|
52
|
+
roundingCategory = `_${roundingCategory}`;
|
|
53
|
+
const scopes = [
|
|
54
|
+
...categories.flatMap((category) => [
|
|
55
|
+
...discountIds.map((discountId) => ({ category, discountId })),
|
|
56
|
+
{ category, discountId: undefined },
|
|
57
|
+
]),
|
|
58
|
+
...discountIds.map((discountId) => ({ category: undefined, discountId })),
|
|
59
|
+
{ category: undefined, discountId: undefined },
|
|
60
|
+
];
|
|
61
|
+
const balances = scopes.map(({ category, discountId }) => ({
|
|
62
|
+
category,
|
|
63
|
+
discountId,
|
|
64
|
+
amount: sum(attributed, category, discountId) - taxSum(category, discountId),
|
|
65
|
+
}));
|
|
66
|
+
for (const { category, discountId, amount } of balances) {
|
|
67
|
+
const difference = amount - sum(converted, category, discountId);
|
|
68
|
+
if (!Number.isFinite(amount) || !Number.isFinite(difference)) {
|
|
69
|
+
throw new Error('Calculation contains an invalid balance');
|
|
70
|
+
}
|
|
71
|
+
if (difference) {
|
|
72
|
+
converted.push({
|
|
73
|
+
category: category ?? roundingCategory,
|
|
74
|
+
discountId,
|
|
75
|
+
amount: difference,
|
|
76
|
+
isNetPrice: true,
|
|
77
|
+
meta: { migration: 20260907120000, reason: 'Preserve historical floating-point balance' },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const { category, discountId, amount } of balances) {
|
|
82
|
+
if (sum(converted, category, discountId) !== amount) {
|
|
83
|
+
throw new Error(`Cannot preserve historical balance for ${category ?? 'order'} ${discountId ?? ''}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const discountId of discountIds) {
|
|
87
|
+
const before = sum(attributed, 'DISCOUNTS', discountId);
|
|
88
|
+
const after = sum(converted, 'DISCOUNTS', discountId) + taxSum('DISCOUNTS', discountId);
|
|
89
|
+
if (Math.round(before) !== Math.round(after) || Boolean(before) !== Boolean(after)) {
|
|
90
|
+
throw new Error(`Cannot preserve historical discount ${discountId}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return converted;
|
|
94
|
+
};
|
|
95
|
+
export default function migrateOrderCalculationToNet(repository) {
|
|
96
|
+
repository?.register({
|
|
97
|
+
id: 20260907120000,
|
|
98
|
+
name: 'Convert persisted order calculations from gross to net category balances',
|
|
99
|
+
up: async ({ logger }) => {
|
|
100
|
+
const Orders = await OrdersCollection(repository.db);
|
|
101
|
+
const projection = { _id: 1, calculation: 1 };
|
|
102
|
+
let migrated = 0;
|
|
103
|
+
for await (const order of Orders.find(legacyCalculationSelector, { projection })) {
|
|
104
|
+
let calculation;
|
|
105
|
+
try {
|
|
106
|
+
calculation = convertOrderCalculationToNet(order.calculation);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
throw new Error(`Cannot migrate order ${order._id}: ${error.message}`, { cause: error });
|
|
110
|
+
}
|
|
111
|
+
if (!calculation.some((row) => row.isNetPrice === true)) {
|
|
112
|
+
calculation = calculation.map((row) => ({ ...row, isNetPrice: true }));
|
|
113
|
+
}
|
|
114
|
+
const result = await Orders.updateOne({ _id: order._id, calculation: order.calculation }, { $set: { calculation } });
|
|
115
|
+
if (result.matchedCount) {
|
|
116
|
+
migrated += 1;
|
|
117
|
+
}
|
|
118
|
+
else if (await Orders.findOne({ _id: order._id, ...legacyCalculationSelector }, { projection })) {
|
|
119
|
+
throw new Error(`Order ${order._id} changed during migration; retry after stopping old writers`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
logger?.info(`Converted ${migrated} order calculation(s) to net category balances`);
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mongodb } from '@unchainedshop/mongodb';
|
|
2
2
|
export const buildFindSelector = ({ includeCarts, status, userId, queryString, paymentIds, deliveryIds, dateRange, orderIds, }) => {
|
|
3
3
|
const selector = {};
|
|
4
4
|
if (userId) {
|
|
@@ -30,7 +30,6 @@ export const buildFindSelector = ({ includeCarts, status, userId, queryString, p
|
|
|
30
30
|
selector.status = { $ne: null };
|
|
31
31
|
}
|
|
32
32
|
if (queryString) {
|
|
33
|
-
assertDocumentDBCompatMode();
|
|
34
33
|
selector.$text = { $search: queryString };
|
|
35
34
|
}
|
|
36
35
|
return selector;
|
|
@@ -8,6 +8,9 @@ export declare const configureOrderDeliveriesModule: ({ OrderDeliveries, }: {
|
|
|
8
8
|
findDelivery: ({ orderDeliveryId }: {
|
|
9
9
|
orderDeliveryId: string;
|
|
10
10
|
}, options?: mongodb.FindOptions) => Promise<OrderDelivery | null>;
|
|
11
|
+
findDeliveries: ({ orderDeliveryIds }: {
|
|
12
|
+
orderDeliveryIds: string[];
|
|
13
|
+
}, options?: mongodb.FindOptions) => Promise<OrderDelivery[]>;
|
|
11
14
|
findDeliveryByProvidersId: ({ deliveryProviderIds }: {
|
|
12
15
|
deliveryProviderIds: string[];
|
|
13
16
|
}, options?: mongodb.FindOptions) => Promise<OrderDelivery[]>;
|
|
@@ -32,6 +32,11 @@ export const configureOrderDeliveriesModule = ({ OrderDeliveries, }) => {
|
|
|
32
32
|
findDelivery: async ({ orderDeliveryId }, options) => {
|
|
33
33
|
return OrderDeliveries.findOne(buildFindByIdSelector(orderDeliveryId), options);
|
|
34
34
|
},
|
|
35
|
+
findDeliveries: async ({ orderDeliveryIds }, options) => {
|
|
36
|
+
if (!orderDeliveryIds?.length)
|
|
37
|
+
return [];
|
|
38
|
+
return OrderDeliveries.find({ _id: { $in: orderDeliveryIds } }, options).toArray();
|
|
39
|
+
},
|
|
35
40
|
findDeliveryByProvidersId: async ({ deliveryProviderIds }, options) => {
|
|
36
41
|
if (!deliveryProviderIds?.length)
|
|
37
42
|
return [];
|
|
@@ -7,8 +7,10 @@ export declare const configureOrderDiscountsModule: ({ OrderDiscounts, }: {
|
|
|
7
7
|
findOrderDiscount: ({ discountId }: {
|
|
8
8
|
discountId: string;
|
|
9
9
|
}, options?: mongodb.FindOptions) => Promise<OrderDiscount | null>;
|
|
10
|
-
findOrderDiscounts: (
|
|
10
|
+
findOrderDiscounts: (query: {
|
|
11
11
|
orderId: string;
|
|
12
|
+
} | {
|
|
13
|
+
orderIds: string[];
|
|
12
14
|
}) => Promise<OrderDiscount[]>;
|
|
13
15
|
create: (doc: Omit<OrderDiscount, "_id" | "created" | "trigger"> & Pick<Partial<OrderDiscount>, "_id" | "created" | "trigger">) => Promise<OrderDiscount>;
|
|
14
16
|
delete: (orderDiscountId: string) => Promise<mongodb.WithId<OrderDiscount> | null>;
|
|
@@ -13,8 +13,11 @@ export const configureOrderDiscountsModule = ({ OrderDiscounts, }) => {
|
|
|
13
13
|
findOrderDiscount: async ({ discountId }, options) => {
|
|
14
14
|
return OrderDiscounts.findOne(buildFindOrderDiscountByIdSelector(discountId), options);
|
|
15
15
|
},
|
|
16
|
-
findOrderDiscounts: async (
|
|
17
|
-
|
|
16
|
+
findOrderDiscounts: async (query) => {
|
|
17
|
+
if ('orderIds' in query && !query.orderIds.length)
|
|
18
|
+
return [];
|
|
19
|
+
const orderId = 'orderIds' in query ? { $in: query.orderIds } : query.orderId;
|
|
20
|
+
const discounts = OrderDiscounts.find({ orderId }, { sort: { created: 1, _id: 1 } });
|
|
18
21
|
return discounts.toArray();
|
|
19
22
|
},
|
|
20
23
|
create: async (doc) => {
|
|
@@ -8,6 +8,9 @@ export declare const configureOrderPaymentsModule: ({ OrderPayments, }: {
|
|
|
8
8
|
findOrderPayment: ({ orderPaymentId, }: {
|
|
9
9
|
orderPaymentId: string;
|
|
10
10
|
}, options?: mongodb.FindOptions) => Promise<OrderPayment | null>;
|
|
11
|
+
findOrderPayments: ({ orderPaymentIds }: {
|
|
12
|
+
orderPaymentIds: string[];
|
|
13
|
+
}, options?: mongodb.FindOptions) => Promise<OrderPayment[]>;
|
|
11
14
|
findOrderPaymentsByProviderIds: ({ paymentProviderIds, }: {
|
|
12
15
|
paymentProviderIds: string[];
|
|
13
16
|
}, options?: mongodb.FindOptions) => Promise<OrderPayment[]>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { emit, registerEvents } from '@unchainedshop/events';
|
|
2
2
|
import { generateDbFilterById, generateDbObjectId, mongodb } from '@unchainedshop/mongodb';
|
|
3
3
|
import { OrderPaymentStatus } from "../db/OrderPaymentsCollection.js";
|
|
4
|
-
const ORDER_PAYMENT_EVENTS = ['ORDER_UPDATE_PAYMENT', '
|
|
4
|
+
const ORDER_PAYMENT_EVENTS = ['ORDER_UPDATE_PAYMENT', 'ORDER_PAY'];
|
|
5
5
|
export const buildFindOrderPaymentByIdSelector = (orderPaymentId) => generateDbFilterById(orderPaymentId);
|
|
6
6
|
export const configureOrderPaymentsModule = ({ OrderPayments, }) => {
|
|
7
7
|
registerEvents(ORDER_PAYMENT_EVENTS);
|
|
@@ -35,6 +35,11 @@ export const configureOrderPaymentsModule = ({ OrderPayments, }) => {
|
|
|
35
35
|
findOrderPayment: async ({ orderPaymentId, }, options) => {
|
|
36
36
|
return OrderPayments.findOne(buildFindOrderPaymentByIdSelector(orderPaymentId), options);
|
|
37
37
|
},
|
|
38
|
+
findOrderPayments: async ({ orderPaymentIds }, options) => {
|
|
39
|
+
if (!orderPaymentIds?.length)
|
|
40
|
+
return [];
|
|
41
|
+
return OrderPayments.find({ _id: { $in: orderPaymentIds } }, options).toArray();
|
|
42
|
+
},
|
|
38
43
|
findOrderPaymentsByProviderIds: async ({ paymentProviderIds, }, options) => {
|
|
39
44
|
if (!paymentProviderIds?.length)
|
|
40
45
|
return [];
|
|
@@ -23,8 +23,10 @@ export declare const configureOrderPositionsModule: ({ OrderPositions, }: {
|
|
|
23
23
|
findOrderPosition: ({ itemId }: {
|
|
24
24
|
itemId: string;
|
|
25
25
|
}, options?: mongodb.FindOptions) => Promise<OrderPosition | null>;
|
|
26
|
-
findOrderPositions: (
|
|
26
|
+
findOrderPositions: (query: {
|
|
27
27
|
orderId: string;
|
|
28
|
+
} | {
|
|
29
|
+
orderIds: string[];
|
|
28
30
|
}) => Promise<OrderPosition[]>;
|
|
29
31
|
delete: (orderPositionId: string) => Promise<mongodb.WithId<OrderPosition> | null>;
|
|
30
32
|
removePositions: ({ orderId }: {
|
|
@@ -13,8 +13,11 @@ export const configureOrderPositionsModule = ({ OrderPositions, }) => {
|
|
|
13
13
|
findOrderPosition: async ({ itemId }, options) => {
|
|
14
14
|
return OrderPositions.findOne(buildFindOrderPositionByIdSelector(itemId), options);
|
|
15
15
|
},
|
|
16
|
-
findOrderPositions: async (
|
|
17
|
-
|
|
16
|
+
findOrderPositions: async (query) => {
|
|
17
|
+
if ('orderIds' in query && !query.orderIds.length)
|
|
18
|
+
return [];
|
|
19
|
+
const orderId = 'orderIds' in query ? { $in: query.orderIds } : query.orderId;
|
|
20
|
+
const positions = OrderPositions.find({ orderId, quantity: { $gt: 0 } }, { sort: { created: 1, _id: 1 } });
|
|
18
21
|
return positions.toArray();
|
|
19
22
|
},
|
|
20
23
|
delete: async (orderPositionId) => {
|
|
@@ -5,7 +5,7 @@ export declare const configureOrderModuleMutations: ({ Orders, OrderPositions, }
|
|
|
5
5
|
Orders: mongodb.Collection<Order>;
|
|
6
6
|
OrderPositions: mongodb.Collection<OrderPosition>;
|
|
7
7
|
}) => {
|
|
8
|
-
create: ({ userId, orderNumber, currencyCode, countryCode, billingAddress, contact, context, }: {
|
|
8
|
+
create: ({ userId, orderNumber, currencyCode, countryCode, billingAddress, contact, originEnrollmentId, context, }: {
|
|
9
9
|
userId: string;
|
|
10
10
|
billingAddress?: Address;
|
|
11
11
|
contact?: Contact;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { emit, registerEvents } from '@unchainedshop/events';
|
|
2
|
+
import { normalizePhoneNumber } from '@unchainedshop/utils';
|
|
2
3
|
import { generateDbFilterById, generateDbObjectId, mongodb, } from '@unchainedshop/mongodb';
|
|
3
4
|
import { OrderStatus } from "../db/OrdersCollection.js";
|
|
4
5
|
const ORDER_EVENTS = [
|
|
@@ -10,20 +11,29 @@ const ORDER_EVENTS = [
|
|
|
10
11
|
];
|
|
11
12
|
export const configureOrderModuleMutations = ({ Orders, OrderPositions, }) => {
|
|
12
13
|
registerEvents(ORDER_EVENTS);
|
|
14
|
+
const normalizeContactPhone = (contact, defaultCountry) => {
|
|
15
|
+
if (!contact?.telNumber)
|
|
16
|
+
return contact;
|
|
17
|
+
return {
|
|
18
|
+
...contact,
|
|
19
|
+
telNumber: normalizePhoneNumber(contact.telNumber, defaultCountry) || contact.telNumber,
|
|
20
|
+
};
|
|
21
|
+
};
|
|
13
22
|
return {
|
|
14
|
-
create: async ({ userId, orderNumber, currencyCode, countryCode, billingAddress, contact, context, }) => {
|
|
23
|
+
create: async ({ userId, orderNumber, currencyCode, countryCode, billingAddress, contact, originEnrollmentId, context, }) => {
|
|
15
24
|
const { insertedId: orderId } = await Orders.insertOne({
|
|
16
25
|
_id: generateDbObjectId(),
|
|
17
26
|
created: new Date(),
|
|
18
27
|
status: null,
|
|
19
28
|
billingAddress,
|
|
20
|
-
contact,
|
|
29
|
+
contact: normalizeContactPhone(contact, billingAddress?.countryCode || countryCode),
|
|
21
30
|
userId,
|
|
22
31
|
currencyCode,
|
|
23
32
|
countryCode,
|
|
24
33
|
calculation: [],
|
|
25
34
|
log: [],
|
|
26
35
|
orderNumber,
|
|
36
|
+
originEnrollmentId,
|
|
27
37
|
context: context || {},
|
|
28
38
|
});
|
|
29
39
|
const order = (await Orders.findOne(generateDbFilterById(orderId), {}));
|
|
@@ -66,9 +76,13 @@ export const configureOrderModuleMutations = ({ Orders, OrderPositions, }) => {
|
|
|
66
76
|
},
|
|
67
77
|
updateContact: async (orderId, contact) => {
|
|
68
78
|
const selector = generateDbFilterById(orderId);
|
|
79
|
+
const existing = await Orders.findOne(selector, {
|
|
80
|
+
projection: { billingAddress: true, countryCode: true },
|
|
81
|
+
});
|
|
82
|
+
const defaultCountry = existing?.billingAddress?.countryCode || existing?.countryCode;
|
|
69
83
|
const order = await Orders.findOneAndUpdate(selector, {
|
|
70
84
|
$set: {
|
|
71
|
-
contact,
|
|
85
|
+
contact: normalizeContactPhone(contact, defaultCountry),
|
|
72
86
|
updated: new Date(),
|
|
73
87
|
},
|
|
74
88
|
}, { returnDocument: 'after' });
|
|
@@ -113,7 +127,14 @@ export const configureOrderModuleMutations = ({ Orders, OrderPositions, }) => {
|
|
|
113
127
|
$set.billingAddress = updates.billingAddress;
|
|
114
128
|
}
|
|
115
129
|
if (updates.contact) {
|
|
116
|
-
|
|
130
|
+
let defaultCountry = updates.billingAddress?.countryCode;
|
|
131
|
+
if (!defaultCountry && updates.contact.telNumber) {
|
|
132
|
+
const existing = await Orders.findOne(generateDbFilterById(orderId), {
|
|
133
|
+
projection: { billingAddress: true, countryCode: true },
|
|
134
|
+
});
|
|
135
|
+
defaultCountry = existing?.billingAddress?.countryCode || existing?.countryCode;
|
|
136
|
+
}
|
|
137
|
+
$set.contact = normalizeContactPhone(updates.contact, defaultCountry);
|
|
117
138
|
}
|
|
118
139
|
if (updates.meta && Object.keys(updates.meta).length > 0) {
|
|
119
140
|
const contextSetters = Object.fromEntries(Object.entries(updates.meta).map(([key, value]) => [`context.${key}`, value]));
|
|
@@ -48,12 +48,16 @@ export declare const configureOrdersModuleQueries: ({ Orders }: {
|
|
|
48
48
|
orderNumber?: string;
|
|
49
49
|
userId: string;
|
|
50
50
|
}) => Promise<mongodb.WithId<Order> | null>;
|
|
51
|
+
findCarts: ({ userIds }: {
|
|
52
|
+
userIds: string[];
|
|
53
|
+
}, options?: mongodb.FindOptions) => Promise<Order[]>;
|
|
51
54
|
count: (query: OrderQuery) => Promise<number>;
|
|
52
55
|
findOrder: ({ orderId, orderNumber, }: {
|
|
53
56
|
orderId?: string;
|
|
54
57
|
orderNumber?: string;
|
|
55
58
|
}, options?: mongodb.FindOptions) => Promise<Order | null>;
|
|
56
|
-
|
|
59
|
+
findCartUserIds: () => Promise<string[]>;
|
|
60
|
+
findCartIdsToInvalidate: (maxAgeDays?: number) => Promise<string[]>;
|
|
57
61
|
findOrders: ({ limit, offset, queryString, sort, ...query }: OrderQuery & {
|
|
58
62
|
limit?: number;
|
|
59
63
|
offset?: number;
|
|
@@ -29,10 +29,14 @@ export const configureOrdersModuleQueries = ({ Orders }) => {
|
|
|
29
29
|
const options = {
|
|
30
30
|
sort: {
|
|
31
31
|
updated: -1,
|
|
32
|
+
_id: -1,
|
|
32
33
|
},
|
|
33
34
|
};
|
|
34
35
|
return Orders.findOne(selector, options);
|
|
35
36
|
},
|
|
37
|
+
findCarts: async ({ userIds }, options) => {
|
|
38
|
+
return Orders.find({ status: { $eq: null }, userId: { $in: userIds } }, { sort: { updated: -1, _id: -1 }, ...options }).toArray();
|
|
39
|
+
},
|
|
36
40
|
count: async (query) => {
|
|
37
41
|
const orderCount = await Orders.countDocuments(buildFindSelector(query));
|
|
38
42
|
return orderCount;
|
|
@@ -41,14 +45,16 @@ export const configureOrdersModuleQueries = ({ Orders }) => {
|
|
|
41
45
|
const selector = orderId ? generateDbFilterById(orderId) : { orderNumber };
|
|
42
46
|
return Orders.findOne(selector, options);
|
|
43
47
|
},
|
|
44
|
-
|
|
48
|
+
findCartUserIds: async () => {
|
|
49
|
+
return Orders.distinct('userId', { status: { $eq: null } });
|
|
50
|
+
},
|
|
51
|
+
findCartIdsToInvalidate: async (maxAgeDays = 30) => {
|
|
45
52
|
const ONE_DAY_IN_MILLISECONDS = 86400000;
|
|
46
53
|
const minValidDate = new Date(new Date().getTime() - maxAgeDays * ONE_DAY_IN_MILLISECONDS);
|
|
47
|
-
|
|
54
|
+
return Orders.distinct('_id', {
|
|
48
55
|
status: { $eq: null },
|
|
49
56
|
updated: { $gte: minValidDate },
|
|
50
|
-
})
|
|
51
|
-
return orders;
|
|
57
|
+
});
|
|
52
58
|
},
|
|
53
59
|
findOrders: async ({ limit, offset, queryString, sort, ...query }, options) => {
|
|
54
60
|
const defaultSortOption = [{ key: 'created', value: SortDirection.DESC }];
|
|
@@ -60,6 +66,7 @@ export const configureOrdersModuleQueries = ({ Orders }) => {
|
|
|
60
66
|
const selector = buildFindSelector({ queryString, ...query });
|
|
61
67
|
if (queryString) {
|
|
62
68
|
return Orders.find(selector, {
|
|
69
|
+
...findOptions,
|
|
63
70
|
...options,
|
|
64
71
|
projection: { score: { $meta: 'textScore' } },
|
|
65
72
|
sort: { score: { $meta: 'textScore' } },
|
|
@@ -154,19 +161,26 @@ export const configureOrdersModuleQueries = ({ Orders }) => {
|
|
|
154
161
|
created: 1,
|
|
155
162
|
currencyCode: 1,
|
|
156
163
|
itemAmount: {
|
|
157
|
-
$
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
$
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
$reduce: {
|
|
165
|
+
input: {
|
|
166
|
+
$filter: {
|
|
167
|
+
input: { $ifNull: ['$calculation', []] },
|
|
168
|
+
as: 'c',
|
|
169
|
+
cond: {
|
|
170
|
+
$or: [
|
|
171
|
+
{ $eq: ['$$c.category', 'ITEMS'] },
|
|
172
|
+
{
|
|
173
|
+
$and: [
|
|
174
|
+
{ $eq: ['$$c.category', 'TAXES'] },
|
|
175
|
+
{ $eq: ['$$c.baseCategory', 'ITEMS'] },
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
166
179
|
},
|
|
167
180
|
},
|
|
168
181
|
},
|
|
169
|
-
|
|
182
|
+
initialValue: 0,
|
|
183
|
+
in: { $add: ['$$value', { $ifNull: ['$$this.amount', 0] }] },
|
|
170
184
|
},
|
|
171
185
|
},
|
|
172
186
|
},
|
|
@@ -6,6 +6,9 @@ export declare const configureOrdersModule: ({ db, migrationRepository, options:
|
|
|
6
6
|
findDelivery: ({ orderDeliveryId }: {
|
|
7
7
|
orderDeliveryId: string;
|
|
8
8
|
}, options?: mongodb.FindOptions) => Promise<import("../db/OrderDeliveriesCollection.ts").OrderDelivery | null>;
|
|
9
|
+
findDeliveries: ({ orderDeliveryIds }: {
|
|
10
|
+
orderDeliveryIds: string[];
|
|
11
|
+
}, options?: mongodb.FindOptions) => Promise<import("../db/OrderDeliveriesCollection.ts").OrderDelivery[]>;
|
|
9
12
|
findDeliveryByProvidersId: ({ deliveryProviderIds }: {
|
|
10
13
|
deliveryProviderIds: string[];
|
|
11
14
|
}, options?: mongodb.FindOptions) => Promise<import("../db/OrderDeliveriesCollection.ts").OrderDelivery[]>;
|
|
@@ -25,8 +28,10 @@ export declare const configureOrdersModule: ({ db, migrationRepository, options:
|
|
|
25
28
|
findOrderDiscount: ({ discountId }: {
|
|
26
29
|
discountId: string;
|
|
27
30
|
}, options?: mongodb.FindOptions) => Promise<import("../db/OrderDiscountsCollection.ts").OrderDiscount | null>;
|
|
28
|
-
findOrderDiscounts: (
|
|
31
|
+
findOrderDiscounts: (query: {
|
|
29
32
|
orderId: string;
|
|
33
|
+
} | {
|
|
34
|
+
orderIds: string[];
|
|
30
35
|
}) => Promise<import("../db/OrderDiscountsCollection.ts").OrderDiscount[]>;
|
|
31
36
|
create: (doc: Omit<import("../db/OrderDiscountsCollection.ts").OrderDiscount, "_id" | "created" | "trigger"> & Pick<Partial<import("../db/OrderDiscountsCollection.ts").OrderDiscount>, "_id" | "created" | "trigger">) => Promise<import("../db/OrderDiscountsCollection.ts").OrderDiscount>;
|
|
32
37
|
delete: (orderDiscountId: string) => Promise<mongodb.WithId<import("../db/OrderDiscountsCollection.ts").OrderDiscount> | null>;
|
|
@@ -44,8 +49,10 @@ export declare const configureOrdersModule: ({ db, migrationRepository, options:
|
|
|
44
49
|
findOrderPosition: ({ itemId }: {
|
|
45
50
|
itemId: string;
|
|
46
51
|
}, options?: mongodb.FindOptions) => Promise<import("../db/OrderPositionsCollection.ts").OrderPosition | null>;
|
|
47
|
-
findOrderPositions: (
|
|
52
|
+
findOrderPositions: (query: {
|
|
48
53
|
orderId: string;
|
|
54
|
+
} | {
|
|
55
|
+
orderIds: string[];
|
|
49
56
|
}) => Promise<import("../db/OrderPositionsCollection.ts").OrderPosition[]>;
|
|
50
57
|
delete: (orderPositionId: string) => Promise<mongodb.WithId<import("../db/OrderPositionsCollection.ts").OrderPosition> | null>;
|
|
51
58
|
removePositions: ({ orderId }: {
|
|
@@ -84,6 +91,9 @@ export declare const configureOrdersModule: ({ db, migrationRepository, options:
|
|
|
84
91
|
findOrderPayment: ({ orderPaymentId, }: {
|
|
85
92
|
orderPaymentId: string;
|
|
86
93
|
}, options?: mongodb.FindOptions) => Promise<import("../db/OrderPaymentsCollection.ts").OrderPayment | null>;
|
|
94
|
+
findOrderPayments: ({ orderPaymentIds }: {
|
|
95
|
+
orderPaymentIds: string[];
|
|
96
|
+
}, options?: mongodb.FindOptions) => Promise<import("../db/OrderPaymentsCollection.ts").OrderPayment[]>;
|
|
87
97
|
findOrderPaymentsByProviderIds: ({ paymentProviderIds, }: {
|
|
88
98
|
paymentProviderIds: string[];
|
|
89
99
|
}, options?: mongodb.FindOptions) => Promise<import("../db/OrderPaymentsCollection.ts").OrderPayment[]>;
|
|
@@ -105,10 +115,12 @@ export declare const configureOrdersModule: ({ db, migrationRepository, options:
|
|
|
105
115
|
status: OrderStatus | null;
|
|
106
116
|
info?: string;
|
|
107
117
|
}) => Promise<mongodb.WithId<Order> | null>;
|
|
108
|
-
acquireLock: (orderId: string, identifier: string, timeout?: number) => Promise<
|
|
118
|
+
acquireLock: (orderId: string, identifier: string, timeout?: number) => Promise<{
|
|
119
|
+
release: () => Promise<void>;
|
|
120
|
+
}>;
|
|
109
121
|
setDeliveryProvider: (orderId: string, deliveryProviderId: string) => Promise<mongodb.WithId<Order> | null>;
|
|
110
122
|
setPaymentProvider: (orderId: string, paymentProviderId: string) => Promise<mongodb.WithId<Order> | null>;
|
|
111
|
-
create: ({ userId, orderNumber, currencyCode, countryCode, billingAddress, contact, context, }: {
|
|
123
|
+
create: ({ userId, orderNumber, currencyCode, countryCode, billingAddress, contact, originEnrollmentId, context, }: {
|
|
112
124
|
userId: string;
|
|
113
125
|
billingAddress?: import("@unchainedshop/mongodb").Address;
|
|
114
126
|
contact?: import("@unchainedshop/mongodb").Contact;
|
|
@@ -142,12 +154,16 @@ export declare const configureOrdersModule: ({ db, migrationRepository, options:
|
|
|
142
154
|
orderNumber?: string;
|
|
143
155
|
userId: string;
|
|
144
156
|
}) => Promise<mongodb.WithId<Order> | null>;
|
|
157
|
+
findCarts: ({ userIds }: {
|
|
158
|
+
userIds: string[];
|
|
159
|
+
}, options?: mongodb.FindOptions) => Promise<Order[]>;
|
|
145
160
|
count: (query: import("../db/OrdersCollection.ts").OrderQuery) => Promise<number>;
|
|
146
161
|
findOrder: ({ orderId, orderNumber, }: {
|
|
147
162
|
orderId?: string;
|
|
148
163
|
orderNumber?: string;
|
|
149
164
|
}, options?: mongodb.FindOptions) => Promise<Order | null>;
|
|
150
|
-
|
|
165
|
+
findCartUserIds: () => Promise<string[]>;
|
|
166
|
+
findCartIdsToInvalidate: (maxAgeDays?: number) => Promise<string[]>;
|
|
151
167
|
findOrders: ({ limit, offset, queryString, sort, ...query }: import("../db/OrdersCollection.ts").OrderQuery & {
|
|
152
168
|
limit?: number;
|
|
153
169
|
offset?: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Locker, MongoAdapter } from '@kontsedal/locco';
|
|
2
1
|
import { generateDbFilterById, mongodb } from '@unchainedshop/mongodb';
|
|
3
2
|
import { OrderDeliveriesCollection } from "../db/OrderDeliveriesCollection.js";
|
|
3
|
+
import { OrderLocksCollection, acquireLock } from "../db/OrderLocksCollection.js";
|
|
4
4
|
import { OrderDiscountsCollection } from "../db/OrderDiscountsCollection.js";
|
|
5
5
|
import { OrderPaymentsCollection } from "../db/OrderPaymentsCollection.js";
|
|
6
6
|
import { OrderPositionsCollection } from "../db/OrderPositionsCollection.js";
|
|
@@ -14,6 +14,8 @@ 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
|
+
import normalizeOrderContactPhone from "../migrations/20260625120000-normalize-contact-phone.js";
|
|
18
|
+
import migrateOrderCalculationToNet from "../migrations/20260907120000-order-calculation-net.js";
|
|
17
19
|
const ORDER_EVENTS = [
|
|
18
20
|
'ORDER_CHECKOUT',
|
|
19
21
|
'ORDER_CONFIRMED',
|
|
@@ -22,6 +24,8 @@ const ORDER_EVENTS = [
|
|
|
22
24
|
];
|
|
23
25
|
export const configureOrdersModule = async ({ db, migrationRepository, options: orderOptions = {}, }) => {
|
|
24
26
|
renameCurrencyCode(migrationRepository);
|
|
27
|
+
normalizeOrderContactPhone(migrationRepository);
|
|
28
|
+
migrateOrderCalculationToNet(migrationRepository);
|
|
25
29
|
registerEvents(ORDER_EVENTS);
|
|
26
30
|
ordersSettings.configureSettings(orderOptions);
|
|
27
31
|
const Orders = await OrdersCollection(db);
|
|
@@ -29,15 +33,7 @@ export const configureOrdersModule = async ({ db, migrationRepository, options:
|
|
|
29
33
|
const OrderDiscounts = await OrderDiscountsCollection(db);
|
|
30
34
|
const OrderPayments = await OrderPaymentsCollection(db);
|
|
31
35
|
const OrderPositions = await OrderPositionsCollection(db);
|
|
32
|
-
const
|
|
33
|
-
client: {
|
|
34
|
-
db: () => db,
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
const locker = new Locker({
|
|
38
|
-
adapter: mongoAdapter,
|
|
39
|
-
retrySettings: { retryDelay: 200, retryTimes: 10 },
|
|
40
|
-
});
|
|
36
|
+
const OrderLocks = await OrderLocksCollection(db);
|
|
41
37
|
const orderQueries = configureOrdersModuleQueries({ Orders });
|
|
42
38
|
const orderMutations = configureOrderModuleMutations({
|
|
43
39
|
Orders,
|
|
@@ -137,7 +133,7 @@ export const configureOrdersModule = async ({ db, migrationRepository, options:
|
|
|
137
133
|
return modificationResult.value || Orders.findOne(selector, {});
|
|
138
134
|
},
|
|
139
135
|
acquireLock: async (orderId, identifier, timeout = 5000) => {
|
|
140
|
-
return
|
|
136
|
+
return acquireLock(OrderLocks, `order:${identifier}:${orderId}`, timeout);
|
|
141
137
|
},
|
|
142
138
|
setDeliveryProvider: async (orderId, deliveryProviderId) => {
|
|
143
139
|
const delivery = await OrderDeliveries.findOne({
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/core-orders",
|
|
3
3
|
"description": "Order management module for the Unchained Engine",
|
|
4
|
-
"version": "5.0.0-alpha.
|
|
4
|
+
"version": "5.0.0-alpha.10",
|
|
5
|
+
"files": [
|
|
6
|
+
"lib"
|
|
7
|
+
],
|
|
5
8
|
"main": "lib/orders-index.js",
|
|
6
9
|
"types": "lib/orders-index.d.ts",
|
|
7
10
|
"type": "module",
|
|
@@ -35,13 +38,12 @@
|
|
|
35
38
|
},
|
|
36
39
|
"homepage": "https://github.com/unchainedshop/unchained#readme",
|
|
37
40
|
"dependencies": {
|
|
38
|
-
"@
|
|
39
|
-
"@unchainedshop/
|
|
40
|
-
"@unchainedshop/
|
|
41
|
-
"@unchainedshop/utils": "^5.0.0-alpha.1"
|
|
41
|
+
"@unchainedshop/events": "^5.0.0-alpha.10",
|
|
42
|
+
"@unchainedshop/mongodb": "^5.0.0-alpha.10",
|
|
43
|
+
"@unchainedshop/utils": "^5.0.0-alpha.10"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
44
|
-
"@types/node": "^
|
|
45
|
-
"typescript": "^
|
|
46
|
+
"@types/node": "^26.2.0",
|
|
47
|
+
"typescript": "^6.0.3"
|
|
46
48
|
}
|
|
47
49
|
}
|