@unchainedshop/core 5.0.0-alpha.4 → 5.0.0-alpha.7
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 +92 -546
- package/lib/bulk-importer/handlers/product/create.d.ts +3 -3
- package/lib/bulk-importer/handlers/product/create.js +1 -1
- package/lib/bulk-importer/handlers/product/update.d.ts +3 -3
- package/lib/bulk-importer/handlers/product/update.js +1 -1
- package/lib/directors/BaseDiscountDirector.js +4 -3
- package/lib/directors/BaseWorker.js +3 -2
- package/lib/directors/OrderPricingSheet.d.ts +1 -0
- package/lib/directors/OrderPricingSheet.js +17 -17
- package/lib/directors/WorkerDirector.d.ts +6 -0
- package/lib/directors/WorkerDirector.js +26 -14
- package/lib/services/bulkAssignProductsToAssortment.d.ts +8 -0
- package/lib/services/bulkAssignProductsToAssortment.js +11 -0
- package/lib/services/bulkDeleteUsers.d.ts +7 -0
- package/lib/services/bulkDeleteUsers.js +11 -0
- package/lib/services/bulkRemoveFilters.d.ts +7 -0
- package/lib/services/bulkRemoveFilters.js +10 -0
- package/lib/services/bulkRemoveProducts.d.ts +7 -0
- package/lib/services/bulkRemoveProducts.js +23 -0
- package/lib/services/bulkSetFilterActive.d.ts +8 -0
- package/lib/services/bulkSetFilterActive.js +12 -0
- package/lib/services/index.d.ts +14 -1
- package/lib/services/index.js +14 -1
- package/package.json +22 -22
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @unchainedshop/core
|
|
5
5
|
|
|
6
|
-
Core orchestration
|
|
6
|
+
Core orchestration for the Unchained Engine: domain modules, cross-module services, plugin directors, and bulk import/export.
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
@@ -13,602 +13,148 @@ npm install @unchainedshop/core
|
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
import { initCore, type UnchainedCore } from '@unchainedshop/core';
|
|
18
|
-
|
|
19
|
-
const unchainedCore = await initCore({
|
|
20
|
-
db,
|
|
21
|
-
migrationRepository,
|
|
22
|
-
options: {
|
|
23
|
-
// Module-specific options
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Access modules
|
|
28
|
-
const products = await unchainedCore.modules.products.findProducts({});
|
|
29
|
-
|
|
30
|
-
// Use services
|
|
31
|
-
const pricing = await unchainedCore.services.orders.pricingSheet(order);
|
|
32
|
-
|
|
33
|
-
// Bulk import
|
|
34
|
-
await unchainedCore.bulkImporter.prepare('PRODUCT');
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## API Overview
|
|
38
|
-
|
|
39
|
-
### Initialization
|
|
40
|
-
|
|
41
|
-
| Export | Description |
|
|
42
|
-
|--------|-------------|
|
|
43
|
-
| `initCore` | Initialize the core with all modules and services |
|
|
44
|
-
| `getAllAdapters` | Get all registered adapters across all directors |
|
|
45
|
-
|
|
46
|
-
### Modules
|
|
47
|
-
|
|
48
|
-
The `modules` object provides access to all core-* module APIs:
|
|
49
|
-
|
|
50
|
-
| Module | Description |
|
|
51
|
-
|--------|-------------|
|
|
52
|
-
| `products` | Product management |
|
|
53
|
-
| `assortments` | Category/assortment management |
|
|
54
|
-
| `filters` | Product filtering and search |
|
|
55
|
-
| `orders` | Order management |
|
|
56
|
-
| `users` | User management |
|
|
57
|
-
| `payment` | Payment providers |
|
|
58
|
-
| `delivery` | Delivery providers |
|
|
59
|
-
| `warehousing` | Inventory and stock |
|
|
60
|
-
| `enrollments` | Subscriptions |
|
|
61
|
-
| `quotations` | Quote management |
|
|
62
|
-
| `bookmarks` | User bookmarks |
|
|
63
|
-
| `countries` | Country management |
|
|
64
|
-
| `currencies` | Currency management |
|
|
65
|
-
| `languages` | Language management |
|
|
66
|
-
| `files` | File management |
|
|
67
|
-
| `events` | Event history |
|
|
68
|
-
| `worker` | Background jobs |
|
|
69
|
-
|
|
70
|
-
### Services
|
|
71
|
-
|
|
72
|
-
Business logic services that orchestrate multiple modules:
|
|
73
|
-
|
|
74
|
-
| Service | Description |
|
|
75
|
-
|---------|-------------|
|
|
76
|
-
| `orders` | Order pricing, checkout workflows |
|
|
77
|
-
| `products` | Product pricing calculations |
|
|
78
|
-
| `users` | User-related operations |
|
|
79
|
-
| `files` | File operations with adapters |
|
|
80
|
-
|
|
81
|
-
### Directors
|
|
82
|
-
|
|
83
|
-
Plugin directors provide extensibility through the Director/Adapter pattern. Each director manages a collection of adapters that implement specific behaviors.
|
|
84
|
-
|
|
85
|
-
| Director | Description |
|
|
86
|
-
|----------|-------------|
|
|
87
|
-
| `PaymentDirector` | Payment processing adapters |
|
|
88
|
-
| `DeliveryDirector` | Delivery/shipping adapters |
|
|
89
|
-
| `WarehousingDirector` | Inventory and stock adapters |
|
|
90
|
-
| `WorkerDirector` | Background job workers |
|
|
91
|
-
| `FilterDirector` | Product search and filtering |
|
|
92
|
-
| `EnrollmentDirector` | Subscription plan handling |
|
|
93
|
-
| `QuotationDirector` | Quote/RFQ processing |
|
|
94
|
-
| `ProductPricingDirector` | Product price calculations |
|
|
95
|
-
| `OrderPricingDirector` | Order total calculations |
|
|
96
|
-
| `DeliveryPricingDirector` | Delivery fee calculations |
|
|
97
|
-
| `PaymentPricingDirector` | Payment fee calculations |
|
|
98
|
-
| `ProductDiscountDirector` | Product-level discounts |
|
|
99
|
-
| `OrderDiscountDirector` | Order-level discounts |
|
|
100
|
-
| `MessagingDirector` | Email/notification templates |
|
|
101
|
-
|
|
102
|
-
## Director/Adapter Architecture
|
|
103
|
-
|
|
104
|
-
The Unchained Engine uses a Director/Adapter pattern for extensibility. Directors manage collections of adapters, and adapters implement specific behaviors.
|
|
105
|
-
|
|
106
|
-
### Base Classes
|
|
16
|
+
Most applications initialize core through `startPlatform()` from `@unchainedshop/platform`. Infrastructure integrations can initialize it directly with an existing database and migration repository:
|
|
107
17
|
|
|
108
18
|
```typescript
|
|
109
|
-
import {
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
- **BaseDirector**: Factory function that creates a generic director with methods to register, unregister, and retrieve adapters
|
|
113
|
-
- **BaseAdapter**: Base implementation providing logging and utility methods for all adapters
|
|
114
|
-
|
|
115
|
-
### Creating a Custom Adapter
|
|
116
|
-
|
|
117
|
-
All adapters extend from `BaseAdapter` and must implement:
|
|
19
|
+
import { initCore } from '@unchainedshop/core';
|
|
118
20
|
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
key: 'my-adapter', // Unique identifier
|
|
122
|
-
label: 'My Custom Adapter', // Human-readable label
|
|
123
|
-
version: '1.0.0', // Adapter version
|
|
124
|
-
|
|
125
|
-
// Adapter-specific methods...
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// Register with the appropriate director
|
|
129
|
-
SomeDirector.registerAdapter(MyAdapter);
|
|
21
|
+
const core = await initCore({ db, migrationRepository });
|
|
22
|
+
const products = await core.modules.products.findProducts({});
|
|
130
23
|
```
|
|
131
24
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
Manages payment processing and orchestrates payment adapters.
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
import { PaymentDirector, type IPaymentAdapter } from '@unchainedshop/core';
|
|
138
|
-
|
|
139
|
-
const MyPaymentAdapter: IPaymentAdapter = {
|
|
140
|
-
key: 'my-payment',
|
|
141
|
-
label: 'My Payment Gateway',
|
|
142
|
-
version: '1.0.0',
|
|
25
|
+
`initCore()` accepts `options` for built-in modules, `modules` containing custom `{ configure }` factories, `services` for custom services, and `bulkImporter`/`bulkExporter` handler configuration. Built-in modules are included automatically. Platform startup additionally initializes plugin modules and lifecycle hooks, accounts, templates, HTTP API, and workers.
|
|
143
26
|
|
|
144
|
-
|
|
145
|
-
return type === 'CARD';
|
|
146
|
-
},
|
|
27
|
+
## Modules and services
|
|
147
28
|
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
configurationError() { return null; },
|
|
151
|
-
isActive() { return true; },
|
|
152
|
-
isPayLaterAllowed() { return false; },
|
|
153
|
-
|
|
154
|
-
async charge() {
|
|
155
|
-
// Process payment charge
|
|
156
|
-
return { transactionId: '...' };
|
|
157
|
-
},
|
|
158
|
-
async confirm() {
|
|
159
|
-
// Confirm payment
|
|
160
|
-
return { transactionId: '...' };
|
|
161
|
-
},
|
|
162
|
-
async cancel() {
|
|
163
|
-
// Cancel payment
|
|
164
|
-
return true;
|
|
165
|
-
},
|
|
166
|
-
async register() {
|
|
167
|
-
// Register payment method
|
|
168
|
-
return { token: '...' };
|
|
169
|
-
},
|
|
170
|
-
async sign() {
|
|
171
|
-
// Sign payment request
|
|
172
|
-
return '...';
|
|
173
|
-
},
|
|
174
|
-
async validate(token) {
|
|
175
|
-
// Validate payment token
|
|
176
|
-
return true;
|
|
177
|
-
},
|
|
178
|
-
};
|
|
179
|
-
},
|
|
180
|
-
};
|
|
29
|
+
`core.modules` exposes `products`, `assortments`, `filters`, `orders`, `users`, `payment`, `delivery`, `warehousing`, `enrollments`, `quotations`, `bookmarks`, `countries`, `currencies`, `languages`, `files`, `events`, and `worker`.
|
|
181
30
|
|
|
182
|
-
|
|
183
|
-
```
|
|
31
|
+
`core.services` coordinates operations across modules, including pricing, checkout, payment, delivery, filtering, files, and enrollment processing. See [module configuration](src/modules.ts) and [service composition](src/services/index.ts) for their current interfaces.
|
|
184
32
|
|
|
185
|
-
|
|
33
|
+
## Plugin registration
|
|
186
34
|
|
|
187
|
-
|
|
35
|
+
Register built-in plugins explicitly before platform startup. Importing a plugin has no registration side effect:
|
|
188
36
|
|
|
189
37
|
```typescript
|
|
190
|
-
import {
|
|
191
|
-
|
|
192
|
-
const MyDeliveryAdapter: IDeliveryAdapter = {
|
|
193
|
-
key: 'my-delivery',
|
|
194
|
-
label: 'My Shipping Provider',
|
|
195
|
-
version: '1.0.0',
|
|
38
|
+
import { pluginRegistry } from '@unchainedshop/core';
|
|
39
|
+
import { StripePlugin } from '@unchainedshop/plugins/payment/stripe';
|
|
196
40
|
|
|
197
|
-
|
|
198
|
-
return type === 'SHIPPING';
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
actions(config, context) {
|
|
202
|
-
return {
|
|
203
|
-
configurationError() { return null; },
|
|
204
|
-
isActive() { return true; },
|
|
205
|
-
isAutoReleaseAllowed() { return false; },
|
|
206
|
-
|
|
207
|
-
async send() {
|
|
208
|
-
// Trigger delivery
|
|
209
|
-
return { trackingNumber: '...' };
|
|
210
|
-
},
|
|
211
|
-
estimatedDeliveryThroughput(warehousingTime) {
|
|
212
|
-
// Return estimated delivery time in ms
|
|
213
|
-
return 3 * 24 * 60 * 60 * 1000; // 3 days
|
|
214
|
-
},
|
|
215
|
-
async pickUpLocations() {
|
|
216
|
-
// Return available pickup locations
|
|
217
|
-
return [];
|
|
218
|
-
},
|
|
219
|
-
async pickUpLocationById(locationId) {
|
|
220
|
-
return null;
|
|
221
|
-
},
|
|
222
|
-
};
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
DeliveryDirector.registerAdapter(MyDeliveryAdapter);
|
|
41
|
+
pluginRegistry.register(StripePlugin);
|
|
227
42
|
```
|
|
228
43
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
Manages inventory and stock operations, including NFT/token support.
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
import { WarehousingDirector, type IWarehousingAdapter } from '@unchainedshop/core';
|
|
235
|
-
|
|
236
|
-
const MyWarehousingAdapter: IWarehousingAdapter = {
|
|
237
|
-
key: 'my-warehouse',
|
|
238
|
-
label: 'My Inventory System',
|
|
239
|
-
version: '1.0.0',
|
|
240
|
-
|
|
241
|
-
typeSupported(type) {
|
|
242
|
-
return type === 'PHYSICAL';
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
actions(config, context) {
|
|
246
|
-
return {
|
|
247
|
-
configurationError() { return null; },
|
|
248
|
-
isActive() { return true; },
|
|
249
|
-
|
|
250
|
-
async stock(referenceDate) {
|
|
251
|
-
// Return current stock quantity
|
|
252
|
-
return 100;
|
|
253
|
-
},
|
|
254
|
-
async productionTime(quantity) {
|
|
255
|
-
// Return production time in ms
|
|
256
|
-
return 0;
|
|
257
|
-
},
|
|
258
|
-
async commissioningTime(quantity) {
|
|
259
|
-
// Return commissioning time in ms
|
|
260
|
-
return 24 * 60 * 60 * 1000; // 1 day
|
|
261
|
-
},
|
|
262
|
-
async estimatedStock() {
|
|
263
|
-
return 100;
|
|
264
|
-
},
|
|
265
|
-
async estimatedDispatch() {
|
|
266
|
-
return new Date();
|
|
267
|
-
},
|
|
268
|
-
// For tokenized products (NFTs):
|
|
269
|
-
async tokenize() { return []; },
|
|
270
|
-
async tokenMetadata(serial, date) { return {}; },
|
|
271
|
-
async isInvalidateable(serial, date) { return false; },
|
|
272
|
-
};
|
|
273
|
-
},
|
|
274
|
-
};
|
|
44
|
+
Alternatively, call `registerBasePlugins()`, `registerAllPlugins()`, or `registerCryptoPlugins()` from the corresponding `@unchainedshop/plugins/presets/base`, `/all`, or `/crypto` subpath. Package subpaths have no file extension.
|
|
275
45
|
|
|
276
|
-
|
|
277
|
-
```
|
|
46
|
+
Directors resolve adapters from `pluginRegistry` by the `adapterType` symbol provided by each domain's base adapter. `getAllAdapters()` lists adapters across the core directors.
|
|
278
47
|
|
|
279
|
-
###
|
|
48
|
+
### Custom adapters
|
|
280
49
|
|
|
281
|
-
|
|
50
|
+
The typed `registerX()` factories both create and register an `IPlugin`. Call them before platform startup:
|
|
282
51
|
|
|
283
52
|
```typescript
|
|
284
|
-
import {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
maxParallelAllocations: 10, // Max concurrent executions
|
|
296
|
-
|
|
297
|
-
async doWork(input, unchainedAPI, workId) {
|
|
298
|
-
// Process the work item
|
|
299
|
-
const { email, subject } = input;
|
|
300
|
-
|
|
301
|
-
// Return result
|
|
302
|
-
return {
|
|
303
|
-
success: true,
|
|
304
|
-
result: { messageId: 'msg-123' },
|
|
305
|
-
};
|
|
306
|
-
},
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
WorkerDirector.registerAdapter(MyWorkerAdapter);
|
|
310
|
-
|
|
311
|
-
// Schedule recurring work
|
|
312
|
-
WorkerDirector.configureAutoscheduling({
|
|
313
|
-
type: 'MY_WORK_TYPE',
|
|
314
|
-
input: { email: 'test@example.com', subject: 'Test' },
|
|
315
|
-
schedule: '0 * * * *', // Every hour (cron syntax)
|
|
53
|
+
import {
|
|
54
|
+
registerPaymentProvider,
|
|
55
|
+
registerDeliveryProvider,
|
|
56
|
+
registerPhysicalWarehousing,
|
|
57
|
+
registerProductPricing,
|
|
58
|
+
} from '@unchainedshop/core';
|
|
59
|
+
|
|
60
|
+
registerPaymentProvider({
|
|
61
|
+
adapterId: 'manual-payment',
|
|
62
|
+
isPayLaterAllowed: true,
|
|
63
|
+
charge: false,
|
|
316
64
|
});
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### Pricing Directors
|
|
320
65
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const MyPricingAdapter: IProductPricingAdapter = {
|
|
327
|
-
key: 'my-pricing',
|
|
328
|
-
label: 'My Pricing Logic',
|
|
329
|
-
version: '1.0.0',
|
|
330
|
-
orderIndex: 10, // Lower numbers run first
|
|
66
|
+
registerDeliveryProvider({
|
|
67
|
+
adapterId: 'manual-shipping',
|
|
68
|
+
autoReleaseAllowed: false,
|
|
69
|
+
send: true,
|
|
70
|
+
});
|
|
331
71
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
72
|
+
registerPhysicalWarehousing({
|
|
73
|
+
adapterId: 'local-stock',
|
|
74
|
+
stock: 100,
|
|
75
|
+
commissioningTime: 24 * 60 * 60 * 1000,
|
|
76
|
+
});
|
|
336
77
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
category: 'BASE',
|
|
343
|
-
amount: 1000, // in smallest currency unit
|
|
344
|
-
});
|
|
345
|
-
},
|
|
346
|
-
};
|
|
78
|
+
registerProductPricing({
|
|
79
|
+
adapterId: 'fixed-price',
|
|
80
|
+
orderIndex: 0,
|
|
81
|
+
calculate: async (sheet) => {
|
|
82
|
+
sheet.addItem({ amount: 1000, isTaxable: true, isNetPrice: false });
|
|
347
83
|
},
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
ProductPricingDirector.registerAdapter(MyPricingAdapter);
|
|
84
|
+
});
|
|
351
85
|
```
|
|
352
86
|
|
|
353
|
-
|
|
87
|
+
The pricing example supplies a fixed base price in the smallest currency unit. Choose the pricing adapters appropriate to your shop so multiple adapters do not unintentionally add base prices.
|
|
354
88
|
|
|
355
|
-
|
|
89
|
+
| Concern | Factories |
|
|
90
|
+
|---------|-----------|
|
|
91
|
+
| Payment | `registerPaymentProvider`, `registerInvoicePayment` |
|
|
92
|
+
| Delivery | `registerDeliveryProvider`, `registerShippingDelivery`, `registerPickUpDelivery` |
|
|
93
|
+
| Warehousing | `registerPhysicalWarehousing`, `registerVirtualWarehousing` |
|
|
94
|
+
| Pricing | `registerProductPricing`, `registerOrderPricing`, `registerPaymentPricing`, `registerDeliveryPricing` |
|
|
95
|
+
| Discounts | `registerProductDiscount`, `registerOrderDiscount` |
|
|
96
|
+
| Filtering | `registerProductDiscoverabilityFilter`, `registerProductSearchFilter`, `registerAssortmentSearchFilter` |
|
|
97
|
+
| Files | `registerFileAdapter` |
|
|
98
|
+
| Quotations | `registerQuotation` |
|
|
99
|
+
| Enrollments | `registerEnrollment` |
|
|
100
|
+
| Workers | `registerWorker` |
|
|
356
101
|
|
|
357
|
-
|
|
358
|
-
import { OrderDiscountDirector, type IDiscountAdapter } from '@unchainedshop/core';
|
|
102
|
+
See the [factory implementations](src/factory/index.ts) for typed callback signatures. For capabilities beyond the factories, spread the relevant domain base adapter (for example `PaymentAdapter`) and its `actions()` defaults, then put the adapter in an `IPlugin` and call `pluginRegistry.register(plugin)`. This preserves the adapter's type symbol and required default methods.
|
|
359
103
|
|
|
360
|
-
|
|
361
|
-
key: 'my-discount',
|
|
362
|
-
label: 'My Discount System',
|
|
363
|
-
version: '1.0.0',
|
|
364
|
-
orderIndex: 10,
|
|
104
|
+
### Workers and recurring schedules
|
|
365
105
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
},
|
|
369
|
-
isManualRemovalAllowed() {
|
|
370
|
-
return true;
|
|
371
|
-
},
|
|
106
|
+
```typescript
|
|
107
|
+
import { registerWorker, schedule, WorkerDirector } from '@unchainedshop/core';
|
|
372
108
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
isValidForCodeTriggering(code) {
|
|
380
|
-
// Apply when code entered?
|
|
381
|
-
return code === 'PROMO10';
|
|
382
|
-
},
|
|
383
|
-
discountForPricingAdapterKey(params) {
|
|
384
|
-
// Return discount configuration for pricing adapter
|
|
385
|
-
return {
|
|
386
|
-
isNetPrice: false,
|
|
387
|
-
rate: 0.1, // 10% off
|
|
388
|
-
};
|
|
389
|
-
},
|
|
390
|
-
async reserve(code) {
|
|
391
|
-
// Reserve discount (e.g., decrement coupon balance)
|
|
392
|
-
},
|
|
393
|
-
async release() {
|
|
394
|
-
// Release reservation on order cancellation
|
|
395
|
-
},
|
|
396
|
-
};
|
|
109
|
+
registerWorker<{ message: string }, { logged: boolean }>({
|
|
110
|
+
type: 'LOG_MESSAGE',
|
|
111
|
+
maxParallelAllocations: 1,
|
|
112
|
+
process: async ({ message }) => {
|
|
113
|
+
console.log(message);
|
|
114
|
+
return { logged: true };
|
|
397
115
|
},
|
|
398
|
-
};
|
|
116
|
+
});
|
|
399
117
|
|
|
400
|
-
|
|
118
|
+
WorkerDirector.configureAutoscheduling({
|
|
119
|
+
type: 'LOG_MESSAGE',
|
|
120
|
+
input: async () => ({ message: 'Hourly check' }),
|
|
121
|
+
schedule: schedule.parse.cron('0 * * * *'),
|
|
122
|
+
});
|
|
401
123
|
```
|
|
402
124
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
Manages product filtering and search functionality.
|
|
406
|
-
|
|
407
|
-
```typescript
|
|
408
|
-
import { FilterDirector, type IFilterAdapter } from '@unchainedshop/core';
|
|
409
|
-
|
|
410
|
-
const MyFilterAdapter: IFilterAdapter = {
|
|
411
|
-
key: 'my-filter',
|
|
412
|
-
label: 'My Search Filter',
|
|
413
|
-
version: '1.0.0',
|
|
414
|
-
orderIndex: 10,
|
|
415
|
-
|
|
416
|
-
actions(context) {
|
|
417
|
-
return {
|
|
418
|
-
async aggregateProductIds(params) {
|
|
419
|
-
// Return product IDs matching filter
|
|
420
|
-
return ['product-1', 'product-2'];
|
|
421
|
-
},
|
|
422
|
-
async searchProducts(params, options) {
|
|
423
|
-
// Search products
|
|
424
|
-
return { productIds: [], totalCount: 0 };
|
|
425
|
-
},
|
|
426
|
-
async searchAssortments(params, options) {
|
|
427
|
-
// Search assortments
|
|
428
|
-
return { assortmentIds: [], totalCount: 0 };
|
|
429
|
-
},
|
|
430
|
-
transformProductSelector(selector, options) {
|
|
431
|
-
// Modify MongoDB product selector
|
|
432
|
-
return selector;
|
|
433
|
-
},
|
|
434
|
-
transformFilterSelector(selector, options) {
|
|
435
|
-
// Modify MongoDB filter selector
|
|
436
|
-
return selector;
|
|
437
|
-
},
|
|
438
|
-
transformSortStage(sort, options) {
|
|
439
|
-
// Modify MongoDB sort stage
|
|
440
|
-
return sort;
|
|
441
|
-
},
|
|
442
|
-
};
|
|
443
|
-
},
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
FilterDirector.registerAdapter(MyFilterAdapter);
|
|
447
|
-
```
|
|
125
|
+
Autoscheduling accepts parsed `ScheduleData` and an asynchronous input factory. The platform's queue managers enqueue and execute the scheduled work.
|
|
448
126
|
|
|
449
|
-
### Messaging
|
|
127
|
+
### Messaging templates
|
|
450
128
|
|
|
451
|
-
|
|
129
|
+
Messaging uses template resolvers registered separately from adapters:
|
|
452
130
|
|
|
453
131
|
```typescript
|
|
454
132
|
import { MessagingDirector } from '@unchainedshop/core';
|
|
455
133
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
to: user.email,
|
|
465
|
-
subject: `Order Confirmation #${order.orderNumber}`,
|
|
466
|
-
html: '<h1>Thank you for your order!</h1>',
|
|
467
|
-
},
|
|
134
|
+
MessagingDirector.registerTemplate('CUSTOM_NOTICE', async ({ email }) => [
|
|
135
|
+
{
|
|
136
|
+
type: 'EMAIL',
|
|
137
|
+
input: {
|
|
138
|
+
from: 'shop@example.com',
|
|
139
|
+
to: email,
|
|
140
|
+
subject: 'Shop notice',
|
|
141
|
+
text: 'Your requested notification.',
|
|
468
142
|
},
|
|
469
|
-
{
|
|
470
|
-
type: 'SMS',
|
|
471
|
-
input: {
|
|
472
|
-
to: user.phone,
|
|
473
|
-
text: `Order #${order.orderNumber} confirmed!`,
|
|
474
|
-
},
|
|
475
|
-
},
|
|
476
|
-
];
|
|
477
|
-
});
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
### Quotation Director
|
|
481
|
-
|
|
482
|
-
Handles quotation/RFQ (Request for Quote) operations.
|
|
483
|
-
|
|
484
|
-
```typescript
|
|
485
|
-
import { QuotationDirector, type IQuotationAdapter } from '@unchainedshop/core';
|
|
486
|
-
|
|
487
|
-
const MyQuotationAdapter: IQuotationAdapter = {
|
|
488
|
-
key: 'my-quotation',
|
|
489
|
-
label: 'My Quote System',
|
|
490
|
-
version: '1.0.0',
|
|
491
|
-
|
|
492
|
-
isActivatedFor(quotationContext, unchainedAPI) {
|
|
493
|
-
return true;
|
|
494
143
|
},
|
|
495
|
-
|
|
496
|
-
actions(context) {
|
|
497
|
-
return {
|
|
498
|
-
configurationError() { return null; },
|
|
499
|
-
isManualProposalRequired() { return true; },
|
|
500
|
-
isManualRequestVerificationRequired() { return false; },
|
|
501
|
-
|
|
502
|
-
async quote() {
|
|
503
|
-
// Generate quote
|
|
504
|
-
return { price: 1000, currency: 'CHF' };
|
|
505
|
-
},
|
|
506
|
-
async submitRequest(quotationContext) {
|
|
507
|
-
// Submit RFQ
|
|
508
|
-
},
|
|
509
|
-
async verifyRequest(quotationContext) {
|
|
510
|
-
// Verify RFQ
|
|
511
|
-
},
|
|
512
|
-
async rejectRequest(quotationContext) {
|
|
513
|
-
// Reject RFQ
|
|
514
|
-
},
|
|
515
|
-
transformItemConfiguration(params) {
|
|
516
|
-
return params.configuration;
|
|
517
|
-
},
|
|
518
|
-
};
|
|
519
|
-
},
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
QuotationDirector.registerAdapter(MyQuotationAdapter);
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
### Enrollment Director
|
|
526
|
-
|
|
527
|
-
Manages subscription/enrollment plans and recurring billing.
|
|
528
|
-
|
|
529
|
-
```typescript
|
|
530
|
-
import { EnrollmentDirector, type IEnrollmentAdapter } from '@unchainedshop/core';
|
|
531
|
-
|
|
532
|
-
const MyEnrollmentAdapter: IEnrollmentAdapter = {
|
|
533
|
-
key: 'my-enrollment',
|
|
534
|
-
label: 'My Subscription System',
|
|
535
|
-
version: '1.0.0',
|
|
536
|
-
|
|
537
|
-
isActivatedFor(productPlan) {
|
|
538
|
-
return productPlan.type === 'PLAN_PRODUCT';
|
|
539
|
-
},
|
|
540
|
-
|
|
541
|
-
transformOrderItemToEnrollmentPlan(orderPosition, unchainedAPI) {
|
|
542
|
-
return {
|
|
543
|
-
configuration: orderPosition.configuration,
|
|
544
|
-
};
|
|
545
|
-
},
|
|
546
|
-
|
|
547
|
-
actions(context) {
|
|
548
|
-
return {
|
|
549
|
-
async nextPeriod() {
|
|
550
|
-
// Calculate next billing period
|
|
551
|
-
return {
|
|
552
|
-
start: new Date(),
|
|
553
|
-
end: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
554
|
-
};
|
|
555
|
-
},
|
|
556
|
-
isValidForActivation() {
|
|
557
|
-
return true;
|
|
558
|
-
},
|
|
559
|
-
isOverdue() {
|
|
560
|
-
return false;
|
|
561
|
-
},
|
|
562
|
-
async configurationForOrder(period) {
|
|
563
|
-
// Return order configuration for period
|
|
564
|
-
return {};
|
|
565
|
-
},
|
|
566
|
-
};
|
|
567
|
-
},
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
EnrollmentDirector.registerAdapter(MyEnrollmentAdapter);
|
|
144
|
+
]);
|
|
571
145
|
```
|
|
572
146
|
|
|
573
|
-
|
|
147
|
+
Each returned item is work for its registered worker type, such as `EMAIL` or `TWILIO`. Template parameters depend on the caller; they are not automatically populated with order/user documents.
|
|
574
148
|
|
|
575
|
-
|
|
576
|
-
|--------|-------------|
|
|
577
|
-
| `bulkImporter.prepare` | Prepare bulk import for entity type |
|
|
578
|
-
| `bulkImporter.process` | Process prepared import data |
|
|
149
|
+
## Bulk import and export
|
|
579
150
|
|
|
580
|
-
|
|
151
|
+
`core.bulkImporter.createBulkImporter(options)` creates an import session with `validate(event)`, `prepare(event, core)`, `execute()`, and `invalidateCaches(core)`. Events carry `entity`, `operation`, and `payload`; the built-in entities are `PRODUCT`, `ASSORTMENT`, and `FILTER`.
|
|
581
152
|
|
|
582
|
-
|
|
583
|
-
|--------|-------------|
|
|
584
|
-
| `UnchainedCore` | Core instance type |
|
|
585
|
-
| `UnchainedCoreOptions` | Initialization options |
|
|
586
|
-
| `Modules` | All modules type |
|
|
587
|
-
| `Services` | All services type |
|
|
588
|
-
| `BulkImporter` | Bulk importer type |
|
|
153
|
+
For streams, use `validateEventStream(readStream)` and `pipeEventStream(readStream, importer, core)`, then execute the import and invalidate caches. See the [importer](src/bulk-importer/createBulkImporter.ts) and [exporter](src/bulk-exporter/index.ts) for the full interfaces.
|
|
589
154
|
|
|
590
|
-
##
|
|
155
|
+
## Types
|
|
591
156
|
|
|
592
|
-
|
|
593
|
-
const core = await initCore({
|
|
594
|
-
db,
|
|
595
|
-
migrationRepository,
|
|
596
|
-
modules: {
|
|
597
|
-
// Custom modules
|
|
598
|
-
},
|
|
599
|
-
services: {
|
|
600
|
-
// Custom services
|
|
601
|
-
},
|
|
602
|
-
bulkImporter: {
|
|
603
|
-
handlers: {
|
|
604
|
-
// Custom import handlers
|
|
605
|
-
},
|
|
606
|
-
},
|
|
607
|
-
options: {
|
|
608
|
-
// Module-specific options
|
|
609
|
-
},
|
|
610
|
-
});
|
|
611
|
-
```
|
|
157
|
+
The package exports `UnchainedCore`, `UnchainedCoreOptions`, `Services`, `BulkImporter`, `BulkExporter`, and `IPlugin`, along with domain adapter/context and pricing sheet types. Access the composed module type through `UnchainedCore['modules']`.
|
|
612
158
|
|
|
613
159
|
## License
|
|
614
160
|
|
|
@@ -15,7 +15,7 @@ export declare const ProductCreateSpecificationSchema: z.ZodMiniObject<{
|
|
|
15
15
|
readonly ACTIVE: "ACTIVE";
|
|
16
16
|
readonly DELETED: "DELETED";
|
|
17
17
|
}>>>;
|
|
18
|
-
published: z.ZodMiniOptional<z.ZodMiniNullable<z.
|
|
18
|
+
published: z.ZodMiniOptional<z.ZodMiniNullable<z.ZodMiniString<string>>>;
|
|
19
19
|
tags: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
|
|
20
20
|
commerce: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
21
21
|
pricing: z.ZodMiniArray<z.ZodMiniObject<{
|
|
@@ -76,7 +76,7 @@ export declare const ProductCreatePayloadSchema: z.ZodMiniObject<{
|
|
|
76
76
|
readonly ACTIVE: "ACTIVE";
|
|
77
77
|
readonly DELETED: "DELETED";
|
|
78
78
|
}>>>;
|
|
79
|
-
published: z.ZodMiniOptional<z.ZodMiniNullable<z.
|
|
79
|
+
published: z.ZodMiniOptional<z.ZodMiniNullable<z.ZodMiniString<string>>>;
|
|
80
80
|
tags: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
|
|
81
81
|
commerce: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
82
82
|
pricing: z.ZodMiniArray<z.ZodMiniObject<{
|
|
@@ -183,7 +183,7 @@ declare namespace createProduct {
|
|
|
183
183
|
readonly ACTIVE: "ACTIVE";
|
|
184
184
|
readonly DELETED: "DELETED";
|
|
185
185
|
}>>>;
|
|
186
|
-
published: z.ZodMiniOptional<z.ZodMiniNullable<z.
|
|
186
|
+
published: z.ZodMiniOptional<z.ZodMiniNullable<z.ZodMiniString<string>>>;
|
|
187
187
|
tags: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
|
|
188
188
|
commerce: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
189
189
|
pricing: z.ZodMiniArray<z.ZodMiniObject<{
|
|
@@ -8,7 +8,7 @@ export const ProductCreateSpecificationSchema = z.object({
|
|
|
8
8
|
type: z.enum(ProductType),
|
|
9
9
|
sequence: z.optional(z.number()),
|
|
10
10
|
status: z.nullish(z.enum(ProductStatus)),
|
|
11
|
-
published: z.nullish(z.
|
|
11
|
+
published: z.nullish(z.string().check(z.refine((value) => !Number.isNaN(new Date(value).getTime()), 'Invalid date'))),
|
|
12
12
|
tags: z.optional(z.array(z.string())),
|
|
13
13
|
commerce: z.optional(z.object({
|
|
14
14
|
pricing: z.array(z.object({
|
|
@@ -9,7 +9,7 @@ export declare const ProductUpdateSpecificationSchema: z.ZodMiniObject<{
|
|
|
9
9
|
readonly ACTIVE: "ACTIVE";
|
|
10
10
|
readonly DELETED: "DELETED";
|
|
11
11
|
}>>>;
|
|
12
|
-
published: z.ZodMiniOptional<z.ZodMiniNullable<z.
|
|
12
|
+
published: z.ZodMiniOptional<z.ZodMiniNullable<z.ZodMiniString<string>>>;
|
|
13
13
|
tags: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
|
|
14
14
|
commerce: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
15
15
|
pricing: z.ZodMiniArray<z.ZodMiniObject<{
|
|
@@ -64,7 +64,7 @@ export declare const ProductUpdatePayloadSchema: z.ZodMiniObject<{
|
|
|
64
64
|
readonly ACTIVE: "ACTIVE";
|
|
65
65
|
readonly DELETED: "DELETED";
|
|
66
66
|
}>>>;
|
|
67
|
-
published: z.ZodMiniOptional<z.ZodMiniNullable<z.
|
|
67
|
+
published: z.ZodMiniOptional<z.ZodMiniNullable<z.ZodMiniString<string>>>;
|
|
68
68
|
tags: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
|
|
69
69
|
commerce: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
70
70
|
pricing: z.ZodMiniArray<z.ZodMiniObject<{
|
|
@@ -165,7 +165,7 @@ declare namespace updateProduct {
|
|
|
165
165
|
readonly ACTIVE: "ACTIVE";
|
|
166
166
|
readonly DELETED: "DELETED";
|
|
167
167
|
}>>>;
|
|
168
|
-
published: z.ZodMiniOptional<z.ZodMiniNullable<z.
|
|
168
|
+
published: z.ZodMiniOptional<z.ZodMiniNullable<z.ZodMiniString<string>>>;
|
|
169
169
|
tags: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
|
|
170
170
|
commerce: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
171
171
|
pricing: z.ZodMiniArray<z.ZodMiniObject<{
|
|
@@ -8,7 +8,7 @@ export const ProductUpdateSpecificationSchema = z.object({
|
|
|
8
8
|
type: z.optional(z.string()),
|
|
9
9
|
sequence: z.optional(z.number()),
|
|
10
10
|
status: z.nullish(z.enum(ProductStatus)),
|
|
11
|
-
published: z.nullish(z.
|
|
11
|
+
published: z.nullish(z.string().check(z.refine((value) => !Number.isNaN(new Date(value).getTime()), 'Invalid date'))),
|
|
12
12
|
tags: z.optional(z.array(z.string())),
|
|
13
13
|
commerce: z.optional(z.object({
|
|
14
14
|
pricing: z.array(z.object({
|
|
@@ -11,9 +11,10 @@ export const BaseDiscountDirector = (directorName) => {
|
|
|
11
11
|
resolveDiscountAdapterFromStaticCode: async (options) => {
|
|
12
12
|
if (!context.order)
|
|
13
13
|
return null;
|
|
14
|
-
const discounts = await Promise.all(this.getAdapters()
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const discounts = await Promise.all(this.getAdapters().map(async (Adapter) => {
|
|
15
|
+
if (!(await Adapter.isManualAdditionAllowed(options?.code))) {
|
|
16
|
+
return { Adapter, isValid: false };
|
|
17
|
+
}
|
|
17
18
|
const adapter = await Adapter.actions({ context });
|
|
18
19
|
return {
|
|
19
20
|
Adapter,
|
|
@@ -48,9 +48,10 @@ export const BaseWorker = {
|
|
|
48
48
|
retries: workConfig.retries || 0,
|
|
49
49
|
};
|
|
50
50
|
if (workConfig.input) {
|
|
51
|
-
|
|
52
|
-
if (!
|
|
51
|
+
const input = await workConfig.input(workData);
|
|
52
|
+
if (!input)
|
|
53
53
|
return null;
|
|
54
|
+
workData.input = input;
|
|
54
55
|
}
|
|
55
56
|
return unchainedAPI.modules.worker.ensureOneWork(workData);
|
|
56
57
|
}));
|
|
@@ -33,5 +33,6 @@ export interface IOrderPricingSheet extends IPricingSheet<OrderPricingCalculatio
|
|
|
33
33
|
taxAmount: number;
|
|
34
34
|
meta?: any;
|
|
35
35
|
}) => void;
|
|
36
|
+
discountSum: (discountId?: string) => number;
|
|
36
37
|
}
|
|
37
38
|
export declare const OrderPricingSheet: (params: PricingSheetParams<OrderPricingCalculation>) => IOrderPricingSheet;
|
|
@@ -15,6 +15,7 @@ export const OrderPricingSheet = (params) => {
|
|
|
15
15
|
amount: taxAmount,
|
|
16
16
|
baseCategory: category,
|
|
17
17
|
discountId,
|
|
18
|
+
isNetPrice: false,
|
|
18
19
|
meta,
|
|
19
20
|
});
|
|
20
21
|
}
|
|
@@ -25,6 +26,7 @@ export const OrderPricingSheet = (params) => {
|
|
|
25
26
|
basePricingSheet.calculation.push({
|
|
26
27
|
category: OrderPricingRowCategory.Items,
|
|
27
28
|
amount,
|
|
29
|
+
isNetPrice: true,
|
|
28
30
|
meta,
|
|
29
31
|
});
|
|
30
32
|
addTaxIfAvailabile(OrderPricingRowCategory.Items, taxAmount, meta);
|
|
@@ -34,6 +36,7 @@ export const OrderPricingSheet = (params) => {
|
|
|
34
36
|
category: OrderPricingRowCategory.Discounts,
|
|
35
37
|
amount,
|
|
36
38
|
discountId,
|
|
39
|
+
isNetPrice: true,
|
|
37
40
|
meta,
|
|
38
41
|
});
|
|
39
42
|
addTaxIfAvailabile(OrderPricingRowCategory.Discounts, taxAmount, meta, discountId);
|
|
@@ -42,6 +45,7 @@ export const OrderPricingSheet = (params) => {
|
|
|
42
45
|
basePricingSheet.calculation.push({
|
|
43
46
|
category: OrderPricingRowCategory.Delivery,
|
|
44
47
|
amount,
|
|
48
|
+
isNetPrice: true,
|
|
45
49
|
meta,
|
|
46
50
|
});
|
|
47
51
|
addTaxIfAvailabile(OrderPricingRowCategory.Delivery, taxAmount, meta);
|
|
@@ -50,6 +54,7 @@ export const OrderPricingSheet = (params) => {
|
|
|
50
54
|
basePricingSheet.calculation.push({
|
|
51
55
|
category: OrderPricingRowCategory.Payment,
|
|
52
56
|
amount,
|
|
57
|
+
isNetPrice: true,
|
|
53
58
|
meta,
|
|
54
59
|
});
|
|
55
60
|
addTaxIfAvailabile(OrderPricingRowCategory.Payment, taxAmount, meta);
|
|
@@ -60,20 +65,11 @@ export const OrderPricingSheet = (params) => {
|
|
|
60
65
|
...(filter || {}),
|
|
61
66
|
});
|
|
62
67
|
},
|
|
63
|
-
|
|
64
|
-
return basePricingSheet.sum(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
-
total({ category, useNetPrice, discountId } = { useNetPrice: false }) {
|
|
70
|
-
const taxAmount = this.taxSum({ baseCategory: category, discountId });
|
|
71
|
-
const amount = this.sum({ category, discountId }) - taxAmount;
|
|
72
|
-
const netAmount = !category ? amount - taxAmount : amount;
|
|
73
|
-
return {
|
|
74
|
-
amount: Math.round(useNetPrice ? netAmount : netAmount + taxAmount),
|
|
75
|
-
currencyCode: this.currencyCode,
|
|
76
|
-
};
|
|
68
|
+
discountSum(discountId) {
|
|
69
|
+
return basePricingSheet.sum({
|
|
70
|
+
category: OrderPricingRowCategory.Discounts,
|
|
71
|
+
discountId,
|
|
72
|
+
});
|
|
77
73
|
},
|
|
78
74
|
discountPrices(explicitDiscountId) {
|
|
79
75
|
const discountIds = pricingSheet
|
|
@@ -85,17 +81,21 @@ export const OrderPricingSheet = (params) => {
|
|
|
85
81
|
.filter(Boolean);
|
|
86
82
|
return [...new Set(discountIds)]
|
|
87
83
|
.map((discountId) => {
|
|
88
|
-
const amount =
|
|
84
|
+
const amount = pricingSheet.sum({
|
|
89
85
|
category: OrderPricingRowCategory.Discounts,
|
|
90
86
|
discountId,
|
|
91
|
-
})
|
|
87
|
+
}) +
|
|
88
|
+
pricingSheet.taxSum({
|
|
89
|
+
baseCategory: OrderPricingRowCategory.Discounts,
|
|
90
|
+
discountId,
|
|
91
|
+
});
|
|
92
92
|
if (!amount) {
|
|
93
93
|
return null;
|
|
94
94
|
}
|
|
95
95
|
return {
|
|
96
96
|
discountId,
|
|
97
97
|
amount: Math.round(amount),
|
|
98
|
-
currencyCode:
|
|
98
|
+
currencyCode: pricingSheet.currencyCode,
|
|
99
99
|
};
|
|
100
100
|
})
|
|
101
101
|
.filter(Boolean);
|
|
@@ -18,6 +18,12 @@ export type IWorkerDirector = IBaseDirector<IWorkerAdapter<any, any>> & {
|
|
|
18
18
|
configureAutoscheduling: (workScheduleConfiguration: WorkScheduleConfiguration) => void;
|
|
19
19
|
getAutoSchedules: () => [string, WorkScheduleConfiguration][];
|
|
20
20
|
doWork: (work: Work, unchainedAPI: any) => Promise<WorkResult>;
|
|
21
|
+
allocateWork: (unchainedAPI: {
|
|
22
|
+
modules: Modules;
|
|
23
|
+
}, options?: {
|
|
24
|
+
types?: string[] | null;
|
|
25
|
+
worker?: string;
|
|
26
|
+
}) => Promise<Work | null>;
|
|
21
27
|
processNextWork: (unchainedAPI: {
|
|
22
28
|
modules: Modules;
|
|
23
29
|
}, workerId?: string) => Promise<Work | null>;
|
|
@@ -57,22 +57,34 @@ export const WorkerDirector = {
|
|
|
57
57
|
return errorOutput;
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
|
+
allocateWork: async (unchainedAPI, { types: requestedTypes, worker = unchainedAPI.modules.worker.workerId } = {}) => {
|
|
61
|
+
const lock = await unchainedAPI.modules.orders.acquireLock('allocation', 'worker');
|
|
62
|
+
try {
|
|
63
|
+
const adapters = WorkerDirector.getAdapters();
|
|
64
|
+
const allocationMap = await unchainedAPI.modules.worker.allocationMap();
|
|
65
|
+
const types = adapters
|
|
66
|
+
.filter((adapter) => {
|
|
67
|
+
if (requestedTypes && !requestedTypes.includes(adapter.type))
|
|
68
|
+
return false;
|
|
69
|
+
if (adapter.maxParallelAllocations &&
|
|
70
|
+
adapter.maxParallelAllocations <= allocationMap[adapter.type])
|
|
71
|
+
return false;
|
|
72
|
+
return true;
|
|
73
|
+
})
|
|
74
|
+
.map((adapter) => adapter.type);
|
|
75
|
+
return await unchainedAPI.modules.worker.allocateWork({
|
|
76
|
+
types,
|
|
77
|
+
worker,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
await lock.release();
|
|
82
|
+
}
|
|
83
|
+
},
|
|
60
84
|
processNextWork: async (unchainedAPI, workerId) => {
|
|
61
|
-
const adapters = WorkerDirector.getAdapters();
|
|
62
|
-
const allocationMap = await unchainedAPI.modules.worker.allocationMap();
|
|
63
|
-
const types = adapters
|
|
64
|
-
.filter((adapter) => {
|
|
65
|
-
if (adapter.external)
|
|
66
|
-
return false;
|
|
67
|
-
if (adapter.maxParallelAllocations &&
|
|
68
|
-
adapter.maxParallelAllocations <= allocationMap[adapter.type])
|
|
69
|
-
return false;
|
|
70
|
-
return true;
|
|
71
|
-
})
|
|
72
|
-
.map((adapter) => adapter.type);
|
|
73
85
|
const worker = workerId ?? unchainedAPI.modules.worker.workerId;
|
|
74
|
-
const work = await
|
|
75
|
-
types,
|
|
86
|
+
const work = await WorkerDirector.allocateWork(unchainedAPI, {
|
|
87
|
+
types: WorkerDirector.getActivePluginTypes({ external: false }),
|
|
76
88
|
worker,
|
|
77
89
|
});
|
|
78
90
|
if (work) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Modules } from '../modules.ts';
|
|
2
|
+
export declare function bulkAssignProductsToAssortmentService(this: Modules, { assortmentId, productIds }: {
|
|
3
|
+
assortmentId: string;
|
|
4
|
+
productIds: string[];
|
|
5
|
+
}): Promise<{
|
|
6
|
+
successIds: string[];
|
|
7
|
+
failedIds: string[];
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { executeBulkOperation } from '@unchainedshop/utils';
|
|
2
|
+
export async function bulkAssignProductsToAssortmentService({ assortmentId, productIds }) {
|
|
3
|
+
const result = await executeBulkOperation(productIds, async (productId) => {
|
|
4
|
+
if (!(await this.products.productExists({ productId })))
|
|
5
|
+
throw new Error('not-found');
|
|
6
|
+
const assignment = await this.assortments.products.create({ assortmentId, productId });
|
|
7
|
+
if (!assignment)
|
|
8
|
+
throw new Error('create-failed');
|
|
9
|
+
});
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { executeBulkOperation } from '@unchainedshop/utils';
|
|
2
|
+
import { deleteUserService } from "./deleteUser.js";
|
|
3
|
+
export async function bulkDeleteUsersService({ userIds }) {
|
|
4
|
+
return executeBulkOperation(userIds, async (userId) => {
|
|
5
|
+
if (!(await this.users.userExists({ userId })))
|
|
6
|
+
throw new Error('not-found');
|
|
7
|
+
const result = await deleteUserService.call(this, { userId });
|
|
8
|
+
if (!result)
|
|
9
|
+
throw new Error('delete-failed');
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { executeBulkOperation } from '@unchainedshop/utils';
|
|
2
|
+
import { removeFilterService } from "./removeFilter.js";
|
|
3
|
+
export async function bulkRemoveFiltersService({ filterIds }) {
|
|
4
|
+
return executeBulkOperation(filterIds, async (filterId) => {
|
|
5
|
+
const filter = await this.filters.findFilter({ filterId });
|
|
6
|
+
if (!filter)
|
|
7
|
+
throw new Error('not-found');
|
|
8
|
+
await removeFilterService.call(this, { filter });
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { executeBulkOperation } from '@unchainedshop/utils';
|
|
2
|
+
import { removeProductService } from "./removeProduct.js";
|
|
3
|
+
export async function bulkRemoveProductsService({ productIds }) {
|
|
4
|
+
return executeBulkOperation(productIds, async (productId) => {
|
|
5
|
+
if (!(await this.products.productExists({ productId })))
|
|
6
|
+
throw new Error('not-found');
|
|
7
|
+
if (await this.products.firstActiveProductBundle(productId)) {
|
|
8
|
+
throw new Error('linked-to-active-bundle');
|
|
9
|
+
}
|
|
10
|
+
if (await this.products.firstActiveProductProxy(productId)) {
|
|
11
|
+
throw new Error('linked-to-active-variation');
|
|
12
|
+
}
|
|
13
|
+
if (await this.quotations.openQuotationWithProduct({ productId })) {
|
|
14
|
+
throw new Error('linked-to-quotation');
|
|
15
|
+
}
|
|
16
|
+
if (await this.enrollments.openEnrollmentWithProduct({ productId })) {
|
|
17
|
+
throw new Error('linked-to-enrollment');
|
|
18
|
+
}
|
|
19
|
+
const result = await removeProductService.call(this, { productId });
|
|
20
|
+
if (!result)
|
|
21
|
+
throw new Error('already-deleted');
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { executeBulkOperation } from '@unchainedshop/utils';
|
|
2
|
+
import { updateFilterService } from "./updateFilter.js";
|
|
3
|
+
export async function bulkSetFilterActiveService({ filterIds, isActive }) {
|
|
4
|
+
return executeBulkOperation(filterIds, async (filterId) => {
|
|
5
|
+
const filter = await this.filters.findFilter({ filterId });
|
|
6
|
+
if (!filter)
|
|
7
|
+
throw new Error('not-found');
|
|
8
|
+
if (!(await updateFilterService.call(this, filterId, { isActive }))) {
|
|
9
|
+
throw new Error('update-failed');
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
package/lib/services/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { searchProductsService } from './searchProducts.ts';
|
|
|
24
24
|
import { calculateDiscountTotalService } from './calculateDiscountTotal.ts';
|
|
25
25
|
import { registerPaymentCredentialsService } from './registerPaymentCredentials.ts';
|
|
26
26
|
import { processOrderService } from './processOrder.ts';
|
|
27
|
+
import { processEnrollmentService } from './processEnrollment.ts';
|
|
27
28
|
import { checkoutOrderService } from './checkoutOrder.ts';
|
|
28
29
|
import { confirmOrderService } from './confirmOrder.ts';
|
|
29
30
|
import { rejectOrderService } from './rejectOrder.ts';
|
|
@@ -62,6 +63,11 @@ import { simulateConfigurablePriceRangeService } from './simulateConfigurablePri
|
|
|
62
63
|
import { createFileDownloadURLService } from './createFileDownloadURL.ts';
|
|
63
64
|
import { resolveTokenStatusService } from './resolveTokenStatus.ts';
|
|
64
65
|
import { isTokenInvalidateableService } from './isTokenInvalidateable.ts';
|
|
66
|
+
import { bulkRemoveProductsService } from './bulkRemoveProducts.ts';
|
|
67
|
+
import { bulkRemoveFiltersService } from './bulkRemoveFilters.ts';
|
|
68
|
+
import { bulkDeleteUsersService } from './bulkDeleteUsers.ts';
|
|
69
|
+
import { bulkSetFilterActiveService } from './bulkSetFilterActive.ts';
|
|
70
|
+
import { bulkAssignProductsToAssortmentService } from './bulkAssignProductsToAssortment.ts';
|
|
65
71
|
export type ServiceInterface = (this: Modules, ...args: any[]) => Promise<any> | any;
|
|
66
72
|
export type Bound<T extends ServiceInterface> = OmitThisParameter<T>;
|
|
67
73
|
export type CustomServices = Record<string, ServiceInterface | Record<string, ServiceInterface>>;
|
|
@@ -105,18 +111,23 @@ export default function initServices(modules: Modules, customServices?: CustomSe
|
|
|
105
111
|
simulateProductDispatching: Bound<typeof simulateProductDispatchingService>;
|
|
106
112
|
simulateProductInventory: Bound<typeof simulateProductInventoryService>;
|
|
107
113
|
removeProduct: Bound<typeof removeProductService>;
|
|
114
|
+
bulkRemoveProducts: Bound<typeof bulkRemoveProductsService>;
|
|
108
115
|
findProductSiblings: Bound<typeof findProductSiblingsService>;
|
|
109
116
|
simulateConfigurablePriceRange: Bound<typeof simulateConfigurablePriceRangeService>;
|
|
110
117
|
};
|
|
118
|
+
assortments: {
|
|
119
|
+
bulkAssignProductsToAssortment: Bound<typeof bulkAssignProductsToAssortmentService>;
|
|
120
|
+
};
|
|
111
121
|
users: {
|
|
112
122
|
migrateUserData: Bound<typeof migrateUserDataService>;
|
|
113
123
|
updateUserAvatarAfterUpload: Bound<typeof updateUserAvatarAfterUploadService>;
|
|
114
124
|
deleteUser: Bound<typeof deleteUserService>;
|
|
125
|
+
bulkDeleteUsers: Bound<typeof bulkDeleteUsersService>;
|
|
115
126
|
};
|
|
116
127
|
enrollments: {
|
|
117
128
|
createEnrollmentFromCheckout: Bound<typeof createEnrollmentFromCheckoutService>;
|
|
118
129
|
generateOrderFromEnrollment: Bound<typeof generateOrderFromEnrollmentService>;
|
|
119
|
-
processEnrollment: Bound<typeof
|
|
130
|
+
processEnrollment: Bound<typeof processEnrollmentService>;
|
|
120
131
|
initializeEnrollment: Bound<typeof initializeEnrollmentService>;
|
|
121
132
|
activateEnrollment: Bound<typeof activateEnrollmentService>;
|
|
122
133
|
terminateEnrollment: Bound<typeof terminateEnrollmentService>;
|
|
@@ -139,6 +150,8 @@ export default function initServices(modules: Modules, customServices?: CustomSe
|
|
|
139
150
|
updateFilter: Bound<typeof updateFilterService>;
|
|
140
151
|
createFilterOption: Bound<typeof createFilterOptionService>;
|
|
141
152
|
removeFilterOption: Bound<typeof removeFilterOptionService>;
|
|
153
|
+
bulkRemoveFilters: Bound<typeof bulkRemoveFiltersService>;
|
|
154
|
+
bulkSetFilterActive: Bound<typeof bulkSetFilterActiveService>;
|
|
142
155
|
};
|
|
143
156
|
warehousing: {
|
|
144
157
|
ercMetadata: Bound<typeof ercMetadataService>;
|
package/lib/services/index.js
CHANGED
|
@@ -23,6 +23,7 @@ import { searchProductsService } from "./searchProducts.js";
|
|
|
23
23
|
import { calculateDiscountTotalService } from "./calculateDiscountTotal.js";
|
|
24
24
|
import { registerPaymentCredentialsService } from "./registerPaymentCredentials.js";
|
|
25
25
|
import { processOrderService } from "./processOrder.js";
|
|
26
|
+
import { processEnrollmentService } from "./processEnrollment.js";
|
|
26
27
|
import { checkoutOrderService } from "./checkoutOrder.js";
|
|
27
28
|
import { confirmOrderService } from "./confirmOrder.js";
|
|
28
29
|
import { rejectOrderService } from "./rejectOrder.js";
|
|
@@ -61,6 +62,11 @@ import { simulateConfigurablePriceRangeService } from "./simulateConfigurablePri
|
|
|
61
62
|
import { createFileDownloadURLService } from "./createFileDownloadURL.js";
|
|
62
63
|
import { resolveTokenStatusService } from "./resolveTokenStatus.js";
|
|
63
64
|
import { isTokenInvalidateableService } from "./isTokenInvalidateable.js";
|
|
65
|
+
import { bulkRemoveProductsService } from "./bulkRemoveProducts.js";
|
|
66
|
+
import { bulkRemoveFiltersService } from "./bulkRemoveFilters.js";
|
|
67
|
+
import { bulkDeleteUsersService } from "./bulkDeleteUsers.js";
|
|
68
|
+
import { bulkSetFilterActiveService } from "./bulkSetFilterActive.js";
|
|
69
|
+
import { bulkAssignProductsToAssortmentService } from "./bulkAssignProductsToAssortment.js";
|
|
64
70
|
function bindMethodsToModules(modules) {
|
|
65
71
|
return {
|
|
66
72
|
get(target, prop, receiver) {
|
|
@@ -116,18 +122,23 @@ export default function initServices(modules, customServices = {}) {
|
|
|
116
122
|
simulateProductDispatching: simulateProductDispatchingService,
|
|
117
123
|
simulateProductInventory: simulateProductInventoryService,
|
|
118
124
|
removeProduct: removeProductService,
|
|
125
|
+
bulkRemoveProducts: bulkRemoveProductsService,
|
|
119
126
|
findProductSiblings: findProductSiblingsService,
|
|
120
127
|
simulateConfigurablePriceRange: simulateConfigurablePriceRangeService,
|
|
121
128
|
},
|
|
129
|
+
assortments: {
|
|
130
|
+
bulkAssignProductsToAssortment: bulkAssignProductsToAssortmentService,
|
|
131
|
+
},
|
|
122
132
|
users: {
|
|
123
133
|
migrateUserData: migrateUserDataService,
|
|
124
134
|
updateUserAvatarAfterUpload: updateUserAvatarAfterUploadService,
|
|
125
135
|
deleteUser: deleteUserService,
|
|
136
|
+
bulkDeleteUsers: bulkDeleteUsersService,
|
|
126
137
|
},
|
|
127
138
|
enrollments: {
|
|
128
139
|
createEnrollmentFromCheckout: createEnrollmentFromCheckoutService,
|
|
129
140
|
generateOrderFromEnrollment: generateOrderFromEnrollmentService,
|
|
130
|
-
processEnrollment:
|
|
141
|
+
processEnrollment: processEnrollmentService,
|
|
131
142
|
initializeEnrollment: initializeEnrollmentService,
|
|
132
143
|
activateEnrollment: activateEnrollmentService,
|
|
133
144
|
terminateEnrollment: terminateEnrollmentService,
|
|
@@ -150,6 +161,8 @@ export default function initServices(modules, customServices = {}) {
|
|
|
150
161
|
updateFilter: updateFilterService,
|
|
151
162
|
createFilterOption: createFilterOptionService,
|
|
152
163
|
removeFilterOption: removeFilterOptionService,
|
|
164
|
+
bulkRemoveFilters: bulkRemoveFiltersService,
|
|
165
|
+
bulkSetFilterActive: bulkSetFilterActiveService,
|
|
153
166
|
},
|
|
154
167
|
warehousing: {
|
|
155
168
|
ercMetadata: ercMetadataService,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/core",
|
|
3
3
|
"description": "Core orchestration package for the Unchained Engine with business services and directors",
|
|
4
|
-
"version": "5.0.0-alpha.
|
|
4
|
+
"version": "5.0.0-alpha.7",
|
|
5
5
|
"main": "lib/core-index.js",
|
|
6
6
|
"types": "lib/core-index.d.ts",
|
|
7
7
|
"type": "module",
|
|
@@ -35,27 +35,27 @@
|
|
|
35
35
|
},
|
|
36
36
|
"homepage": "https://github.com/unchainedshop/unchained#readme",
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@unchainedshop/core-assortments": "^5.0.0-alpha.
|
|
39
|
-
"@unchainedshop/core-bookmarks": "^5.0.0-alpha.
|
|
40
|
-
"@unchainedshop/core-countries": "^5.0.0-alpha.
|
|
41
|
-
"@unchainedshop/core-currencies": "^5.0.0-alpha.
|
|
42
|
-
"@unchainedshop/core-delivery": "^5.0.0-alpha.
|
|
43
|
-
"@unchainedshop/core-enrollments": "^5.0.0-alpha.
|
|
44
|
-
"@unchainedshop/core-events": "^5.0.0-alpha.
|
|
45
|
-
"@unchainedshop/core-files": "^5.0.0-alpha.
|
|
46
|
-
"@unchainedshop/core-filters": "^5.0.0-alpha.
|
|
47
|
-
"@unchainedshop/core-languages": "^5.0.0-alpha.
|
|
48
|
-
"@unchainedshop/core-orders": "^5.0.0-alpha.
|
|
49
|
-
"@unchainedshop/core-payment": "^5.0.0-alpha.
|
|
50
|
-
"@unchainedshop/core-products": "^5.0.0-alpha.
|
|
51
|
-
"@unchainedshop/core-quotations": "^5.0.0-alpha.
|
|
52
|
-
"@unchainedshop/core-users": "^5.0.0-alpha.
|
|
53
|
-
"@unchainedshop/core-warehousing": "^5.0.0-alpha.
|
|
54
|
-
"@unchainedshop/core-worker": "^5.0.0-alpha.
|
|
55
|
-
"@unchainedshop/events": "^5.0.0-alpha.
|
|
56
|
-
"@unchainedshop/logger": "^5.0.0-alpha.
|
|
57
|
-
"@unchainedshop/mongodb": "^5.0.0-alpha.
|
|
58
|
-
"@unchainedshop/utils": "^5.0.0-alpha.
|
|
38
|
+
"@unchainedshop/core-assortments": "^5.0.0-alpha.7",
|
|
39
|
+
"@unchainedshop/core-bookmarks": "^5.0.0-alpha.7",
|
|
40
|
+
"@unchainedshop/core-countries": "^5.0.0-alpha.7",
|
|
41
|
+
"@unchainedshop/core-currencies": "^5.0.0-alpha.7",
|
|
42
|
+
"@unchainedshop/core-delivery": "^5.0.0-alpha.7",
|
|
43
|
+
"@unchainedshop/core-enrollments": "^5.0.0-alpha.7",
|
|
44
|
+
"@unchainedshop/core-events": "^5.0.0-alpha.7",
|
|
45
|
+
"@unchainedshop/core-files": "^5.0.0-alpha.7",
|
|
46
|
+
"@unchainedshop/core-filters": "^5.0.0-alpha.7",
|
|
47
|
+
"@unchainedshop/core-languages": "^5.0.0-alpha.7",
|
|
48
|
+
"@unchainedshop/core-orders": "^5.0.0-alpha.7",
|
|
49
|
+
"@unchainedshop/core-payment": "^5.0.0-alpha.7",
|
|
50
|
+
"@unchainedshop/core-products": "^5.0.0-alpha.7",
|
|
51
|
+
"@unchainedshop/core-quotations": "^5.0.0-alpha.7",
|
|
52
|
+
"@unchainedshop/core-users": "^5.0.0-alpha.7",
|
|
53
|
+
"@unchainedshop/core-warehousing": "^5.0.0-alpha.7",
|
|
54
|
+
"@unchainedshop/core-worker": "^5.0.0-alpha.7",
|
|
55
|
+
"@unchainedshop/events": "^5.0.0-alpha.7",
|
|
56
|
+
"@unchainedshop/logger": "^5.0.0-alpha.7",
|
|
57
|
+
"@unchainedshop/mongodb": "^5.0.0-alpha.7",
|
|
58
|
+
"@unchainedshop/utils": "^5.0.0-alpha.7",
|
|
59
59
|
"minipass-json-stream": "^1.0.2",
|
|
60
60
|
"zod": "^4.2.0"
|
|
61
61
|
},
|