@contrail/flexplm 1.7.4-alpha.a05d6ab → 1.7.4-alpha.ad2bc4e
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/interfaces/item-family-changes.d.ts +1 -0
- package/lib/interfaces/item-family-changes.js +2 -0
- package/lib/publish/base-process-publish-assortment.js +145 -68
- package/lib/publish/base-process-publish-assortment.spec.js +263 -0
- package/lib/util/data-converter.js +18 -0
- package/package.json +66 -66
|
@@ -12,6 +12,7 @@ export declare class ItemFamilyChanges {
|
|
|
12
12
|
colorDeletes: string[];
|
|
13
13
|
colorUpdates: string[];
|
|
14
14
|
colorUnchanged: string[];
|
|
15
|
+
colorSuppressed: string[];
|
|
15
16
|
assortmentItemFullChangeMap: Map<string, any>;
|
|
16
17
|
assortmentItemDeleteMap: Map<string, any>;
|
|
17
18
|
itemToFederatedIdMapping: Map<string, string>;
|
|
@@ -14,6 +14,7 @@ class ItemFamilyChanges {
|
|
|
14
14
|
this.colorDeletes = [];
|
|
15
15
|
this.colorUpdates = [];
|
|
16
16
|
this.colorUnchanged = [];
|
|
17
|
+
this.colorSuppressed = []; // changed this window, but excluded by the lifecycle filter
|
|
17
18
|
this.assortmentItemFullChangeMap = new Map();
|
|
18
19
|
this.assortmentItemDeleteMap = new Map();
|
|
19
20
|
this.itemToFederatedIdMapping = new Map();
|
|
@@ -32,6 +33,7 @@ class ItemFamilyChanges {
|
|
|
32
33
|
s += ' colorDeletes: [' + this.colorDeletes + ']\n';
|
|
33
34
|
s += ' colorUpdates: [' + this.colorUpdates + ']\n';
|
|
34
35
|
s += ' colorUnchanged: [' + this.colorUnchanged + ']\n';
|
|
36
|
+
s += ' colorSuppressed: [' + this.colorSuppressed + ']\n';
|
|
35
37
|
s += 'assortmentItemFullChangeMap:\n';
|
|
36
38
|
s += 'size: ' + this.assortmentItemFullChangeMap.size + '\n[';
|
|
37
39
|
for (const key of this.assortmentItemFullChangeMap.keys()) {
|
|
@@ -15,7 +15,7 @@ class BaseProcessPublishAssortment {
|
|
|
15
15
|
constructor(_config, _dc, _mapFileUtil) {
|
|
16
16
|
this.TTL = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
17
17
|
this.cache = {
|
|
18
|
-
carriedFromSeason: {}
|
|
18
|
+
carriedFromSeason: {},
|
|
19
19
|
};
|
|
20
20
|
this.config = _config;
|
|
21
21
|
this.dc = _dc;
|
|
@@ -29,8 +29,7 @@ class BaseProcessPublishAssortment {
|
|
|
29
29
|
let publisher;
|
|
30
30
|
const assortmentId = event.assortmentId;
|
|
31
31
|
try {
|
|
32
|
-
console.info('process-start
|
|
33
|
-
+ ', assortmentPublishChangeId: ' + assortmentPublishChangeId);
|
|
32
|
+
console.info('process-start!');
|
|
34
33
|
let seasonFed;
|
|
35
34
|
try {
|
|
36
35
|
seasonFed = await this.getSeasonFederation(assortmentId);
|
|
@@ -47,13 +46,13 @@ class BaseProcessPublishAssortment {
|
|
|
47
46
|
console.log(message);
|
|
48
47
|
const output = {
|
|
49
48
|
results: { message, event },
|
|
50
|
-
skip_await: true
|
|
49
|
+
skip_await: true,
|
|
51
50
|
};
|
|
52
51
|
return output;
|
|
53
52
|
}
|
|
54
53
|
apcHistory = await this.getApcHistory(assortmentId);
|
|
55
54
|
const sinceDate = await this.getSinceDate(assortmentId, assortmentPublishChangeId, apcHistory);
|
|
56
|
-
//Get detail information
|
|
55
|
+
// Get detail information
|
|
57
56
|
const assortmentPublishChange = await this.downloadAssortmentPublishChange(assortmentId, assortmentPublishChangeId);
|
|
58
57
|
publisher = this.getPublisher(assortmentPublishChange);
|
|
59
58
|
const changeDetail = await this.downloadHydratedChangeDetail(assortmentPublishChange);
|
|
@@ -75,9 +74,9 @@ class BaseProcessPublishAssortment {
|
|
|
75
74
|
e.publishInfo = publishInfo;
|
|
76
75
|
}
|
|
77
76
|
catch (e2) {
|
|
78
|
-
console.
|
|
77
|
+
console.log('catch e2: ' + e2.message);
|
|
79
78
|
}
|
|
80
|
-
console.
|
|
79
|
+
console.log('catch e: ' + e.message);
|
|
81
80
|
throw e;
|
|
82
81
|
}
|
|
83
82
|
}
|
|
@@ -92,7 +91,7 @@ class BaseProcessPublishAssortment {
|
|
|
92
91
|
versionComments: apc?.versionComments,
|
|
93
92
|
publishDate: apc?.createdOn,
|
|
94
93
|
publisher,
|
|
95
|
-
versionHistoryNumber
|
|
94
|
+
versionHistoryNumber,
|
|
96
95
|
};
|
|
97
96
|
return publishInfo;
|
|
98
97
|
}
|
|
@@ -112,15 +111,15 @@ class BaseProcessPublishAssortment {
|
|
|
112
111
|
return 'unknown';
|
|
113
112
|
}
|
|
114
113
|
const createdOnDate = new Date(createdOnString);
|
|
115
|
-
createdOnDate.setMonth(createdOnDate.getMonth() - 1); //subtract 1 month
|
|
114
|
+
createdOnDate.setMonth(createdOnDate.getMonth() - 1); // subtract 1 month
|
|
116
115
|
const createdOnStringMinus1 = createdOnDate.toISOString();
|
|
117
116
|
const createdOn = 'ISGREATERTHAN ' + createdOnStringMinus1;
|
|
118
117
|
const snapshots = await new sdk_1.Entities().get({
|
|
119
118
|
entityName: 'entity-snapshot',
|
|
120
119
|
criteria: {
|
|
121
120
|
entityReference,
|
|
122
|
-
createdOn
|
|
123
|
-
}
|
|
121
|
+
createdOn,
|
|
122
|
+
},
|
|
124
123
|
});
|
|
125
124
|
const assortmentPublishChangeId = apc?.id;
|
|
126
125
|
const snapshot = snapshots.find(snap => snap?.assortmentChangeId === assortmentPublishChangeId);
|
|
@@ -170,7 +169,7 @@ class BaseProcessPublishAssortment {
|
|
|
170
169
|
}
|
|
171
170
|
this.assortment = await new sdk_1.Entities().get({
|
|
172
171
|
entityName: 'assortment',
|
|
173
|
-
id: assortmentId
|
|
172
|
+
id: assortmentId,
|
|
174
173
|
});
|
|
175
174
|
return this.assortment;
|
|
176
175
|
}
|
|
@@ -215,9 +214,9 @@ class BaseProcessPublishAssortment {
|
|
|
215
214
|
return sdk_1.Request.request(resource, {
|
|
216
215
|
method: 'GET',
|
|
217
216
|
headers: {
|
|
218
|
-
Accept: 'application/json',
|
|
219
|
-
'Content-Type': 'application/json'
|
|
220
|
-
}
|
|
217
|
+
"Accept": 'application/json',
|
|
218
|
+
'Content-Type': 'application/json',
|
|
219
|
+
},
|
|
221
220
|
});
|
|
222
221
|
}
|
|
223
222
|
updatedSinceDate(entity, sinceDate) {
|
|
@@ -288,7 +287,7 @@ class BaseProcessPublishAssortment {
|
|
|
288
287
|
}
|
|
289
288
|
}
|
|
290
289
|
}
|
|
291
|
-
dateArray.sort((a, b) =>
|
|
290
|
+
dateArray.sort((a, b) => Date.parse(a) - Date.parse(b));
|
|
292
291
|
const arrIndex = (dateArray.length - pastPublishCount > 0) ? dateArray.length - pastPublishCount : 0;
|
|
293
292
|
sinceDate = dateArray[arrIndex];
|
|
294
293
|
return sinceDate;
|
|
@@ -309,24 +308,26 @@ class BaseProcessPublishAssortment {
|
|
|
309
308
|
return await sdk_1.Request.request(resourceString, {
|
|
310
309
|
method: 'GET',
|
|
311
310
|
Headers: {
|
|
312
|
-
Accept: 'application/json',
|
|
313
|
-
'Content-Type': 'application/json'
|
|
314
|
-
}
|
|
311
|
+
"Accept": 'application/json',
|
|
312
|
+
'Content-Type': 'application/json',
|
|
313
|
+
},
|
|
315
314
|
});
|
|
316
315
|
}
|
|
317
316
|
async downloadHydratedChangeDetail(assortmentPublishChange) {
|
|
318
317
|
try {
|
|
318
|
+
console.info('downloadHydratedChangeDetail-start');
|
|
319
319
|
const link = assortmentPublishChange?.hydratedDetailDownloadLink;
|
|
320
320
|
const response = await fetch(link);
|
|
321
321
|
const data = await response.json();
|
|
322
322
|
return data;
|
|
323
323
|
}
|
|
324
324
|
catch (e) {
|
|
325
|
-
console.
|
|
325
|
+
console.log('Error hydratedDetailDownloadLink: ' + e.message);
|
|
326
326
|
}
|
|
327
327
|
return undefined;
|
|
328
328
|
}
|
|
329
329
|
async downloadAssortmentBaseline(assortmentPublishChange) {
|
|
330
|
+
console.info('downloadAssortmentBaseline-start');
|
|
330
331
|
try {
|
|
331
332
|
const link = assortmentPublishChange?.assortmentBaselineDownloadLink;
|
|
332
333
|
const response = await fetch(link);
|
|
@@ -334,12 +335,15 @@ class BaseProcessPublishAssortment {
|
|
|
334
335
|
return data;
|
|
335
336
|
}
|
|
336
337
|
catch (e) {
|
|
337
|
-
console.
|
|
338
|
+
console.log('Error assortmentBaselineDownloadLink: ' + e.message);
|
|
338
339
|
}
|
|
339
340
|
return undefined;
|
|
340
341
|
}
|
|
341
342
|
async getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
|
|
343
|
+
console.info('getDeleteChanges(): ' + assortmentPublishChange?.id);
|
|
344
|
+
console.info(sinceDate);
|
|
342
345
|
const currentAPCIndex = apcHistory.findIndex(pc => pc?.id === assortmentPublishChange?.id);
|
|
346
|
+
console.info(' currentAPCIndex: ' + currentAPCIndex);
|
|
343
347
|
if (currentAPCIndex == 0) {
|
|
344
348
|
return [];
|
|
345
349
|
}
|
|
@@ -348,13 +352,16 @@ class BaseProcessPublishAssortment {
|
|
|
348
352
|
const apcDeletes = await this.downloadDeleteChanges(assortmentPublishChange);
|
|
349
353
|
let previousApcDate = Date.parse(apcCreatedOn);
|
|
350
354
|
const sinceDateMilliseconds = sinceDate.getTime();
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
355
|
+
if (app_framework_1.Logger.isInfoOn()) {
|
|
356
|
+
console.info('apcCreatedOn: ' + apcCreatedOn);
|
|
357
|
+
console.info('sinceDate: ' + sinceDate);
|
|
358
|
+
console.info('sinceDateMilliseconds: ' + sinceDateMilliseconds);
|
|
359
|
+
console.info('apcDateMilliseconds: ' + previousApcDate);
|
|
360
|
+
}
|
|
361
|
+
// if only 1 apc, no processing needed
|
|
354
362
|
if (sinceDateMilliseconds !== previousApcDate) {
|
|
363
|
+
console.info('sinceDateMilliseconds !== apcDateMilliseconds');
|
|
355
364
|
const currentAssortmentItemIds = this.getBaselineItemIds(assortmentBaseline);
|
|
356
|
-
let scannedCount = 0;
|
|
357
|
-
let addedCount = 0;
|
|
358
365
|
const deleteIds = (apcDeletes.length === 0)
|
|
359
366
|
? []
|
|
360
367
|
: apcDeletes.map(item => item?.itemId);
|
|
@@ -369,20 +376,19 @@ class BaseProcessPublishAssortment {
|
|
|
369
376
|
? apcHistory[i - 1]
|
|
370
377
|
: undefined;
|
|
371
378
|
const deleteChanges = await this.buildDeleteChanges(workingAPC, previousApc);
|
|
379
|
+
console.info('checking deleteChanges');
|
|
372
380
|
for (const dItem of deleteChanges) {
|
|
373
381
|
const dItemId = dItem?.itemId;
|
|
374
|
-
|
|
382
|
+
console.info(dItemId);
|
|
375
383
|
if (!currentAssortmentItemIds.includes(dItemId) && !deleteIds.includes(dItemId)) {
|
|
376
|
-
|
|
384
|
+
console.info('adding');
|
|
377
385
|
deleteIds.push(dItemId);
|
|
378
386
|
apcDeletes.push(dItem);
|
|
379
387
|
}
|
|
380
388
|
}
|
|
381
389
|
}
|
|
382
390
|
}
|
|
383
|
-
console.info('getDeleteChanges():
|
|
384
|
-
+ ', scanned ' + scannedCount + ' historical delete changes, added ' + addedCount
|
|
385
|
-
+ ', deleteIds: ' + deleteIds.length);
|
|
391
|
+
console.info('getDeleteChanges()-currentAssortmentItemIds: ' + currentAssortmentItemIds);
|
|
386
392
|
console.info('getDeleteChanges()-deleteIds: ' + deleteIds);
|
|
387
393
|
}
|
|
388
394
|
return apcDeletes;
|
|
@@ -396,6 +402,7 @@ class BaseProcessPublishAssortment {
|
|
|
396
402
|
return itemIds;
|
|
397
403
|
}
|
|
398
404
|
async downloadDeleteChanges(apc) {
|
|
405
|
+
console.info('deleteDataDownloadLink-start');
|
|
399
406
|
try {
|
|
400
407
|
const link = apc?.deleteDataDownloadLink;
|
|
401
408
|
const response = await fetch(link);
|
|
@@ -403,17 +410,19 @@ class BaseProcessPublishAssortment {
|
|
|
403
410
|
return data;
|
|
404
411
|
}
|
|
405
412
|
catch (e) {
|
|
406
|
-
console.
|
|
413
|
+
console.log('Error deleteDataDownloadLink: ' + e.message);
|
|
407
414
|
}
|
|
408
415
|
return undefined;
|
|
409
416
|
}
|
|
410
417
|
async buildDeleteChanges(apc, previousApc) {
|
|
418
|
+
console.info('buildDeleteChanges()');
|
|
411
419
|
if (apc?.deleteDataDownloadLink) {
|
|
412
420
|
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
413
421
|
if (deleteChanges) {
|
|
414
422
|
return deleteChanges;
|
|
415
423
|
}
|
|
416
424
|
}
|
|
425
|
+
console.info('pulling down full APC');
|
|
417
426
|
apc = await this.downloadAssortmentPublishChange(apc?.assortmentId, apc?.id);
|
|
418
427
|
if (apc?.deleteDataDownloadLink) {
|
|
419
428
|
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
@@ -425,18 +434,19 @@ class BaseProcessPublishAssortment {
|
|
|
425
434
|
throw new Error(BaseProcessPublishAssortment.NOT_ABLE_TO_PROCESS_DELETE_CHANGES);
|
|
426
435
|
}
|
|
427
436
|
let previousBaseline;
|
|
437
|
+
console.info('check previousApc');
|
|
428
438
|
if (previousApc?.assortmentBaselineDownloadLink) {
|
|
429
439
|
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
430
440
|
}
|
|
431
441
|
else {
|
|
442
|
+
console.info('previousApc pulling down full APC');
|
|
432
443
|
previousApc = await this.downloadAssortmentPublishChange(previousApc?.assortmentId, previousApc?.id);
|
|
433
444
|
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
434
445
|
}
|
|
435
446
|
const deleteIds = apc?.detail?.deletes.map(dItem => dItem?.id);
|
|
436
|
-
//building deletes based on previous baseline; because some APCs don't have delete data
|
|
447
|
+
// building deletes based on previous baseline; because some APCs don't have delete data
|
|
437
448
|
const deleteArray = previousBaseline?.assortmentItems.filter(aItem => deleteIds.includes(aItem?.itemId));
|
|
438
|
-
console.info('
|
|
439
|
-
+ '; rebuilding ' + deleteArray.length + ' deletes from previous baseline');
|
|
449
|
+
console.info('deleteArray.length: ' + deleteArray.length);
|
|
440
450
|
return deleteArray;
|
|
441
451
|
}
|
|
442
452
|
async getItemFederatedIds( /*itemIds*/) {
|
|
@@ -474,6 +484,7 @@ class BaseProcessPublishAssortment {
|
|
|
474
484
|
return deleteItems;
|
|
475
485
|
}
|
|
476
486
|
getReleasedForDevelopmentItemAndFamilyIds(fullChange, deleteChanges) {
|
|
487
|
+
console.info('getReleasedForDevelopmentItemAndFamilyIds');
|
|
477
488
|
const releasedForDevelopmentItemIds = [];
|
|
478
489
|
const itemFamilySet = new Set();
|
|
479
490
|
const assortmentItemsArray = fullChange?.assortmentItems;
|
|
@@ -484,13 +495,18 @@ class BaseProcessPublishAssortment {
|
|
|
484
495
|
const itemFamilyId = item?.itemFamilyId;
|
|
485
496
|
if (meetsCriteria) {
|
|
486
497
|
releasedForDevelopmentItemIds.push(itemId);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
498
|
+
}
|
|
499
|
+
// ---- NEW (Step 5): family resolution moved OUT of the meetsCriteria block.
|
|
500
|
+
// A family whose options are ALL pre-development has no other route into this
|
|
501
|
+
// list when it has no assortment-item row of its own. ----
|
|
502
|
+
if (!itemFamilySet.has(itemFamilyId) && itemId !== itemFamilyId) {
|
|
503
|
+
const familyItem = item?.itemFamily;
|
|
504
|
+
if (familyItem) {
|
|
505
|
+
if (this.meetsCriteria({ item: familyItem })
|
|
506
|
+
&& !releasedForDevelopmentItemIds.includes(itemFamilyId)) {
|
|
491
507
|
releasedForDevelopmentItemIds.push(itemFamilyId);
|
|
492
508
|
}
|
|
493
|
-
itemFamilySet.add(itemFamilyId);
|
|
509
|
+
itemFamilySet.add(itemFamilyId); // only mark resolved when we had data to judge
|
|
494
510
|
}
|
|
495
511
|
}
|
|
496
512
|
}
|
|
@@ -525,9 +541,10 @@ class BaseProcessPublishAssortment {
|
|
|
525
541
|
return !!lifecycleStage && !this.config.itemPreDevelopmentLifecycleStages.includes(lifecycleStage);
|
|
526
542
|
}
|
|
527
543
|
async processPublish(pcd, changeDetail, fullChange, deleteChanges) {
|
|
544
|
+
console.info('processPublish-start');
|
|
528
545
|
const event = {
|
|
529
546
|
assortmentId: pcd.assortmentId,
|
|
530
|
-
assortmentPublishChangeId: pcd.assortmentPublishChangeId
|
|
547
|
+
assortmentPublishChangeId: pcd.assortmentPublishChangeId,
|
|
531
548
|
};
|
|
532
549
|
const assortmentItemFullChangeMap = this.getFullChangeAssortmentMap(fullChange);
|
|
533
550
|
const assortmentItemDeleteMap = this.getDeleteChangesAssortmentMap(deleteChanges);
|
|
@@ -542,7 +559,7 @@ class BaseProcessPublishAssortment {
|
|
|
542
559
|
console.log(message);
|
|
543
560
|
return {
|
|
544
561
|
results: { message, event },
|
|
545
|
-
skip_await: true
|
|
562
|
+
skip_await: true,
|
|
546
563
|
};
|
|
547
564
|
}
|
|
548
565
|
const results = await this.sendEvents(events);
|
|
@@ -587,7 +604,7 @@ class BaseProcessPublishAssortment {
|
|
|
587
604
|
taskId: this.config.taskId,
|
|
588
605
|
eventType,
|
|
589
606
|
objectClass: 'LCSSeason',
|
|
590
|
-
events
|
|
607
|
+
events,
|
|
591
608
|
};
|
|
592
609
|
const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
|
|
593
610
|
const result = await this.sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent);
|
|
@@ -633,13 +650,13 @@ class BaseProcessPublishAssortment {
|
|
|
633
650
|
taskId: this.config.taskId,
|
|
634
651
|
eventType,
|
|
635
652
|
objectClass: 'LCSSeason',
|
|
636
|
-
events
|
|
653
|
+
events,
|
|
637
654
|
};
|
|
638
655
|
await fileHandle.writeFile(JSON.stringify(outboundPublishEvent));
|
|
639
656
|
results = {
|
|
640
657
|
message: 'Successfully Saved File',
|
|
641
658
|
outboundPublishEvent,
|
|
642
|
-
fileLocation: dirName + fileName
|
|
659
|
+
fileLocation: dirName + fileName,
|
|
643
660
|
};
|
|
644
661
|
}
|
|
645
662
|
catch (e) {
|
|
@@ -660,7 +677,7 @@ class BaseProcessPublishAssortment {
|
|
|
660
677
|
objectClass: 'LCSSeason',
|
|
661
678
|
eventsFileId: uploadFile.id,
|
|
662
679
|
eventsDownloadLink: uploadFile.downloadUrl,
|
|
663
|
-
eventsAdminDownloadLink: uploadFile.adminDownloadUrl
|
|
680
|
+
eventsAdminDownloadLink: uploadFile.adminDownloadUrl,
|
|
664
681
|
};
|
|
665
682
|
if (sendMode === 'vibeiqfile') {
|
|
666
683
|
const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
|
|
@@ -674,7 +691,7 @@ class BaseProcessPublishAssortment {
|
|
|
674
691
|
await this.sendPublishPayloadEvent(outboundPublishEvent);
|
|
675
692
|
return {
|
|
676
693
|
message: 'Successfully Uploaded File.',
|
|
677
|
-
outboundPublishEvent
|
|
694
|
+
outboundPublishEvent,
|
|
678
695
|
};
|
|
679
696
|
}
|
|
680
697
|
}
|
|
@@ -690,11 +707,11 @@ class BaseProcessPublishAssortment {
|
|
|
690
707
|
originSystemType: 'VibeIQ',
|
|
691
708
|
objectClass: 'AssortmentPublishedToFlexPLM',
|
|
692
709
|
outboundPublishEvent,
|
|
693
|
-
initialEvent
|
|
710
|
+
initialEvent,
|
|
694
711
|
};
|
|
695
712
|
await new sdk_1.Entities().create({
|
|
696
713
|
entityName: 'external-event',
|
|
697
|
-
object: eventBody
|
|
714
|
+
object: eventBody,
|
|
698
715
|
});
|
|
699
716
|
}
|
|
700
717
|
getCurrentDateString() {
|
|
@@ -708,6 +725,7 @@ class BaseProcessPublishAssortment {
|
|
|
708
725
|
+ '-' + d.getUTCMilliseconds();
|
|
709
726
|
}
|
|
710
727
|
getItemFamilyChanges(pcd, changeDetail, assortmentItemFullChangeMap, assortmentItemDeleteMap) {
|
|
728
|
+
console.info('getItemFamilyChanges-start');
|
|
711
729
|
const itemFamilyChanges = new Map();
|
|
712
730
|
const { adds, deletes, updates, familyItemsRemoved } = changeDetail;
|
|
713
731
|
const addIds = adds.map(item => item.id);
|
|
@@ -717,7 +735,7 @@ class BaseProcessPublishAssortment {
|
|
|
717
735
|
for (const [itemId, aItem] of assortmentItemFullChangeMap) {
|
|
718
736
|
const projectItem = aItem?.projectItem;
|
|
719
737
|
const itemFamilyId = aItem?.item?.itemFamilyId;
|
|
720
|
-
if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId)
|
|
738
|
+
if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId)) {
|
|
721
739
|
continue;
|
|
722
740
|
}
|
|
723
741
|
const ifc = itemFamilyChanges.get(itemFamilyId) || new item_family_changes_1.ItemFamilyChanges(itemFamilyId, pcd.sinceDate);
|
|
@@ -729,6 +747,20 @@ class BaseProcessPublishAssortment {
|
|
|
729
747
|
if (pcd.itemToFederatedIdMapping.has(itemId)) {
|
|
730
748
|
ifc.itemToFederatedIdMapping.set(itemId, pcd.itemToFederatedIdMapping.get(itemId));
|
|
731
749
|
}
|
|
750
|
+
// ---- NEW Step to enable ItemFamily publish ET-243: option is pre-development. It must not produce an
|
|
751
|
+
// LCSSKUSeasonLink, but a change to it still makes the family publishable. ----
|
|
752
|
+
if (!pcd.releasedForDevelopmentItemIds.includes(itemId)) {
|
|
753
|
+
const changedInWindow = addIds.includes(itemId)
|
|
754
|
+
|| deleteIds.includes(itemId)
|
|
755
|
+
|| updateIds.includes(itemId)
|
|
756
|
+
|| this.updatedSinceDate(projectItem, pcd.sinceDate)
|
|
757
|
+
|| this.updatedSinceDate(aItem?.item, pcd.sinceDate);
|
|
758
|
+
if (changedInWindow) {
|
|
759
|
+
ifc.colorSuppressed.push(itemId);
|
|
760
|
+
}
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
// NEW Step - ends
|
|
732
764
|
if (itemId === itemFamilyId) {
|
|
733
765
|
if (addIds.includes(itemId))
|
|
734
766
|
ifc.familyAdd = true;
|
|
@@ -744,6 +776,12 @@ class BaseProcessPublishAssortment {
|
|
|
744
776
|
ifc.familyUpdate = true;
|
|
745
777
|
}
|
|
746
778
|
}
|
|
779
|
+
// NEW Step to enable ItemFamily publish ET-243: item-level edits (incl. lifecycleStage) must publish
|
|
780
|
+
// the family. Guarded so the "first option added" flow stays suppressed. ----
|
|
781
|
+
if (!ifc.familyItemRemoved && this.updatedSinceDate(aItem?.item, pcd.sinceDate)) {
|
|
782
|
+
ifc.familyUpdate = true;
|
|
783
|
+
}
|
|
784
|
+
// NEW Step - ends
|
|
747
785
|
if (projectItem && !projectItem?.roles && aItem?.item?.roles) {
|
|
748
786
|
projectItem.roles = aItem?.item?.roles;
|
|
749
787
|
}
|
|
@@ -772,7 +810,8 @@ class BaseProcessPublishAssortment {
|
|
|
772
810
|
}
|
|
773
811
|
else {
|
|
774
812
|
if (!item) {
|
|
775
|
-
console.error('Failed to find deleted item entity
|
|
813
|
+
console.error('Failed to find deleted item entity');
|
|
814
|
+
console.error(' itemFamilyId: ' + itemFamilyId + ' -itemId: ' + itemId);
|
|
776
815
|
continue;
|
|
777
816
|
}
|
|
778
817
|
}
|
|
@@ -799,7 +838,41 @@ class BaseProcessPublishAssortment {
|
|
|
799
838
|
else {
|
|
800
839
|
ifc.colorDeletes.push(itemId);
|
|
801
840
|
}
|
|
802
|
-
} //End deletes for loop
|
|
841
|
+
} // End deletes for loop
|
|
842
|
+
// ---- ET-243: the family's own row left the assortment while the family still has
|
|
843
|
+
// option rows. The platform reports this in changeDetail.familyItemsRemoved, and the
|
|
844
|
+
// row is in neither the baseline nor the delete-data file, so neither loop above can
|
|
845
|
+
// see it. `deletes` is folded in as a fallback in case a removal is ever classified
|
|
846
|
+
// there while still missing from the delete-data file. ----
|
|
847
|
+
for (const removedRow of [...familyItemsRemoved, ...deletes]) {
|
|
848
|
+
const itemId = removedRow?.itemId ?? removedRow?.id;
|
|
849
|
+
const item = removedRow?.item;
|
|
850
|
+
if (!itemId || !item)
|
|
851
|
+
continue;
|
|
852
|
+
const itemFamilyId = item?.itemFamilyId ?? itemId;
|
|
853
|
+
if (itemId !== itemFamilyId)
|
|
854
|
+
continue; // family rows only
|
|
855
|
+
if (assortmentItemFullChangeMap.has(itemId))
|
|
856
|
+
continue; // still in baseline -> loop 1
|
|
857
|
+
if (assortmentItemDeleteMap.has(itemId))
|
|
858
|
+
continue; // hard delete -> loop 2
|
|
859
|
+
if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId))
|
|
860
|
+
continue;
|
|
861
|
+
if (!this.meetsCriteria(removedRow))
|
|
862
|
+
continue;
|
|
863
|
+
let ifc = itemFamilyChanges.get(itemFamilyId);
|
|
864
|
+
if (!ifc) {
|
|
865
|
+
ifc = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, pcd.sinceDate);
|
|
866
|
+
itemFamilyChanges.set(itemFamilyId, ifc);
|
|
867
|
+
}
|
|
868
|
+
if (!ifc.itemFamilyObject) {
|
|
869
|
+
ifc.itemFamilyObject = item;
|
|
870
|
+
}
|
|
871
|
+
if (removedRow?.projectItem) {
|
|
872
|
+
ifc.assortmentItemFullChangeMap.set(itemId, removedRow);
|
|
873
|
+
}
|
|
874
|
+
ifc.familyItemRemoved = true;
|
|
875
|
+
}
|
|
803
876
|
if (app_framework_1.Logger.isDebugOn()) {
|
|
804
877
|
console.debug('returning size: ' + itemFamilyChanges.size);
|
|
805
878
|
for (const [key, value] of itemFamilyChanges) {
|
|
@@ -809,6 +882,7 @@ class BaseProcessPublishAssortment {
|
|
|
809
882
|
return itemFamilyChanges;
|
|
810
883
|
}
|
|
811
884
|
async getEventsForPublishChangeData(publishChangeData) {
|
|
885
|
+
console.info('getEventsForPublishChangeData-start');
|
|
812
886
|
const seasonalPayloads = [];
|
|
813
887
|
for (const itemFamilyChange of publishChangeData.itemFamilyChanges.values()) {
|
|
814
888
|
const events = await this.getEventsForItemFamilyChanges(itemFamilyChange, publishChangeData.assortmentId, publishChangeData.seasonFed, publishChangeData.itemToFederatedIdMapping);
|
|
@@ -816,8 +890,6 @@ class BaseProcessPublishAssortment {
|
|
|
816
890
|
seasonalPayloads.push(...events);
|
|
817
891
|
}
|
|
818
892
|
}
|
|
819
|
-
console.info('getEventsForPublishChangeData: itemFamilies: '
|
|
820
|
-
+ publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
|
|
821
893
|
return seasonalPayloads;
|
|
822
894
|
}
|
|
823
895
|
/**Returns the events for a given ItemFamilyChanges object
|
|
@@ -837,17 +909,22 @@ class BaseProcessPublishAssortment {
|
|
|
837
909
|
* @returns
|
|
838
910
|
*/
|
|
839
911
|
async getEventsForItemFamilyChanges(itemFamilyChanges, assortmentId, seasonFed, itemToFederatedIdMapping) {
|
|
912
|
+
console.info('getEventsForItemFamilyChanges()');
|
|
840
913
|
const events = [];
|
|
841
914
|
const LCSSeason = Object.assign({}, seasonFed);
|
|
842
915
|
const assortment = await this.getAssortment(assortmentId);
|
|
843
916
|
const projectItem = this.getProjectItem(itemFamilyChanges, itemFamilyChanges.itemFamilyId);
|
|
917
|
+
// ---- NEW Step to enable ItemFamily publish ET-243: added color suppressed length to existing condition and familyItemRemovedFlag
|
|
844
918
|
const familyAssortmentItem = itemFamilyChanges.familyAdd || itemFamilyChanges.familyUpdate || itemFamilyChanges.familyDelete
|
|
919
|
+
|| itemFamilyChanges.familyItemRemoved
|
|
845
920
|
|| itemFamilyChanges.colorAdds.length > 0 || itemFamilyChanges.colorUpdates.length > 0 || itemFamilyChanges.colorDeletes.length > 0
|
|
921
|
+
|| itemFamilyChanges.colorSuppressed.length > 0
|
|
846
922
|
|| (projectItem && this.updatedSinceDate(projectItem, itemFamilyChanges.sinceDate));
|
|
847
|
-
//familyItemRemoved
|
|
848
|
-
//
|
|
923
|
+
// familyItemRemoved: the family's own assortment row left the assortment while the
|
|
924
|
+
// family still has option rows. The family stays in the season, so this publishes an
|
|
925
|
+
// LCSProductSeasonLink update and no LCSSKUSeasonLink.
|
|
849
926
|
if (familyAssortmentItem) {
|
|
850
|
-
//Product-season add
|
|
927
|
+
// Product-season add
|
|
851
928
|
const entityReference = itemFamilyChanges.itemFamilyId;
|
|
852
929
|
const prodEntityData = (itemFamilyChanges.assortmentItemFullChangeMap.has(entityReference))
|
|
853
930
|
? itemFamilyChanges.assortmentItemFullChangeMap.get(entityReference)?.item
|
|
@@ -863,7 +940,7 @@ class BaseProcessPublishAssortment {
|
|
|
863
940
|
entityReference: 'item:' + entityReference,
|
|
864
941
|
LCSSeason,
|
|
865
942
|
LCSProduct,
|
|
866
|
-
data: seasonalData
|
|
943
|
+
data: seasonalData,
|
|
867
944
|
};
|
|
868
945
|
if ('ASSORTMENT' === projectItem.addedFromSource && projectItem.addedFromAssortment) {
|
|
869
946
|
const carriedFromSeason = await this.getCarriedFromSeason(projectItem);
|
|
@@ -873,9 +950,9 @@ class BaseProcessPublishAssortment {
|
|
|
873
950
|
}
|
|
874
951
|
events.push(psUpsert);
|
|
875
952
|
}
|
|
876
|
-
//colorway-season adds
|
|
877
|
-
//colorAdds
|
|
878
|
-
//colorUpdates
|
|
953
|
+
// colorway-season adds
|
|
954
|
+
// colorAdds
|
|
955
|
+
// colorUpdates
|
|
879
956
|
const colorwayChanges = [];
|
|
880
957
|
colorwayChanges.push(...itemFamilyChanges.colorAdds);
|
|
881
958
|
colorwayChanges.push(...itemFamilyChanges.colorUpdates);
|
|
@@ -898,7 +975,7 @@ class BaseProcessPublishAssortment {
|
|
|
898
975
|
entityReference: 'item:' + entityReference,
|
|
899
976
|
LCSSeason,
|
|
900
977
|
LCSSKU,
|
|
901
|
-
data: seasonalData
|
|
978
|
+
data: seasonalData,
|
|
902
979
|
};
|
|
903
980
|
if ('ASSORTMENT' === projectItem.addedFromSource && projectItem.addedFromAssortment) {
|
|
904
981
|
const carriedFromSeason = await this.getCarriedFromSeason(projectItem);
|
|
@@ -914,10 +991,10 @@ class BaseProcessPublishAssortment {
|
|
|
914
991
|
let projectItem = {};
|
|
915
992
|
if (itemFamilyChanges?.assortmentItemFullChangeMap.has(id)) {
|
|
916
993
|
const aItem = itemFamilyChanges?.assortmentItemFullChangeMap.get(id);
|
|
917
|
-
projectItem = aItem?.projectItem;
|
|
994
|
+
projectItem = aItem?.projectItem ?? {};
|
|
918
995
|
}
|
|
919
996
|
/////////////////////////////////////////////////////////////////////////////
|
|
920
|
-
//Get from option assortmentItem because family not in assortmentItemFullChangeMap:start
|
|
997
|
+
// Get from option assortmentItem because family not in assortmentItemFullChangeMap:start
|
|
921
998
|
/////////////////////////////////////////////////////////////////////////////
|
|
922
999
|
if (id === itemFamilyChanges.itemFamilyId && Object.keys(projectItem).length == 0) {
|
|
923
1000
|
for (const asstItem of itemFamilyChanges.assortmentItemFullChangeMap.values()) {
|
|
@@ -928,7 +1005,7 @@ class BaseProcessPublishAssortment {
|
|
|
928
1005
|
}
|
|
929
1006
|
}
|
|
930
1007
|
/////////////////////////////////////////////////////////////////////////////
|
|
931
|
-
//Get from option assortmentItem because family not in assortmentItemFullChangeMap:end
|
|
1008
|
+
// Get from option assortmentItem because family not in assortmentItemFullChangeMap:end
|
|
932
1009
|
/////////////////////////////////////////////////////////////////////////////
|
|
933
1010
|
return projectItem;
|
|
934
1011
|
}
|
|
@@ -978,7 +1055,7 @@ class BaseProcessPublishAssortment {
|
|
|
978
1055
|
if (this.cache.carriedFromSeason[addedFromAssortment]) {
|
|
979
1056
|
return this.cache.carriedFromSeason[addedFromAssortment];
|
|
980
1057
|
}
|
|
981
|
-
let seasonFederation
|
|
1058
|
+
let seasonFederation;
|
|
982
1059
|
const assortmentEntity = await this.getAssormentEntityFromId(addedFromAssortment);
|
|
983
1060
|
if (assortmentEntity) {
|
|
984
1061
|
seasonFederation = await this.getSeasonFederationFromAssortment(assortmentEntity);
|
|
@@ -991,7 +1068,7 @@ class BaseProcessPublishAssortment {
|
|
|
991
1068
|
try {
|
|
992
1069
|
assortment = await new sdk_1.Entities().get({
|
|
993
1070
|
entityName: 'assortment',
|
|
994
|
-
id: assortmentId
|
|
1071
|
+
id: assortmentId,
|
|
995
1072
|
});
|
|
996
1073
|
}
|
|
997
1074
|
catch (e) {
|
|
@@ -1005,7 +1082,7 @@ class BaseProcessPublishAssortment {
|
|
|
1005
1082
|
let seasonObj = {
|
|
1006
1083
|
entityReference: 'assortment:' + assortment.id,
|
|
1007
1084
|
objectClass: 'LCSSeason',
|
|
1008
|
-
flexPLMTypePath
|
|
1085
|
+
flexPLMTypePath,
|
|
1009
1086
|
};
|
|
1010
1087
|
try {
|
|
1011
1088
|
const assortmentObj = await this.dc.getFlexPLMObjectData(assortment, [], true);
|
|
@@ -1337,6 +1337,176 @@ describe('getItemFamilyChanges', () => {
|
|
|
1337
1337
|
expect(ifc.colorDeletes.length).toEqual(1);
|
|
1338
1338
|
});
|
|
1339
1339
|
});
|
|
1340
|
+
describe('getItemFamilyChanges - lifecycle-suppressed options', () => {
|
|
1341
|
+
const familyId = 'AERfdvdAt1HHEVvQ';
|
|
1342
|
+
const maroonId = 'YwWKVoL_vGN8khLy';
|
|
1343
|
+
const limeId = 'rsmWhWiXRNv_XBJG';
|
|
1344
|
+
const sinceDate = new Date('2026-08-01T00:00:00.000Z');
|
|
1345
|
+
const afterSince = '2026-08-26T00:00:00.000Z';
|
|
1346
|
+
const beforeSince = '2026-05-11T00:00:00.000Z';
|
|
1347
|
+
const noDetailChanges = () => ({ adds: [], deletes: [], updates: [], familyItemsRemoved: [] });
|
|
1348
|
+
const buildPpa = () => {
|
|
1349
|
+
const config = { itemPreDevelopmentLifecycleStages: ['concept'] };
|
|
1350
|
+
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
1351
|
+
const dc = new data_converter_1.DataConverter(config, mapFileUtil);
|
|
1352
|
+
return new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1353
|
+
};
|
|
1354
|
+
const buildPcd = (releasedIds) => {
|
|
1355
|
+
const seasonFed = {
|
|
1356
|
+
entityReference: 'assortment:11EZML5dMn3tREuT',
|
|
1357
|
+
objectClass: 'LCSSeason'
|
|
1358
|
+
};
|
|
1359
|
+
const pcd = new publish_change_data_1.PublishChangeData('11EZML5dMn3tREuT', seasonFed, 'Pg3y2x5IclYxk3hM', sinceDate);
|
|
1360
|
+
pcd.itemToFederatedIdMapping = new Map();
|
|
1361
|
+
releasedIds.forEach(id => pcd.releasedForDevelopmentItemIds.push(id));
|
|
1362
|
+
return pcd;
|
|
1363
|
+
};
|
|
1364
|
+
const familyItem = (updatedOn = beforeSince, lifecycleStage = 'released') => ({
|
|
1365
|
+
id: familyId, itemFamilyId: familyId, lifecycleStage, name: 'Sweatpant', updatedOn
|
|
1366
|
+
});
|
|
1367
|
+
const familyRow = (updatedOn = beforeSince, lifecycleStage = 'released') => ({
|
|
1368
|
+
itemId: familyId,
|
|
1369
|
+
item: familyItem(updatedOn, lifecycleStage),
|
|
1370
|
+
projectItem: { id: 'pi-' + familyId, updatedOn: beforeSince }
|
|
1371
|
+
});
|
|
1372
|
+
const optionRow = (id, updatedOn, lifecycleStage = 'concept') => ({
|
|
1373
|
+
itemId: id,
|
|
1374
|
+
item: { id, itemFamilyId: familyId, lifecycleStage, updatedOn, itemFamily: familyItem() },
|
|
1375
|
+
projectItem: { id: 'pi-' + id, updatedOn: beforeSince },
|
|
1376
|
+
familyProjectItem: { id: 'pi-' + familyId, updatedOn: beforeSince }
|
|
1377
|
+
});
|
|
1378
|
+
it('records changed concept options as suppressed, not as colour updates', () => {
|
|
1379
|
+
const ppa = buildPpa();
|
|
1380
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1381
|
+
assortmentItems: [familyRow(), optionRow(maroonId, afterSince), optionRow(limeId, afterSince)]
|
|
1382
|
+
});
|
|
1383
|
+
const pcd = buildPcd([familyId]);
|
|
1384
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1385
|
+
expect(pcd.itemFamilyChanges.size).toEqual(1);
|
|
1386
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1387
|
+
expect(ifc.colorSuppressed).toEqual([maroonId, limeId]);
|
|
1388
|
+
expect(ifc.colorAdds.length).toEqual(0);
|
|
1389
|
+
expect(ifc.colorUpdates.length).toEqual(0);
|
|
1390
|
+
expect(ifc.colorDeletes.length).toEqual(0);
|
|
1391
|
+
expect(ifc.familyUpdate).toBe(false); // family itself unchanged
|
|
1392
|
+
});
|
|
1393
|
+
it('keeps the family reachable when only concept option rows are in the baseline', () => {
|
|
1394
|
+
const ppa = buildPpa();
|
|
1395
|
+
const assortmentItems = [optionRow(maroonId, afterSince), optionRow(limeId, afterSince)];
|
|
1396
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems });
|
|
1397
|
+
// derive the gate the way production does, rather than seeding it
|
|
1398
|
+
const releasedIds = ppa.getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems }, []);
|
|
1399
|
+
expect(releasedIds).toEqual([familyId]);
|
|
1400
|
+
const pcd = buildPcd(releasedIds);
|
|
1401
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1402
|
+
expect(pcd.itemFamilyChanges.size).toEqual(1);
|
|
1403
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1404
|
+
expect(ifc.itemFamilyObject.id).toEqual(familyId);
|
|
1405
|
+
expect(ifc.colorSuppressed.length).toEqual(2);
|
|
1406
|
+
expect(ppa.getProjectItem(ifc, familyId)).toEqual({ id: 'pi-' + familyId, updatedOn: beforeSince });
|
|
1407
|
+
});
|
|
1408
|
+
it('does not suppress-record an unchanged concept option', () => {
|
|
1409
|
+
const ppa = buildPpa();
|
|
1410
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1411
|
+
assortmentItems: [familyRow(), optionRow(maroonId, beforeSince)]
|
|
1412
|
+
});
|
|
1413
|
+
const pcd = buildPcd([familyId]);
|
|
1414
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1415
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1416
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1417
|
+
});
|
|
1418
|
+
it('picks up a concept option listed in the change detail even when timestamps are stale', () => {
|
|
1419
|
+
const ppa = buildPpa();
|
|
1420
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1421
|
+
assortmentItems: [familyRow(), optionRow(maroonId, beforeSince)]
|
|
1422
|
+
});
|
|
1423
|
+
const details = noDetailChanges();
|
|
1424
|
+
details.updates = [{ id: maroonId }];
|
|
1425
|
+
const pcd = buildPcd([familyId]);
|
|
1426
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1427
|
+
expect(pcd.itemFamilyChanges.get(familyId).colorSuppressed).toEqual([maroonId]);
|
|
1428
|
+
});
|
|
1429
|
+
it('skips everything when the family itself is pre-development', () => {
|
|
1430
|
+
const ppa = buildPpa();
|
|
1431
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1432
|
+
assortmentItems: [familyRow(afterSince, 'concept'), optionRow(maroonId, afterSince)]
|
|
1433
|
+
});
|
|
1434
|
+
const pcd = buildPcd([]); // nothing released
|
|
1435
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1436
|
+
expect(pcd.itemFamilyChanges.size).toEqual(0);
|
|
1437
|
+
});
|
|
1438
|
+
it('still classifies released options normally', () => {
|
|
1439
|
+
const ppa = buildPpa();
|
|
1440
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1441
|
+
assortmentItems: [familyRow(), optionRow(maroonId, afterSince, 'released')]
|
|
1442
|
+
});
|
|
1443
|
+
const details = noDetailChanges();
|
|
1444
|
+
details.adds = [{ id: maroonId }];
|
|
1445
|
+
const pcd = buildPcd([familyId, maroonId]);
|
|
1446
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1447
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1448
|
+
expect(ifc.colorAdds).toEqual([maroonId]);
|
|
1449
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1450
|
+
});
|
|
1451
|
+
it('publishes a family-only row whose lifecycle just flipped (item-level edit)', () => {
|
|
1452
|
+
const ppa = buildPpa();
|
|
1453
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1454
|
+
assortmentItems: [familyRow(afterSince)] // item.updatedOn moved, projectItem did not
|
|
1455
|
+
});
|
|
1456
|
+
const pcd = buildPcd([familyId]);
|
|
1457
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1458
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1459
|
+
expect(ifc.familyUpdate).toBe(true); // fails without Step 3
|
|
1460
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1461
|
+
});
|
|
1462
|
+
it('leaves an unchanged family-only row with no flags', () => {
|
|
1463
|
+
const ppa = buildPpa();
|
|
1464
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [familyRow()] });
|
|
1465
|
+
const pcd = buildPcd([familyId]);
|
|
1466
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1467
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1468
|
+
expect(ifc.familyAdd).toBe(false);
|
|
1469
|
+
expect(ifc.familyUpdate).toBe(false);
|
|
1470
|
+
expect(ifc.familyDelete).toBe(false);
|
|
1471
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1472
|
+
});
|
|
1473
|
+
it('does not set familyUpdate when the family item was removed', () => {
|
|
1474
|
+
const ppa = buildPpa();
|
|
1475
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [familyRow(afterSince)] });
|
|
1476
|
+
const details = noDetailChanges();
|
|
1477
|
+
details.familyItemsRemoved = [{ itemId: familyId }]; // note: itemId, not id
|
|
1478
|
+
const pcd = buildPcd([familyId]);
|
|
1479
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1480
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1481
|
+
expect(ifc.familyItemRemoved).toBe(true);
|
|
1482
|
+
expect(ifc.familyUpdate).toBe(false); // fails if the !familyItemRemoved guard is dropped
|
|
1483
|
+
expect(ifc.assortmentItemFullChangeMap.size).toEqual(1); // third pass must not re-insert the row
|
|
1484
|
+
});
|
|
1485
|
+
it('flags the family when its row is removed but option rows remain', () => {
|
|
1486
|
+
const ppa = buildPpa();
|
|
1487
|
+
const assortmentItems = [optionRow(maroonId, beforeSince), optionRow(limeId, beforeSince, 'released')];
|
|
1488
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems });
|
|
1489
|
+
const details = { ...noDetailChanges(), familyItemsRemoved: [{ itemId: familyId, item: familyItem() }] };
|
|
1490
|
+
const pcd = buildPcd([familyId, limeId]);
|
|
1491
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1492
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1493
|
+
expect(ifc.familyItemRemoved).toBe(true); // false before the third pass
|
|
1494
|
+
expect(ifc.itemFamilyObject.id).toEqual(familyId);
|
|
1495
|
+
expect(ifc.colorDeletes.length).toEqual(0); // no SKU event should follow
|
|
1496
|
+
expect(ifc.assortmentItemFullChangeMap.has(familyId)).toBe(false); // removed row carries no projectItem
|
|
1497
|
+
});
|
|
1498
|
+
it('leaves a hard-deleted family row to the delete pass', () => {
|
|
1499
|
+
const ppa = buildPpa();
|
|
1500
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [] });
|
|
1501
|
+
const deleteMap = new Map([[familyId, familyRow()]]);
|
|
1502
|
+
const details = { ...noDetailChanges(), familyItemsRemoved: [{ itemId: familyId, item: familyItem() }] };
|
|
1503
|
+
const pcd = buildPcd([familyId]);
|
|
1504
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, deleteMap);
|
|
1505
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1506
|
+
expect(ifc.familyDelete).toBe(true); // set by the delete pass
|
|
1507
|
+
expect(ifc.familyItemRemoved).toBe(false); // third pass skipped it - no double handling
|
|
1508
|
+
});
|
|
1509
|
+
});
|
|
1340
1510
|
describe('getProjectItem', () => {
|
|
1341
1511
|
const config = {
|
|
1342
1512
|
identifierAtts: {
|
|
@@ -1500,6 +1670,70 @@ describe('meetsCriteria', () => {
|
|
|
1500
1670
|
expect(results).toBeTruthy();
|
|
1501
1671
|
});
|
|
1502
1672
|
});
|
|
1673
|
+
describe('getReleasedForDevelopmentItemAndFamilyIds', () => {
|
|
1674
|
+
const familyId = 'AERfdvdAt1HHEVvQ';
|
|
1675
|
+
const maroonId = 'YwWKVoL_vGN8khLy';
|
|
1676
|
+
const limeId = 'rsmWhWiXRNv_XBJG';
|
|
1677
|
+
const buildPpa = (preDev = ['concept']) => {
|
|
1678
|
+
const config = {
|
|
1679
|
+
identifierAtts: { LCSSeason: ['flexPLMSeasonName'] },
|
|
1680
|
+
itemPreDevelopmentLifecycleStages: preDev
|
|
1681
|
+
};
|
|
1682
|
+
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
1683
|
+
const dc = new data_converter_1.DataConverter(config, mapFileUtil);
|
|
1684
|
+
return new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1685
|
+
};
|
|
1686
|
+
const familyItem = (lifecycleStage = 'released') => ({
|
|
1687
|
+
id: familyId, itemFamilyId: familyId, lifecycleStage, name: 'Sweatpant'
|
|
1688
|
+
});
|
|
1689
|
+
// option row as the hydrated baseline actually delivers it: itemFamily present
|
|
1690
|
+
const optionRow = (id, lifecycleStage = 'concept', family = familyItem()) => ({
|
|
1691
|
+
itemId: id,
|
|
1692
|
+
item: { id, itemFamilyId: familyId, lifecycleStage, itemFamily: family }
|
|
1693
|
+
});
|
|
1694
|
+
const familyRow = (lifecycleStage = 'released') => ({
|
|
1695
|
+
itemId: familyId,
|
|
1696
|
+
item: familyItem(lifecycleStage)
|
|
1697
|
+
});
|
|
1698
|
+
const run = (ppa, assortmentItems) => ppa.getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems }, []);
|
|
1699
|
+
it('surfaces the family when every option is pre-development', () => {
|
|
1700
|
+
const ppa = buildPpa();
|
|
1701
|
+
expect(run(ppa, [optionRow(maroonId), optionRow(limeId)])).toEqual([familyId]);
|
|
1702
|
+
});
|
|
1703
|
+
it('does not duplicate the family id when a family row and a released option both exist', () => {
|
|
1704
|
+
const ppa = buildPpa();
|
|
1705
|
+
const ids = run(ppa, [familyRow(), optionRow(limeId, 'released')]);
|
|
1706
|
+
expect(ids.filter(id => id === familyId)).toHaveLength(1);
|
|
1707
|
+
});
|
|
1708
|
+
it('still resolves the family when an earlier row lacks itemFamily hydration', () => {
|
|
1709
|
+
const ppa = buildPpa();
|
|
1710
|
+
const noFamilyHydration = {
|
|
1711
|
+
itemId: maroonId,
|
|
1712
|
+
item: { id: maroonId, itemFamilyId: familyId, lifecycleStage: 'concept' }
|
|
1713
|
+
};
|
|
1714
|
+
expect(run(ppa, [noFamilyHydration, optionRow(limeId)])).toEqual([familyId]);
|
|
1715
|
+
});
|
|
1716
|
+
it('returns nothing when the family itself is pre-development', () => {
|
|
1717
|
+
const ppa = buildPpa();
|
|
1718
|
+
expect(run(ppa, [optionRow(maroonId, 'concept', familyItem('concept'))])).toEqual([]);
|
|
1719
|
+
});
|
|
1720
|
+
it('includes a family-only row exactly once', () => {
|
|
1721
|
+
const ppa = buildPpa();
|
|
1722
|
+
expect(run(ppa, [familyRow()])).toEqual([familyId]);
|
|
1723
|
+
});
|
|
1724
|
+
it('includes a released option and its family, excluding the concept option', () => {
|
|
1725
|
+
const ppa = buildPpa();
|
|
1726
|
+
const ids = run(ppa, [optionRow(maroonId, 'concept'), optionRow(limeId, 'released')]);
|
|
1727
|
+
expect(ids).toContain(familyId);
|
|
1728
|
+
expect(ids).toContain(limeId);
|
|
1729
|
+
expect(ids).not.toContain(maroonId);
|
|
1730
|
+
});
|
|
1731
|
+
it('treats the stage list as case-sensitive (documents the Concept vs concept trap)', () => {
|
|
1732
|
+
const ppa = buildPpa(['Concept']); // as mis-configured in praveen-dev-1
|
|
1733
|
+
const ids = run(ppa, [optionRow(maroonId), optionRow(limeId)]);
|
|
1734
|
+
expect(ids).toEqual([maroonId, familyId, limeId]); // filter inert: everything qualifies
|
|
1735
|
+
});
|
|
1736
|
+
});
|
|
1503
1737
|
describe('getEventsForItemFamilyChanges - conditional eventType', () => {
|
|
1504
1738
|
const config = {
|
|
1505
1739
|
identifierAtts: {
|
|
@@ -1705,6 +1939,35 @@ describe('getEventsForItemFamilyChanges - conditional eventType', () => {
|
|
|
1705
1939
|
// Verify SKU event called with assortment entity
|
|
1706
1940
|
expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenNthCalledWith(2, undefined, mapFileUtil, colorProjectItem, { item: itemData, assortment });
|
|
1707
1941
|
});
|
|
1942
|
+
it('should emit only an LCSProductSeasonLink when the family row was removed', async () => {
|
|
1943
|
+
const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1944
|
+
const itemFamilyId = 'AERfdvdAt1HHEVvQ';
|
|
1945
|
+
const prodEntityData = { id: itemFamilyId, identifier: 'FAM-11', itemNumber: 'FAM-11' };
|
|
1946
|
+
//const projectItem = { id: 'proj-fam', updatedOn: '2023-01-15T00:00:00.000Z' };
|
|
1947
|
+
const projectItem = { id: 'proj-fam', updatedOn: '2022-12-01T00:00:00.000Z' };
|
|
1948
|
+
const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
|
|
1949
|
+
const seasonFed = {
|
|
1950
|
+
entityReference: 'assortment:assort123',
|
|
1951
|
+
objectClass: 'LCSSeason',
|
|
1952
|
+
flexPLMSeasonName: 'Fall 2023'
|
|
1953
|
+
};
|
|
1954
|
+
const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
|
|
1955
|
+
itemFamilyChanges.familyItemRemoved = true; // the only trigger
|
|
1956
|
+
itemFamilyChanges.colorUnchanged.push('rsmWhWiXRNv_XBJG'); // surviving option must not emit
|
|
1957
|
+
itemFamilyChanges.itemFamilyObject = prodEntityData;
|
|
1958
|
+
jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
|
|
1959
|
+
jest.spyOn(bppa, 'getProjectItem').mockReturnValue(projectItem);
|
|
1960
|
+
jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
|
|
1961
|
+
jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
|
|
1962
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
|
|
1963
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
|
|
1964
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(false);
|
|
1965
|
+
const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
|
|
1966
|
+
expect(events).toHaveLength(1);
|
|
1967
|
+
expect(events[0].objectClass).toBe('LCSProductSeasonLink');
|
|
1968
|
+
expect(events[0].eventType).toBe('UPDATE_ON_SEASON');
|
|
1969
|
+
expect(events[0].entityReference).toBe('item:' + itemFamilyId);
|
|
1970
|
+
});
|
|
1708
1971
|
});
|
|
1709
1972
|
describe('sendToFlexPLM / handleVibeIQFile / sendPublishPayloadEvent', () => {
|
|
1710
1973
|
const config = {
|
|
@@ -175,12 +175,18 @@ class DataConverter {
|
|
|
175
175
|
}
|
|
176
176
|
async getObjectReferenceValue(prop, newData, inflateObjRef = false) {
|
|
177
177
|
const slug = prop['slug'];
|
|
178
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
179
|
+
console.debug('getObjectReferenceValue-prop: ' + slug);
|
|
180
|
+
}
|
|
178
181
|
let value = newData[slug];
|
|
179
182
|
const entityType = prop['referencedTypeRootSlug'];
|
|
180
183
|
const entityId = newData[slug + 'Id'];
|
|
181
184
|
if ((!value || typeof value === 'string') && inflateObjRef) {
|
|
182
185
|
if (entityId) {
|
|
183
186
|
if (this.objRefCache[entityId]) {
|
|
187
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
188
|
+
console.debug('cache hit: ' + entityId);
|
|
189
|
+
}
|
|
184
190
|
return this.objRefCache[entityId];
|
|
185
191
|
}
|
|
186
192
|
const criteria = {
|
|
@@ -426,6 +432,9 @@ class DataConverter {
|
|
|
426
432
|
return "";
|
|
427
433
|
}
|
|
428
434
|
if (this.objRefCache[ctx.cacheKey]) {
|
|
435
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
436
|
+
console.debug(`object reference cache hit: ${ctx.cacheKey}`);
|
|
437
|
+
}
|
|
429
438
|
return this.objRefCache[ctx.cacheKey];
|
|
430
439
|
}
|
|
431
440
|
const objectReferenceId = ctx.useIdentityService
|
|
@@ -625,6 +634,9 @@ class DataConverter {
|
|
|
625
634
|
}
|
|
626
635
|
let cacheUser = DataConverter.getFromStaticCache(nd.email);
|
|
627
636
|
if (cacheUser) {
|
|
637
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
638
|
+
console.debug('user cache hit: ' + nd.email);
|
|
639
|
+
}
|
|
628
640
|
await this.processGroupMemberCheck(prop, nd.email);
|
|
629
641
|
return cacheUser;
|
|
630
642
|
}
|
|
@@ -694,12 +706,18 @@ class DataConverter {
|
|
|
694
706
|
*/
|
|
695
707
|
async getUserListValue(prop, newData) {
|
|
696
708
|
const slug = prop['slug'];
|
|
709
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
710
|
+
console.debug('getUserListValue-prop: ' + slug);
|
|
711
|
+
}
|
|
697
712
|
const entityId = newData[slug + 'Id'];
|
|
698
713
|
if (!entityId) {
|
|
699
714
|
return {};
|
|
700
715
|
}
|
|
701
716
|
const cacheUser = DataConverter.getFromStaticCache(entityId);
|
|
702
717
|
if (cacheUser) {
|
|
718
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
719
|
+
console.debug('user cache hit: ' + entityId);
|
|
720
|
+
}
|
|
703
721
|
return Object.assign({}, cacheUser);
|
|
704
722
|
}
|
|
705
723
|
const user = await this.getUserById(entityId);
|
package/package.json
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@contrail/flexplm",
|
|
3
|
-
"version": "1.7.4-alpha.
|
|
4
|
-
"description": "Library used for integration with flexplm.",
|
|
5
|
-
"main": "lib/index.js",
|
|
6
|
-
"types": "lib/index.d.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"flexplm-mapping": "lib/cli/index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"lib/**/*",
|
|
12
|
-
"scripts/copy-template.js"
|
|
13
|
-
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "tsc; node scripts/copy-template.js",
|
|
16
|
-
"build:win": "tsc && node scripts/copy-template.js",
|
|
17
|
-
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
|
|
18
|
-
"lint": "tslint -p tsconfig.json",
|
|
19
|
-
"test": "jest",
|
|
20
|
-
"test-watch": "jest --watch",
|
|
21
|
-
"test-debug": "jest --runInBand",
|
|
22
|
-
"test-coverage": "jest --coverage"
|
|
23
|
-
},
|
|
24
|
-
"keywords": [],
|
|
25
|
-
"author": "VibeIQ",
|
|
26
|
-
"license": "ISC",
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@types/jest": "^29.5.2",
|
|
29
|
-
"jest": "^29.5.0",
|
|
30
|
-
"prettier": "^1.19.1",
|
|
31
|
-
"ts-jest": "^29.1.1",
|
|
32
|
-
"tslint": "^5.11.0",
|
|
33
|
-
"tslint-config-prettier": "^1.18.0",
|
|
34
|
-
"typescript": "^4.0.0"
|
|
35
|
-
},
|
|
36
|
-
"peerDependencies": {
|
|
37
|
-
"typescript": ">=4.0.0"
|
|
38
|
-
},
|
|
39
|
-
"peerDependenciesMeta": {
|
|
40
|
-
"typescript": {
|
|
41
|
-
"optional": true
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
"jest": {
|
|
45
|
-
"moduleFileExtensions": [
|
|
46
|
-
"js",
|
|
47
|
-
"json",
|
|
48
|
-
"ts"
|
|
49
|
-
],
|
|
50
|
-
"rootDir": "src",
|
|
51
|
-
"testRegex": ".spec.ts$",
|
|
52
|
-
"transform": {
|
|
53
|
-
"^.+\\.(t|j)s$": "ts-jest"
|
|
54
|
-
},
|
|
55
|
-
"coverageDirectory": "../coverage",
|
|
56
|
-
"testEnvironment": "node"
|
|
57
|
-
},
|
|
58
|
-
"dependencies": {
|
|
59
|
-
"@contrail/app-framework": "^1.4.3",
|
|
60
|
-
"@contrail/sdk": "^1.5.10",
|
|
61
|
-
"@contrail/transform-data": "^1.3.2",
|
|
62
|
-
"@contrail/util": "^1.3.1",
|
|
63
|
-
"axios": "^1.4.0",
|
|
64
|
-
"p-limit": "^3.1.0"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@contrail/flexplm",
|
|
3
|
+
"version": "1.7.4-alpha.ad2bc4e",
|
|
4
|
+
"description": "Library used for integration with flexplm.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"flexplm-mapping": "lib/cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"lib/**/*",
|
|
12
|
+
"scripts/copy-template.js"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc; node scripts/copy-template.js",
|
|
16
|
+
"build:win": "tsc && node scripts/copy-template.js",
|
|
17
|
+
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
|
|
18
|
+
"lint": "tslint -p tsconfig.json",
|
|
19
|
+
"test": "jest",
|
|
20
|
+
"test-watch": "jest --watch",
|
|
21
|
+
"test-debug": "jest --runInBand",
|
|
22
|
+
"test-coverage": "jest --coverage"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [],
|
|
25
|
+
"author": "VibeIQ",
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/jest": "^29.5.2",
|
|
29
|
+
"jest": "^29.5.0",
|
|
30
|
+
"prettier": "^1.19.1",
|
|
31
|
+
"ts-jest": "^29.1.1",
|
|
32
|
+
"tslint": "^5.11.0",
|
|
33
|
+
"tslint-config-prettier": "^1.18.0",
|
|
34
|
+
"typescript": "^4.0.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"typescript": ">=4.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"typescript": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"jest": {
|
|
45
|
+
"moduleFileExtensions": [
|
|
46
|
+
"js",
|
|
47
|
+
"json",
|
|
48
|
+
"ts"
|
|
49
|
+
],
|
|
50
|
+
"rootDir": "src",
|
|
51
|
+
"testRegex": ".spec.ts$",
|
|
52
|
+
"transform": {
|
|
53
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
54
|
+
},
|
|
55
|
+
"coverageDirectory": "../coverage",
|
|
56
|
+
"testEnvironment": "node"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@contrail/app-framework": "^1.4.3",
|
|
60
|
+
"@contrail/sdk": "^1.5.10",
|
|
61
|
+
"@contrail/transform-data": "^1.3.2",
|
|
62
|
+
"@contrail/util": "^1.3.1",
|
|
63
|
+
"axios": "^1.4.0",
|
|
64
|
+
"p-limit": "^3.1.0"
|
|
65
|
+
}
|
|
66
|
+
}
|