@unchainedshop/core-quotations 3.0.0-alpha7 → 3.0.0-rc2
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/lib/db/QuotationsCollection.d.ts +39 -2
- package/lib/db/QuotationsCollection.d.ts.map +1 -1
- package/lib/db/QuotationsCollection.js +8 -0
- package/lib/db/QuotationsCollection.js.map +1 -1
- package/lib/director/QuotationDirector.d.ts.map +1 -1
- package/lib/director/QuotationDirector.js.map +1 -1
- package/lib/module/configureQuotationsModule.d.ts +31 -45
- package/lib/module/configureQuotationsModule.d.ts.map +1 -1
- package/lib/module/configureQuotationsModule.js +17 -124
- package/lib/module/configureQuotationsModule.js.map +1 -1
- package/lib/module/configureQuotationsModule_BACKUP_56551.d.ts +52 -0
- package/lib/module/configureQuotationsModule_BACKUP_56551.d.ts.map +1 -0
- package/lib/module/configureQuotationsModule_BACKUP_56551.js +152 -0
- package/lib/module/configureQuotationsModule_BACKUP_56551.js.map +1 -0
- package/lib/module/configureQuotationsModule_BASE_56551.d.ts +65 -0
- package/lib/module/configureQuotationsModule_BASE_56551.d.ts.map +1 -0
- package/lib/module/configureQuotationsModule_BASE_56551.js +259 -0
- package/lib/module/configureQuotationsModule_BASE_56551.js.map +1 -0
- package/lib/module/configureQuotationsModule_LOCAL_56551.d.ts +51 -0
- package/lib/module/configureQuotationsModule_LOCAL_56551.d.ts.map +1 -0
- package/lib/module/configureQuotationsModule_LOCAL_56551.js +145 -0
- package/lib/module/configureQuotationsModule_LOCAL_56551.js.map +1 -0
- package/lib/module/configureQuotationsModule_REMOTE_56551.d.ts +66 -0
- package/lib/module/configureQuotationsModule_REMOTE_56551.d.ts.map +1 -0
- package/lib/module/configureQuotationsModule_REMOTE_56551.js +266 -0
- package/lib/module/configureQuotationsModule_REMOTE_56551.js.map +1 -0
- package/lib/quotations-index.d.ts +1 -5
- package/lib/quotations-index.d.ts.map +1 -1
- package/lib/quotations-index.js +1 -5
- package/lib/quotations-index.js.map +1 -1
- package/lib/quotations-index_BACKUP_56874.d.ts +4 -0
- package/lib/quotations-index_BACKUP_56874.d.ts.map +1 -0
- package/lib/quotations-index_BACKUP_56874.js +4 -0
- package/lib/quotations-index_BACKUP_56874.js.map +1 -0
- package/lib/quotations-index_BASE_56874.d.ts +8 -0
- package/lib/quotations-index_BASE_56874.d.ts.map +1 -0
- package/lib/quotations-index_BASE_56874.js +8 -0
- package/lib/quotations-index_BASE_56874.js.map +1 -0
- package/lib/quotations-index_LOCAL_56874.d.ts +4 -0
- package/lib/quotations-index_LOCAL_56874.d.ts.map +1 -0
- package/lib/quotations-index_LOCAL_56874.js +4 -0
- package/lib/quotations-index_LOCAL_56874.js.map +1 -0
- package/lib/quotations-index_REMOTE_56874.d.ts +9 -0
- package/lib/quotations-index_REMOTE_56874.d.ts.map +1 -0
- package/lib/quotations-index_REMOTE_56874.js +9 -0
- package/lib/quotations-index_REMOTE_56874.js.map +1 -0
- package/lib/quotations-settings.d.ts +1 -1
- package/lib/quotations-settings.d.ts.map +1 -1
- package/lib/types.d.ts +5 -6
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js.map +1 -1
- package/package.json +9 -12
- package/src/db/QuotationsCollection.ts +34 -2
- package/src/module/configureQuotationsModule.ts +50 -234
- package/src/quotations-index.ts +2 -8
- package/src/quotations-settings.ts +1 -1
- package/tsconfig.json +5 -7
- package/src/db/QuotationStatus.ts +0 -7
- package/src/director/QuotationAdapter.ts +0 -51
- package/src/director/QuotationDirector.ts +0 -99
- package/src/director/QuotationError.ts +0 -6
- package/src/types.ts +0 -74
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { SortDirection, SortOption } from '@unchainedshop/utils';
|
|
2
|
-
import {
|
|
3
|
-
import { Quotation, QuotationItemConfiguration, QuotationProposal } from '../types.js';
|
|
2
|
+
import { Quotation, QuotationStatus } from '../db/QuotationsCollection.js';
|
|
4
3
|
import { emit, registerEvents } from '@unchainedshop/events';
|
|
5
4
|
import {
|
|
6
5
|
generateDbFilterById,
|
|
@@ -10,81 +9,18 @@ import {
|
|
|
10
9
|
ModuleInput,
|
|
11
10
|
} from '@unchainedshop/mongodb';
|
|
12
11
|
import { QuotationsCollection } from '../db/QuotationsCollection.js';
|
|
13
|
-
import { QuotationStatus } from '../db/QuotationStatus.js';
|
|
14
|
-
import { QuotationDirector } from '../quotations-index.js';
|
|
15
12
|
import { quotationsSettings, QuotationsSettingsOptions } from '../quotations-settings.js';
|
|
16
|
-
import { resolveBestCurrency } from '@unchainedshop/utils';
|
|
17
13
|
|
|
18
14
|
export type QuotationQuery = {
|
|
19
15
|
userId?: string;
|
|
20
16
|
queryString?: string;
|
|
21
17
|
};
|
|
22
|
-
export interface QuotationQueries {
|
|
23
|
-
findQuotation: (query: { quotationId: string }, options?: mongodb.FindOptions) => Promise<Quotation>;
|
|
24
|
-
findQuotations: (
|
|
25
|
-
query: QuotationQuery & {
|
|
26
|
-
limit?: number;
|
|
27
|
-
offset?: number;
|
|
28
|
-
sort?: Array<SortOption>;
|
|
29
|
-
},
|
|
30
|
-
options?: mongodb.FindOptions,
|
|
31
|
-
) => Promise<Array<Quotation>>;
|
|
32
|
-
count: (query: QuotationQuery) => Promise<number>;
|
|
33
|
-
openQuotationWithProduct: (param: { productId: string }) => Promise<Quotation | null>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Transformations
|
|
37
|
-
|
|
38
|
-
export interface QuotationTransformations {
|
|
39
|
-
isExpired: (quotation: Quotation, params?: { referenceDate: Date }) => boolean;
|
|
40
|
-
isProposalValid: (quotation: Quotation) => boolean;
|
|
41
|
-
normalizedStatus: (quotation: Quotation) => string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Processing
|
|
45
|
-
|
|
46
|
-
export type QuotationContextParams = (
|
|
47
|
-
quotation: Quotation,
|
|
48
|
-
params: { quotationContext?: any },
|
|
49
|
-
unchainedAPI: UnchainedCore,
|
|
50
|
-
) => Promise<Quotation>;
|
|
51
|
-
|
|
52
|
-
// Mutations
|
|
53
18
|
export interface QuotationData {
|
|
54
19
|
configuration?: Array<{ key: string; value: string }>;
|
|
55
20
|
countryCode?: string;
|
|
56
21
|
productId: string;
|
|
57
22
|
userId: string;
|
|
58
23
|
}
|
|
59
|
-
export interface QuotationMutations {
|
|
60
|
-
create: (doc: QuotationData, unchainedAPI: UnchainedCore) => Promise<Quotation>;
|
|
61
|
-
|
|
62
|
-
updateContext: (quotationId: string, context: any) => Promise<Quotation | null>;
|
|
63
|
-
|
|
64
|
-
updateProposal: (quotationId: string, proposal: QuotationProposal) => Promise<Quotation>;
|
|
65
|
-
|
|
66
|
-
updateStatus: (
|
|
67
|
-
quotationId: string,
|
|
68
|
-
params: { status: QuotationStatus; info?: string },
|
|
69
|
-
) => Promise<Quotation>;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface QuotationProcessing {
|
|
73
|
-
fullfillQuotation: (quotationId: string, info: any, unchainedAPI: UnchainedCore) => Promise<Quotation>;
|
|
74
|
-
proposeQuotation: QuotationContextParams;
|
|
75
|
-
rejectQuotation: QuotationContextParams;
|
|
76
|
-
verifyQuotation: QuotationContextParams;
|
|
77
|
-
transformItemConfiguration: (
|
|
78
|
-
quotation: Quotation,
|
|
79
|
-
configuration: QuotationItemConfiguration,
|
|
80
|
-
unchainedAPI: UnchainedCore,
|
|
81
|
-
) => Promise<QuotationItemConfiguration>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export type QuotationsModule = QuotationQueries &
|
|
85
|
-
QuotationTransformations &
|
|
86
|
-
QuotationProcessing &
|
|
87
|
-
QuotationMutations;
|
|
88
24
|
|
|
89
25
|
const QUOTATION_EVENTS: string[] = ['QUOTATION_REQUEST_CREATE', 'QUOTATION_REMOVE', 'QUOTATION_UPDATE'];
|
|
90
26
|
|
|
@@ -100,17 +36,10 @@ export const buildFindSelector = (query: QuotationQuery = {}) => {
|
|
|
100
36
|
return selector;
|
|
101
37
|
};
|
|
102
38
|
|
|
103
|
-
const isExpired: QuotationsModule['isExpired'] = (quotation, { referenceDate }) => {
|
|
104
|
-
const relevantDate = referenceDate ? new Date(referenceDate) : new Date();
|
|
105
|
-
const expiryDate = new Date(quotation.expires);
|
|
106
|
-
const isQuotationExpired = relevantDate.getTime() > expiryDate.getTime();
|
|
107
|
-
return isQuotationExpired;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
39
|
export const configureQuotationsModule = async ({
|
|
111
40
|
db,
|
|
112
41
|
options: quotationsOptions = {},
|
|
113
|
-
}: ModuleInput<QuotationsSettingsOptions>)
|
|
42
|
+
}: ModuleInput<QuotationsSettingsOptions>) => {
|
|
114
43
|
registerEvents(QUOTATION_EVENTS);
|
|
115
44
|
|
|
116
45
|
quotationsSettings.configureSettings(quotationsOptions);
|
|
@@ -125,27 +54,10 @@ export const configureQuotationsModule = async ({
|
|
|
125
54
|
return findNewQuotationNumber(quotation, index + 1);
|
|
126
55
|
};
|
|
127
56
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
): Promise<
|
|
132
|
-
let status = quotation.status as QuotationStatus;
|
|
133
|
-
const director = await QuotationDirector.actions({ quotation }, unchainedAPI);
|
|
134
|
-
|
|
135
|
-
if (status === QuotationStatus.REQUESTED) {
|
|
136
|
-
if (!(await director.isManualRequestVerificationRequired())) {
|
|
137
|
-
status = QuotationStatus.PROCESSING;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (status === QuotationStatus.PROCESSING) {
|
|
141
|
-
if (!(await director.isManualProposalRequired())) {
|
|
142
|
-
status = QuotationStatus.PROPOSED;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return status;
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const updateStatus: QuotationsModule['updateStatus'] = async (quotationId, { status, info = '' }) => {
|
|
57
|
+
const updateStatus = async (
|
|
58
|
+
quotationId: string,
|
|
59
|
+
{ status, info = '' }: { status: QuotationStatus; info?: string },
|
|
60
|
+
): Promise<Quotation> => {
|
|
149
61
|
const selector = generateDbFilterById(quotationId);
|
|
150
62
|
const quotation = await Quotations.findOne(selector, {});
|
|
151
63
|
|
|
@@ -197,66 +109,9 @@ export const configureQuotationsModule = async ({
|
|
|
197
109
|
return updatedQuotation;
|
|
198
110
|
};
|
|
199
111
|
|
|
200
|
-
const processQuotation = async (
|
|
201
|
-
initialQuotation: Quotation,
|
|
202
|
-
params: { quotationContext?: any },
|
|
203
|
-
unchainedAPI: UnchainedCore,
|
|
204
|
-
) => {
|
|
205
|
-
const { modules } = unchainedAPI;
|
|
206
|
-
|
|
207
|
-
const quotationId = initialQuotation._id;
|
|
208
|
-
let quotation = initialQuotation;
|
|
209
|
-
let nextStatus = await findNextStatus(quotation, unchainedAPI);
|
|
210
|
-
const director = await QuotationDirector.actions({ quotation }, unchainedAPI);
|
|
211
|
-
|
|
212
|
-
if (quotation.status === QuotationStatus.REQUESTED && nextStatus !== QuotationStatus.REQUESTED) {
|
|
213
|
-
await director.submitRequest(params.quotationContext);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
quotation = await modules.quotations.findQuotation({ quotationId });
|
|
217
|
-
nextStatus = await findNextStatus(quotation, unchainedAPI);
|
|
218
|
-
if (nextStatus !== QuotationStatus.PROCESSING) {
|
|
219
|
-
await director.verifyRequest(params.quotationContext);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
quotation = await modules.quotations.findQuotation({ quotationId });
|
|
223
|
-
nextStatus = await findNextStatus(quotation, unchainedAPI);
|
|
224
|
-
if (nextStatus === QuotationStatus.REJECTED) {
|
|
225
|
-
await director.rejectRequest(params.quotationContext);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
quotation = await modules.quotations.findQuotation({ quotationId });
|
|
229
|
-
nextStatus = await findNextStatus(quotation, unchainedAPI);
|
|
230
|
-
if (nextStatus === QuotationStatus.PROPOSED) {
|
|
231
|
-
const proposal = await director.quote();
|
|
232
|
-
quotation = await modules.quotations.updateProposal(quotation._id, proposal);
|
|
233
|
-
nextStatus = await findNextStatus(quotation, unchainedAPI);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return updateStatus(quotation._id, { status: nextStatus, info: 'quotation processed' });
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
const sendStatusToCustomer = async (quotation: Quotation, unchainedAPI: UnchainedCore) => {
|
|
240
|
-
const { modules } = unchainedAPI;
|
|
241
|
-
|
|
242
|
-
const user = await modules.users.findUserById(quotation.userId);
|
|
243
|
-
const locale = modules.users.userLocale(user);
|
|
244
|
-
|
|
245
|
-
await modules.worker.addWork({
|
|
246
|
-
type: 'MESSAGE',
|
|
247
|
-
retries: 0,
|
|
248
|
-
input: {
|
|
249
|
-
locale,
|
|
250
|
-
template: 'QUOTATION_STATUS',
|
|
251
|
-
quotationId: quotation._id,
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
return quotation;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
112
|
const updateQuotationFields =
|
|
259
|
-
(fieldKeys: Array<string>) =>
|
|
113
|
+
(fieldKeys: Array<string>) =>
|
|
114
|
+
async (quotationId: string, values: any): Promise<Quotation> => {
|
|
260
115
|
const quotation = await Quotations.findOneAndUpdate(generateDbFilterById(quotationId), {
|
|
261
116
|
$set: {
|
|
262
117
|
updated: new Date(),
|
|
@@ -271,23 +126,38 @@ export const configureQuotationsModule = async ({
|
|
|
271
126
|
|
|
272
127
|
return {
|
|
273
128
|
// Queries
|
|
274
|
-
count: async (query) => {
|
|
129
|
+
count: async (query: QuotationQuery): Promise<number> => {
|
|
275
130
|
const quotationCount = await Quotations.countDocuments(buildFindSelector(query));
|
|
276
131
|
return quotationCount;
|
|
277
132
|
},
|
|
278
|
-
openQuotationWithProduct: async ({ productId }) => {
|
|
133
|
+
openQuotationWithProduct: async ({ productId }: { productId: string }): Promise<Quotation> => {
|
|
279
134
|
const selector: mongodb.Filter<Quotation> = { productId };
|
|
280
135
|
selector.status = { $in: [QuotationStatus.REQUESTED, QuotationStatus.PROPOSED] };
|
|
281
136
|
|
|
282
137
|
return Quotations.findOne(selector);
|
|
283
138
|
},
|
|
284
139
|
|
|
285
|
-
findQuotation: async (
|
|
140
|
+
findQuotation: async (
|
|
141
|
+
{ quotationId }: { quotationId: string },
|
|
142
|
+
options?: mongodb.FindOptions<Quotation>,
|
|
143
|
+
): Promise<Quotation> => {
|
|
286
144
|
const selector = generateDbFilterById(quotationId);
|
|
287
145
|
return Quotations.findOne(selector, options);
|
|
288
146
|
},
|
|
289
147
|
|
|
290
|
-
findQuotations: async (
|
|
148
|
+
findQuotations: async (
|
|
149
|
+
{
|
|
150
|
+
limit,
|
|
151
|
+
offset,
|
|
152
|
+
sort,
|
|
153
|
+
...query
|
|
154
|
+
}: QuotationQuery & {
|
|
155
|
+
limit?: number;
|
|
156
|
+
offset?: number;
|
|
157
|
+
sort?: Array<SortOption>;
|
|
158
|
+
},
|
|
159
|
+
options?: mongodb.FindOptions<Quotation>,
|
|
160
|
+
): Promise<Array<Quotation>> => {
|
|
291
161
|
const defaultSortOption: Array<SortOption> = [{ key: 'created', value: SortDirection.ASC }];
|
|
292
162
|
const quotations = Quotations.find(buildFindSelector(query), {
|
|
293
163
|
limit,
|
|
@@ -300,87 +170,29 @@ export const configureQuotationsModule = async ({
|
|
|
300
170
|
},
|
|
301
171
|
|
|
302
172
|
// Transformations
|
|
303
|
-
normalizedStatus: (quotation) => {
|
|
173
|
+
normalizedStatus: (quotation: Quotation): QuotationStatus => {
|
|
304
174
|
return quotation.status === null
|
|
305
175
|
? QuotationStatus.REQUESTED
|
|
306
176
|
: (quotation.status as QuotationStatus);
|
|
307
177
|
},
|
|
308
178
|
|
|
309
|
-
isExpired,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
// Processing
|
|
316
|
-
fullfillQuotation: async (quotationId, info, unchainedAPI) => {
|
|
317
|
-
const selector = generateDbFilterById(quotationId);
|
|
318
|
-
const quotation = await Quotations.findOne(selector, {});
|
|
319
|
-
|
|
320
|
-
if (quotation.status === QuotationStatus.FULLFILLED) return quotation;
|
|
321
|
-
|
|
322
|
-
let updatedQuotation = await updateStatus(quotation._id, {
|
|
323
|
-
status: QuotationStatus.FULLFILLED,
|
|
324
|
-
info: JSON.stringify(info),
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
updatedQuotation = await processQuotation(updatedQuotation, {}, unchainedAPI);
|
|
328
|
-
|
|
329
|
-
return sendStatusToCustomer(updatedQuotation, unchainedAPI);
|
|
179
|
+
isExpired(quotation: Quotation, { referenceDate }: { referenceDate: Date }) {
|
|
180
|
+
const relevantDate = referenceDate ? new Date(referenceDate) : new Date();
|
|
181
|
+
const expiryDate = new Date(quotation.expires);
|
|
182
|
+
const isQuotationExpired = relevantDate.getTime() > expiryDate.getTime();
|
|
183
|
+
return isQuotationExpired;
|
|
330
184
|
},
|
|
331
185
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
let updatedQuotation = await updateStatus(quotation._id, {
|
|
336
|
-
status: QuotationStatus.PROPOSED,
|
|
337
|
-
info: 'proposed manually',
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
updatedQuotation = await processQuotation(updatedQuotation, { quotationContext }, unchainedAPI);
|
|
341
|
-
|
|
342
|
-
return sendStatusToCustomer(updatedQuotation, unchainedAPI);
|
|
343
|
-
},
|
|
344
|
-
|
|
345
|
-
rejectQuotation: async (quotation, { quotationContext }, unchainedAPI) => {
|
|
346
|
-
if (quotation.status === QuotationStatus.FULLFILLED) return quotation;
|
|
347
|
-
|
|
348
|
-
let updatedQuotation = await updateStatus(quotation._id, {
|
|
349
|
-
status: QuotationStatus.REJECTED,
|
|
350
|
-
info: 'rejected manually',
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
updatedQuotation = await processQuotation(updatedQuotation, { quotationContext }, unchainedAPI);
|
|
354
|
-
|
|
355
|
-
return sendStatusToCustomer(updatedQuotation, unchainedAPI);
|
|
356
|
-
},
|
|
357
|
-
|
|
358
|
-
verifyQuotation: async (quotation, { quotationContext }, unchainedAPI) => {
|
|
359
|
-
if (quotation.status !== QuotationStatus.REQUESTED) return quotation;
|
|
360
|
-
|
|
361
|
-
let updatedQuotation = await updateStatus(quotation._id, {
|
|
362
|
-
status: QuotationStatus.PROCESSING,
|
|
363
|
-
info: 'verified elligibility manually',
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
updatedQuotation = await processQuotation(updatedQuotation, { quotationContext }, unchainedAPI);
|
|
367
|
-
|
|
368
|
-
return sendStatusToCustomer(updatedQuotation, unchainedAPI);
|
|
369
|
-
},
|
|
370
|
-
|
|
371
|
-
transformItemConfiguration: async (quotation, configuration, unchainedAPI) => {
|
|
372
|
-
const director = await QuotationDirector.actions({ quotation }, unchainedAPI);
|
|
373
|
-
return director.transformItemConfiguration(configuration);
|
|
186
|
+
isProposalValid(quotation: Quotation): boolean {
|
|
187
|
+
return quotation.status === QuotationStatus.PROPOSED && !this.isExpired(quotation);
|
|
374
188
|
},
|
|
375
189
|
|
|
376
190
|
// Mutations
|
|
377
|
-
create: async ({
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const currency = resolveBestCurrency(countryObject.defaultCurrencyCode, currencies);
|
|
383
|
-
|
|
191
|
+
create: async ({
|
|
192
|
+
countryCode,
|
|
193
|
+
currency,
|
|
194
|
+
...quotationData
|
|
195
|
+
}: QuotationData & { currency: string }): Promise<Quotation> => {
|
|
384
196
|
const { insertedId: quotationId } = await Quotations.insertOne({
|
|
385
197
|
_id: generateDbObjectId(),
|
|
386
198
|
created: new Date(),
|
|
@@ -392,20 +204,24 @@ export const configureQuotationsModule = async ({
|
|
|
392
204
|
status: QuotationStatus.REQUESTED,
|
|
393
205
|
});
|
|
394
206
|
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
let quotation = await processQuotation(newQuotation, {}, unchainedAPI);
|
|
398
|
-
|
|
399
|
-
quotation = await sendStatusToCustomer(quotation, unchainedAPI);
|
|
207
|
+
const quotation = await Quotations.findOne(generateDbFilterById(quotationId), {});
|
|
400
208
|
|
|
401
209
|
await emit('QUOTATION_REQUEST_CREATE', { quotation });
|
|
402
210
|
|
|
403
211
|
return quotation;
|
|
404
212
|
},
|
|
405
|
-
|
|
213
|
+
deleteRequestedUserQuotations: async (userId: string) => {
|
|
214
|
+
const { deletedCount } = await Quotations.deleteMany({
|
|
215
|
+
userId,
|
|
216
|
+
status: { $in: [QuotationStatus.REQUESTED, null] },
|
|
217
|
+
});
|
|
218
|
+
return deletedCount;
|
|
219
|
+
},
|
|
406
220
|
updateContext: updateQuotationFields(['context']),
|
|
407
221
|
updateProposal: updateQuotationFields(['price', 'expires', 'meta']),
|
|
408
222
|
|
|
409
223
|
updateStatus,
|
|
410
224
|
};
|
|
411
225
|
};
|
|
226
|
+
|
|
227
|
+
export type QuotationsModule = Awaited<ReturnType<typeof configureQuotationsModule>>;
|
package/src/quotations-index.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './db/QuotationsCollection.js';
|
|
2
|
+
|
|
2
3
|
export * from './module/configureQuotationsModule.js';
|
|
3
4
|
export * from './quotations-settings.js';
|
|
4
|
-
|
|
5
|
-
export { QuotationStatus } from './db/QuotationStatus.js';
|
|
6
|
-
|
|
7
|
-
export { QuotationAdapter } from './director/QuotationAdapter.js';
|
|
8
|
-
export { QuotationDirector } from './director/QuotationDirector.js';
|
|
9
|
-
|
|
10
|
-
export { QuotationError } from './director/QuotationError.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { generateRandomHash } from '@unchainedshop/utils';
|
|
2
|
-
import { Quotation } from './
|
|
2
|
+
import { Quotation } from './db/QuotationsCollection.js';
|
|
3
3
|
|
|
4
4
|
export interface QuotationsSettingsOptions {
|
|
5
5
|
quotationNumberHashFn?: (quotation: Quotation, index: number) => string;
|
package/tsconfig.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": "../shared/base.tsconfig.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
-
"
|
|
5
|
-
"
|
|
4
|
+
"declarationDir": "./lib",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"outDir": "./lib"
|
|
6
7
|
},
|
|
7
|
-
"exclude": ["**/*.test.ts", "**/*.test.js", "tests"]
|
|
8
|
-
|
|
9
|
-
"./src"
|
|
10
|
-
]
|
|
11
|
-
}
|
|
8
|
+
"exclude": ["**/*.test.ts", "**/*.test.js", "tests", "lib"]
|
|
9
|
+
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { log, LogLevel } from '@unchainedshop/logger';
|
|
2
|
-
import { IQuotationAdapter } from '../types.js';
|
|
3
|
-
import { QuotationError } from './QuotationError.js';
|
|
4
|
-
|
|
5
|
-
export const QuotationAdapter: Omit<IQuotationAdapter, 'key' | 'label' | 'version'> = {
|
|
6
|
-
orderIndex: 0,
|
|
7
|
-
|
|
8
|
-
isActivatedFor: () => {
|
|
9
|
-
return false;
|
|
10
|
-
},
|
|
11
|
-
|
|
12
|
-
actions: () => {
|
|
13
|
-
return {
|
|
14
|
-
configurationError: () => {
|
|
15
|
-
return QuotationError.NOT_IMPLEMENTED;
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
isManualRequestVerificationRequired: async () => {
|
|
19
|
-
return true;
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
isManualProposalRequired: async () => {
|
|
23
|
-
return true;
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
quote: async () => {
|
|
27
|
-
return {};
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
rejectRequest: async () => {
|
|
31
|
-
return true;
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
submitRequest: async () => {
|
|
35
|
-
return true;
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
verifyRequest: async () => {
|
|
39
|
-
return true;
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
transformItemConfiguration: async ({ quantity, configuration }) => {
|
|
43
|
-
return { quantity, configuration };
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
},
|
|
47
|
-
|
|
48
|
-
log(message: string, { level = LogLevel.Debug, ...options } = {}) {
|
|
49
|
-
return log(message, { level, ...options });
|
|
50
|
-
},
|
|
51
|
-
};
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { LogLevel, log } from '@unchainedshop/logger';
|
|
2
|
-
import { IQuotationAdapter, IQuotationDirector, QuotationContext } from '../types.js';
|
|
3
|
-
import { BaseDirector } from '@unchainedshop/utils';
|
|
4
|
-
import { UnchainedCore } from '@unchainedshop/core';
|
|
5
|
-
import { QuotationError } from './QuotationError.js';
|
|
6
|
-
|
|
7
|
-
const baseDirector = BaseDirector<IQuotationAdapter>('QuotationDirector', {
|
|
8
|
-
adapterSortKey: 'orderIndex',
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
const findAppropriateAdapters = (quotationContext: QuotationContext, unchainedAPI: UnchainedCore) =>
|
|
12
|
-
baseDirector.getAdapters({
|
|
13
|
-
adapterFilter: (Adapter: IQuotationAdapter) => {
|
|
14
|
-
const activated = Adapter.isActivatedFor(quotationContext, unchainedAPI);
|
|
15
|
-
if (!activated) {
|
|
16
|
-
log(`Quotation Director -> ${Adapter.key} (${Adapter.version}) skipped`, {
|
|
17
|
-
level: LogLevel.Warning,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
return activated;
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
export const QuotationDirector: IQuotationDirector = {
|
|
25
|
-
...baseDirector,
|
|
26
|
-
|
|
27
|
-
actions: async (quotationContext, unchainedAPI) => {
|
|
28
|
-
const context = { ...quotationContext, ...unchainedAPI };
|
|
29
|
-
|
|
30
|
-
const Adapter = findAppropriateAdapters(quotationContext, unchainedAPI)?.shift();
|
|
31
|
-
|
|
32
|
-
if (!Adapter) {
|
|
33
|
-
throw new Error('No suitable quotation plugin available for this context');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const adapter = Adapter.actions(context);
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
configurationError: () => {
|
|
40
|
-
try {
|
|
41
|
-
return adapter.configurationError();
|
|
42
|
-
} catch (error) {
|
|
43
|
-
log('QuotationDirector -> Error while checking for configurationError', {
|
|
44
|
-
level: LogLevel.Warning,
|
|
45
|
-
...error,
|
|
46
|
-
});
|
|
47
|
-
return QuotationError.ADAPTER_NOT_FOUND;
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
isManualRequestVerificationRequired: async () => {
|
|
52
|
-
try {
|
|
53
|
-
const isRequired = await adapter.isManualRequestVerificationRequired();
|
|
54
|
-
return isRequired;
|
|
55
|
-
} catch (error) {
|
|
56
|
-
log('QuotationDirector -> Error while checking if is manual request verification required', {
|
|
57
|
-
level: LogLevel.Error,
|
|
58
|
-
...error,
|
|
59
|
-
});
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
isManualProposalRequired: async () => {
|
|
65
|
-
try {
|
|
66
|
-
const isRequired = await adapter.isManualProposalRequired();
|
|
67
|
-
return isRequired;
|
|
68
|
-
} catch (error) {
|
|
69
|
-
log('QuotationDirector -> Error while checking if is manual proposal required', {
|
|
70
|
-
level: LogLevel.Error,
|
|
71
|
-
...error,
|
|
72
|
-
});
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
quote: adapter.quote,
|
|
78
|
-
rejectRequest: adapter.rejectRequest,
|
|
79
|
-
submitRequest: adapter.submitRequest,
|
|
80
|
-
verifyRequest: adapter.verifyRequest,
|
|
81
|
-
|
|
82
|
-
transformItemConfiguration: async ({ quantity, configuration }) => {
|
|
83
|
-
try {
|
|
84
|
-
const itemConfiguration = await adapter.transformItemConfiguration({
|
|
85
|
-
quantity,
|
|
86
|
-
configuration,
|
|
87
|
-
});
|
|
88
|
-
return itemConfiguration;
|
|
89
|
-
} catch (error) {
|
|
90
|
-
log('QuotationDirector -> Error while transforming item configuration', {
|
|
91
|
-
level: LogLevel.Error,
|
|
92
|
-
...error,
|
|
93
|
-
});
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
},
|
|
99
|
-
};
|
package/src/types.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { IBaseAdapter, IBaseDirector } from '@unchainedshop/utils';
|
|
2
|
-
import type { TimestampFields, LogFields } from '@unchainedshop/mongodb';
|
|
3
|
-
import type { UnchainedCore } from '@unchainedshop/core';
|
|
4
|
-
|
|
5
|
-
export enum QuotationStatus {
|
|
6
|
-
REQUESTED = 'REQUESTED',
|
|
7
|
-
PROCESSING = 'PROCESSING',
|
|
8
|
-
PROPOSED = 'PROPOSED',
|
|
9
|
-
FULLFILLED = 'FULLFILLED',
|
|
10
|
-
REJECTED = 'REJECTED',
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export enum QuotationError {
|
|
14
|
-
ADAPTER_NOT_FOUND = 'ADAPTER_NOT_FOUND',
|
|
15
|
-
NOT_IMPLEMENTED = 'NOT_IMPLEMENTED',
|
|
16
|
-
INCOMPLETE_CONFIGURATION = 'INCOMPLETE_CONFIGURATION',
|
|
17
|
-
WRONG_CREDENTIALS = 'WRONG_CREDENTIALS',
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type Quotation = {
|
|
21
|
-
_id?: string;
|
|
22
|
-
configuration?: Array<{ key: string; value: string }>;
|
|
23
|
-
context?: any;
|
|
24
|
-
countryCode?: string;
|
|
25
|
-
currency?: string;
|
|
26
|
-
expires?: Date;
|
|
27
|
-
fullfilled?: Date;
|
|
28
|
-
meta?: any;
|
|
29
|
-
price?: number;
|
|
30
|
-
productId: string;
|
|
31
|
-
quotationNumber?: string;
|
|
32
|
-
rejected?: Date;
|
|
33
|
-
status: string;
|
|
34
|
-
userId: string;
|
|
35
|
-
} & LogFields &
|
|
36
|
-
TimestampFields;
|
|
37
|
-
|
|
38
|
-
export type QuotationProposal = { price?: number; expires?: Date; meta?: any };
|
|
39
|
-
|
|
40
|
-
export interface QuotationItemConfiguration {
|
|
41
|
-
quantity?: number;
|
|
42
|
-
configuration: Array<{ key: string; value: string }>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export type QuotationContext = {
|
|
46
|
-
quotation?: Quotation;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export interface QuotationAdapterActions {
|
|
50
|
-
configurationError: () => QuotationError;
|
|
51
|
-
isManualProposalRequired: () => Promise<boolean>;
|
|
52
|
-
isManualRequestVerificationRequired: () => Promise<boolean>;
|
|
53
|
-
quote: () => Promise<QuotationProposal>;
|
|
54
|
-
rejectRequest: (unchainedAPI?: any) => Promise<boolean>;
|
|
55
|
-
submitRequest: (unchainedAPI?: any) => Promise<boolean>;
|
|
56
|
-
verifyRequest: (unchainedAPI?: any) => Promise<boolean>;
|
|
57
|
-
|
|
58
|
-
transformItemConfiguration: (
|
|
59
|
-
params: QuotationItemConfiguration,
|
|
60
|
-
) => Promise<QuotationItemConfiguration>;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export type IQuotationAdapter = IBaseAdapter & {
|
|
64
|
-
orderIndex: number;
|
|
65
|
-
isActivatedFor: (quotationContext: QuotationContext, unchainedAPI: UnchainedCore) => boolean;
|
|
66
|
-
actions: (params: QuotationContext & UnchainedCore) => QuotationAdapterActions;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
export type IQuotationDirector = IBaseDirector<IQuotationAdapter> & {
|
|
70
|
-
actions: (
|
|
71
|
-
quotationContext: QuotationContext,
|
|
72
|
-
unchainedAPI: UnchainedCore,
|
|
73
|
-
) => Promise<QuotationAdapterActions>;
|
|
74
|
-
};
|