@contrail/flexplm 1.7.4-alpha.72422a6 → 1.7.4-alpha.858e2cc
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/interfaces.d.ts +6 -0
- package/lib/interfaces/item-family-changes.d.ts +6 -0
- package/lib/interfaces/item-family-changes.js +8 -0
- package/lib/publish/base-process-publish-assortment.d.ts +5 -0
- package/lib/publish/base-process-publish-assortment.js +108 -24
- package/lib/publish/base-process-publish-assortment.spec.js +256 -0
- package/lib/util/config-defaults.d.ts +2 -0
- package/lib/util/config-defaults.js +2 -0
- package/lib/util/config-defaults.spec.js +47 -0
- package/lib/util/data-converter.js +18 -0
- package/package.json +66 -66
|
@@ -15,6 +15,12 @@ export interface FCConfig {
|
|
|
15
15
|
vibeEventEndpoint: string;
|
|
16
16
|
csrfEndpoint: string;
|
|
17
17
|
useDistinctRestEndPointForImages?: boolean;
|
|
18
|
+
/** Opt-in. When true, an item family that itself passes meetsCriteria() is published to the
|
|
19
|
+
* season even when every one of its options is still in a pre-development lifecycle stage. */
|
|
20
|
+
publishReleasedFamilyWithPreDevelopmentOptions?: boolean;
|
|
21
|
+
/** Only meaningful when publishReleasedFamilyWithPreDevelopmentOptions is true. When true, such a
|
|
22
|
+
* family is republished on EVERY publish, not only when one of its options actually changed. */
|
|
23
|
+
alwaysPublishReleasedFamilyWithPreDevelopmentOptions?: boolean;
|
|
18
24
|
itemPreDevelopmentLifecycleStages: string[];
|
|
19
25
|
identifierAtts?: {
|
|
20
26
|
[key: string]: string[];
|
|
@@ -12,6 +12,12 @@ export declare class ItemFamilyChanges {
|
|
|
12
12
|
colorDeletes: string[];
|
|
13
13
|
colorUpdates: string[];
|
|
14
14
|
colorUnchanged: string[];
|
|
15
|
+
/** Options that changed but are still pre-development. They do NOT produce
|
|
16
|
+
* LCSSKUSeasonLink events - they only signal that the family needs republishing. */
|
|
17
|
+
preDevelopmentOptionChanges: string[];
|
|
18
|
+
/** True when at least one option row was tracked at family level only, because the option is
|
|
19
|
+
* pre-development while the family itself is released. */
|
|
20
|
+
hasPreDevelopmentOnlyOptions: boolean;
|
|
15
21
|
assortmentItemFullChangeMap: Map<string, any>;
|
|
16
22
|
assortmentItemDeleteMap: Map<string, any>;
|
|
17
23
|
itemToFederatedIdMapping: Map<string, string>;
|
|
@@ -14,6 +14,12 @@ class ItemFamilyChanges {
|
|
|
14
14
|
this.colorDeletes = [];
|
|
15
15
|
this.colorUpdates = [];
|
|
16
16
|
this.colorUnchanged = [];
|
|
17
|
+
/** Options that changed but are still pre-development. They do NOT produce
|
|
18
|
+
* LCSSKUSeasonLink events - they only signal that the family needs republishing. */
|
|
19
|
+
this.preDevelopmentOptionChanges = [];
|
|
20
|
+
/** True when at least one option row was tracked at family level only, because the option is
|
|
21
|
+
* pre-development while the family itself is released. */
|
|
22
|
+
this.hasPreDevelopmentOnlyOptions = false;
|
|
17
23
|
this.assortmentItemFullChangeMap = new Map();
|
|
18
24
|
this.assortmentItemDeleteMap = new Map();
|
|
19
25
|
this.itemToFederatedIdMapping = new Map();
|
|
@@ -32,6 +38,8 @@ class ItemFamilyChanges {
|
|
|
32
38
|
s += ' colorDeletes: [' + this.colorDeletes + ']\n';
|
|
33
39
|
s += ' colorUpdates: [' + this.colorUpdates + ']\n';
|
|
34
40
|
s += ' colorUnchanged: [' + this.colorUnchanged + ']\n';
|
|
41
|
+
s += ' preDevelopmentOptionChanges: [' + this.preDevelopmentOptionChanges + ']\n';
|
|
42
|
+
s += ' hasPreDevelopmentOnlyOptions: ' + this.hasPreDevelopmentOnlyOptions + '\n';
|
|
35
43
|
s += 'assortmentItemFullChangeMap:\n';
|
|
36
44
|
s += 'size: ' + this.assortmentItemFullChangeMap.size + '\n[';
|
|
37
45
|
for (const key of this.assortmentItemFullChangeMap.keys()) {
|
|
@@ -62,6 +62,11 @@ export declare class BaseProcessPublishAssortment {
|
|
|
62
62
|
getFullChangeAssortmentMap(fullChange: any): Map<string, object>;
|
|
63
63
|
getDeleteChangesAssortmentMap(deleteChanges: any[]): Map<string, object>;
|
|
64
64
|
getReleasedForDevelopmentItemAndFamilyIds(fullChange: any, deleteChanges: any): string[];
|
|
65
|
+
/** Adds item families that pass meetsCriteria() but whose option rows are all pre-development.
|
|
66
|
+
* Only ever ADDS family ids - never option ids, and never removes anything. */
|
|
67
|
+
protected addReleasedFamiliesOfPreDevelopmentOptions(aItems: any[], releasedForDevelopmentItemIds: string[]): void;
|
|
68
|
+
protected publishReleasedFamilyWithPreDevelopmentOptions(): boolean;
|
|
69
|
+
protected alwaysPublishReleasedFamilyWithPreDevelopmentOptions(): boolean;
|
|
65
70
|
meetsCriteria(aItem: any): boolean;
|
|
66
71
|
processPublish(pcd: PublishChangeData, changeDetail: any, fullChange: any, deleteChanges: any): Promise<{
|
|
67
72
|
results: {
|
|
@@ -9,7 +9,9 @@ const map_utils_1 = require("../util/map-utils");
|
|
|
9
9
|
const type_conversion_utils_1 = require("../util/type-conversion-utils");
|
|
10
10
|
const fsPromise = require("fs/promises");
|
|
11
11
|
const path = require("path");
|
|
12
|
+
const app_framework_1 = require("@contrail/app-framework");
|
|
12
13
|
const event_short_message_status_1 = require("../util/event-short-message-status");
|
|
14
|
+
const config_defaults_1 = require("../util/config-defaults");
|
|
13
15
|
class BaseProcessPublishAssortment {
|
|
14
16
|
constructor(_config, _dc, _mapFileUtil) {
|
|
15
17
|
this.TTL = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
@@ -28,8 +30,7 @@ class BaseProcessPublishAssortment {
|
|
|
28
30
|
let publisher;
|
|
29
31
|
const assortmentId = event.assortmentId;
|
|
30
32
|
try {
|
|
31
|
-
console.info('process-start
|
|
32
|
-
+ ', assortmentPublishChangeId: ' + assortmentPublishChangeId);
|
|
33
|
+
console.info('process-start!');
|
|
33
34
|
let seasonFed;
|
|
34
35
|
try {
|
|
35
36
|
seasonFed = await this.getSeasonFederation(assortmentId);
|
|
@@ -74,9 +75,9 @@ class BaseProcessPublishAssortment {
|
|
|
74
75
|
e.publishInfo = publishInfo;
|
|
75
76
|
}
|
|
76
77
|
catch (e2) {
|
|
77
|
-
console.
|
|
78
|
+
console.log('catch e2: ' + e2.message);
|
|
78
79
|
}
|
|
79
|
-
console.
|
|
80
|
+
console.log('catch e: ' + e.message);
|
|
80
81
|
throw e;
|
|
81
82
|
}
|
|
82
83
|
}
|
|
@@ -315,17 +316,19 @@ class BaseProcessPublishAssortment {
|
|
|
315
316
|
}
|
|
316
317
|
async downloadHydratedChangeDetail(assortmentPublishChange) {
|
|
317
318
|
try {
|
|
319
|
+
console.info('downloadHydratedChangeDetail-start');
|
|
318
320
|
const link = assortmentPublishChange?.hydratedDetailDownloadLink;
|
|
319
321
|
const response = await fetch(link);
|
|
320
322
|
const data = await response.json();
|
|
321
323
|
return data;
|
|
322
324
|
}
|
|
323
325
|
catch (e) {
|
|
324
|
-
console.
|
|
326
|
+
console.log('Error hydratedDetailDownloadLink: ' + e.message);
|
|
325
327
|
}
|
|
326
328
|
return undefined;
|
|
327
329
|
}
|
|
328
330
|
async downloadAssortmentBaseline(assortmentPublishChange) {
|
|
331
|
+
console.info('downloadAssortmentBaseline-start');
|
|
329
332
|
try {
|
|
330
333
|
const link = assortmentPublishChange?.assortmentBaselineDownloadLink;
|
|
331
334
|
const response = await fetch(link);
|
|
@@ -333,12 +336,15 @@ class BaseProcessPublishAssortment {
|
|
|
333
336
|
return data;
|
|
334
337
|
}
|
|
335
338
|
catch (e) {
|
|
336
|
-
console.
|
|
339
|
+
console.log('Error assortmentBaselineDownloadLink: ' + e.message);
|
|
337
340
|
}
|
|
338
341
|
return undefined;
|
|
339
342
|
}
|
|
340
343
|
async getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
|
|
344
|
+
console.info('getDeleteChanges(): ' + assortmentPublishChange?.id);
|
|
345
|
+
console.info(sinceDate);
|
|
341
346
|
const currentAPCIndex = apcHistory.findIndex(pc => pc?.id === assortmentPublishChange?.id);
|
|
347
|
+
console.info(' currentAPCIndex: ' + currentAPCIndex);
|
|
342
348
|
if (currentAPCIndex == 0) {
|
|
343
349
|
return [];
|
|
344
350
|
}
|
|
@@ -347,13 +353,16 @@ class BaseProcessPublishAssortment {
|
|
|
347
353
|
const apcDeletes = await this.downloadDeleteChanges(assortmentPublishChange);
|
|
348
354
|
let previousApcDate = Date.parse(apcCreatedOn);
|
|
349
355
|
const sinceDateMilliseconds = sinceDate.getTime();
|
|
350
|
-
|
|
351
|
-
|
|
356
|
+
if (app_framework_1.Logger.isInfoOn()) {
|
|
357
|
+
console.info('apcCreatedOn: ' + apcCreatedOn);
|
|
358
|
+
console.info('sinceDate: ' + sinceDate);
|
|
359
|
+
console.info('sinceDateMilliseconds: ' + sinceDateMilliseconds);
|
|
360
|
+
console.info('apcDateMilliseconds: ' + previousApcDate);
|
|
361
|
+
}
|
|
352
362
|
//if only 1 apc, no processing needed
|
|
353
363
|
if (sinceDateMilliseconds !== previousApcDate) {
|
|
364
|
+
console.info('sinceDateMilliseconds !== apcDateMilliseconds');
|
|
354
365
|
const currentAssortmentItemIds = this.getBaselineItemIds(assortmentBaseline);
|
|
355
|
-
let scannedCount = 0;
|
|
356
|
-
let addedCount = 0;
|
|
357
366
|
const deleteIds = (apcDeletes.length === 0)
|
|
358
367
|
? []
|
|
359
368
|
: apcDeletes.map(item => item?.itemId);
|
|
@@ -368,20 +377,19 @@ class BaseProcessPublishAssortment {
|
|
|
368
377
|
? apcHistory[i - 1]
|
|
369
378
|
: undefined;
|
|
370
379
|
const deleteChanges = await this.buildDeleteChanges(workingAPC, previousApc);
|
|
380
|
+
console.info('checking deleteChanges');
|
|
371
381
|
for (const dItem of deleteChanges) {
|
|
372
382
|
const dItemId = dItem?.itemId;
|
|
373
|
-
|
|
383
|
+
console.info(dItemId);
|
|
374
384
|
if (!currentAssortmentItemIds.includes(dItemId) && !deleteIds.includes(dItemId)) {
|
|
375
|
-
|
|
385
|
+
console.info('adding');
|
|
376
386
|
deleteIds.push(dItemId);
|
|
377
387
|
apcDeletes.push(dItem);
|
|
378
388
|
}
|
|
379
389
|
}
|
|
380
390
|
}
|
|
381
391
|
}
|
|
382
|
-
console.info('getDeleteChanges():
|
|
383
|
-
+ ', scanned ' + scannedCount + ' historical delete changes, added ' + addedCount
|
|
384
|
-
+ ', deleteIds: ' + deleteIds.length);
|
|
392
|
+
console.info('getDeleteChanges()-currentAssortmentItemIds: ' + currentAssortmentItemIds);
|
|
385
393
|
console.info('getDeleteChanges()-deleteIds: ' + deleteIds);
|
|
386
394
|
}
|
|
387
395
|
return apcDeletes;
|
|
@@ -395,6 +403,7 @@ class BaseProcessPublishAssortment {
|
|
|
395
403
|
return itemIds;
|
|
396
404
|
}
|
|
397
405
|
async downloadDeleteChanges(apc) {
|
|
406
|
+
console.info('deleteDataDownloadLink-start');
|
|
398
407
|
try {
|
|
399
408
|
const link = apc?.deleteDataDownloadLink;
|
|
400
409
|
const response = await fetch(link);
|
|
@@ -402,17 +411,19 @@ class BaseProcessPublishAssortment {
|
|
|
402
411
|
return data;
|
|
403
412
|
}
|
|
404
413
|
catch (e) {
|
|
405
|
-
console.
|
|
414
|
+
console.log('Error deleteDataDownloadLink: ' + e.message);
|
|
406
415
|
}
|
|
407
416
|
return undefined;
|
|
408
417
|
}
|
|
409
418
|
async buildDeleteChanges(apc, previousApc) {
|
|
419
|
+
console.info('buildDeleteChanges()');
|
|
410
420
|
if (apc?.deleteDataDownloadLink) {
|
|
411
421
|
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
412
422
|
if (deleteChanges) {
|
|
413
423
|
return deleteChanges;
|
|
414
424
|
}
|
|
415
425
|
}
|
|
426
|
+
console.info('pulling down full APC');
|
|
416
427
|
apc = await this.downloadAssortmentPublishChange(apc?.assortmentId, apc?.id);
|
|
417
428
|
if (apc?.deleteDataDownloadLink) {
|
|
418
429
|
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
@@ -424,18 +435,19 @@ class BaseProcessPublishAssortment {
|
|
|
424
435
|
throw new Error(BaseProcessPublishAssortment.NOT_ABLE_TO_PROCESS_DELETE_CHANGES);
|
|
425
436
|
}
|
|
426
437
|
let previousBaseline;
|
|
438
|
+
console.info('check previousApc');
|
|
427
439
|
if (previousApc?.assortmentBaselineDownloadLink) {
|
|
428
440
|
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
429
441
|
}
|
|
430
442
|
else {
|
|
443
|
+
console.info('previousApc pulling down full APC');
|
|
431
444
|
previousApc = await this.downloadAssortmentPublishChange(previousApc?.assortmentId, previousApc?.id);
|
|
432
445
|
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
433
446
|
}
|
|
434
447
|
const deleteIds = apc?.detail?.deletes.map(dItem => dItem?.id);
|
|
435
448
|
//building deletes based on previous baseline; because some APCs don't have delete data
|
|
436
449
|
const deleteArray = previousBaseline?.assortmentItems.filter(aItem => deleteIds.includes(aItem?.itemId));
|
|
437
|
-
console.info('
|
|
438
|
-
+ '; rebuilding ' + deleteArray.length + ' deletes from previous baseline');
|
|
450
|
+
console.info('deleteArray.length: ' + deleteArray.length);
|
|
439
451
|
return deleteArray;
|
|
440
452
|
}
|
|
441
453
|
async getItemFederatedIds( /*itemIds*/) {
|
|
@@ -473,6 +485,7 @@ class BaseProcessPublishAssortment {
|
|
|
473
485
|
return deleteItems;
|
|
474
486
|
}
|
|
475
487
|
getReleasedForDevelopmentItemAndFamilyIds(fullChange, deleteChanges) {
|
|
488
|
+
console.info('getReleasedForDevelopmentItemAndFamilyIds');
|
|
476
489
|
const releasedForDevelopmentItemIds = [];
|
|
477
490
|
const itemFamilySet = new Set();
|
|
478
491
|
const assortmentItemsArray = fullChange?.assortmentItems;
|
|
@@ -515,15 +528,50 @@ class BaseProcessPublishAssortment {
|
|
|
515
528
|
}
|
|
516
529
|
}
|
|
517
530
|
}
|
|
531
|
+
if (this.publishReleasedFamilyWithPreDevelopmentOptions()) {
|
|
532
|
+
this.addReleasedFamiliesOfPreDevelopmentOptions(assortmentItemsArray, releasedForDevelopmentItemIds);
|
|
533
|
+
this.addReleasedFamiliesOfPreDevelopmentOptions(deleteChanges, releasedForDevelopmentItemIds);
|
|
534
|
+
}
|
|
518
535
|
console.info('releasedForDevelopmentItemIds: ' + releasedForDevelopmentItemIds);
|
|
519
536
|
return releasedForDevelopmentItemIds;
|
|
520
537
|
}
|
|
538
|
+
/** Adds item families that pass meetsCriteria() but whose option rows are all pre-development.
|
|
539
|
+
* Only ever ADDS family ids - never option ids, and never removes anything. */
|
|
540
|
+
addReleasedFamiliesOfPreDevelopmentOptions(aItems, releasedForDevelopmentItemIds) {
|
|
541
|
+
for (const aItem of (aItems || [])) {
|
|
542
|
+
const item = aItem?.item;
|
|
543
|
+
const itemId = item?.id;
|
|
544
|
+
const itemFamilyId = item?.itemFamilyId;
|
|
545
|
+
if (!itemFamilyId || itemId === itemFamilyId) {
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (releasedForDevelopmentItemIds.includes(itemFamilyId)) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (this.meetsCriteria(aItem)) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
const familyItem = item?.itemFamily;
|
|
555
|
+
if (familyItem && this.meetsCriteria({ item: familyItem })) {
|
|
556
|
+
console.info('adding released family with pre-development options: ' + itemFamilyId);
|
|
557
|
+
releasedForDevelopmentItemIds.push(itemFamilyId);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
publishReleasedFamilyWithPreDevelopmentOptions() {
|
|
562
|
+
return config_defaults_1.ConfigDefaults.isPropertyTrue(this.config?.publishReleasedFamilyWithPreDevelopmentOptions);
|
|
563
|
+
}
|
|
564
|
+
alwaysPublishReleasedFamilyWithPreDevelopmentOptions() {
|
|
565
|
+
return this.publishReleasedFamilyWithPreDevelopmentOptions()
|
|
566
|
+
&& config_defaults_1.ConfigDefaults.isPropertyTrue(this.config?.alwaysPublishReleasedFamilyWithPreDevelopmentOptions);
|
|
567
|
+
}
|
|
521
568
|
meetsCriteria(aItem) {
|
|
522
569
|
const item = aItem?.item;
|
|
523
570
|
const lifecycleStage = item?.lifecycleStage;
|
|
524
571
|
return !!lifecycleStage && !this.config.itemPreDevelopmentLifecycleStages.includes(lifecycleStage);
|
|
525
572
|
}
|
|
526
573
|
async processPublish(pcd, changeDetail, fullChange, deleteChanges) {
|
|
574
|
+
console.info('processPublish-start');
|
|
527
575
|
const event = {
|
|
528
576
|
assortmentId: pcd.assortmentId,
|
|
529
577
|
assortmentPublishChangeId: pcd.assortmentPublishChangeId
|
|
@@ -707,6 +755,7 @@ class BaseProcessPublishAssortment {
|
|
|
707
755
|
+ '-' + d.getUTCMilliseconds();
|
|
708
756
|
}
|
|
709
757
|
getItemFamilyChanges(pcd, changeDetail, assortmentItemFullChangeMap, assortmentItemDeleteMap) {
|
|
758
|
+
console.info('getItemFamilyChanges-start');
|
|
710
759
|
const itemFamilyChanges = new Map();
|
|
711
760
|
const { adds, deletes, updates, familyItemsRemoved } = changeDetail;
|
|
712
761
|
const addIds = adds.map(item => item.id);
|
|
@@ -716,7 +765,12 @@ class BaseProcessPublishAssortment {
|
|
|
716
765
|
for (const [itemId, aItem] of assortmentItemFullChangeMap) {
|
|
717
766
|
const projectItem = aItem?.projectItem;
|
|
718
767
|
const itemFamilyId = aItem?.item?.itemFamilyId;
|
|
719
|
-
|
|
768
|
+
const familyInScope = pcd.releasedForDevelopmentItemIds.includes(itemFamilyId);
|
|
769
|
+
const itemInScope = pcd.releasedForDevelopmentItemIds.includes(itemId);
|
|
770
|
+
// Released family + pre-development option: track at family level only (no SKU events).
|
|
771
|
+
const familyOnly = !itemInScope && familyInScope && itemId !== itemFamilyId
|
|
772
|
+
&& this.publishReleasedFamilyWithPreDevelopmentOptions();
|
|
773
|
+
if (!familyInScope || (!itemInScope && !familyOnly)) {
|
|
720
774
|
continue;
|
|
721
775
|
}
|
|
722
776
|
const ifc = itemFamilyChanges.get(itemFamilyId) || new item_family_changes_1.ItemFamilyChanges(itemFamilyId, pcd.sinceDate);
|
|
@@ -724,10 +778,29 @@ class BaseProcessPublishAssortment {
|
|
|
724
778
|
ifc.itemFamilyObject = itemId === itemFamilyId ? aItem?.item : aItem?.item?.itemFamily;
|
|
725
779
|
itemFamilyChanges.set(itemFamilyId, ifc);
|
|
726
780
|
}
|
|
781
|
+
else if (itemId === itemFamilyId && this.publishReleasedFamilyWithPreDevelopmentOptions()) {
|
|
782
|
+
// A pre-development option row may have created this bucket with the partial
|
|
783
|
+
// item.itemFamily; the family's own row carries the full item, so prefer it.
|
|
784
|
+
ifc.itemFamilyObject = aItem?.item;
|
|
785
|
+
}
|
|
727
786
|
ifc.assortmentItemFullChangeMap.set(itemId, aItem);
|
|
728
787
|
if (pcd.itemToFederatedIdMapping.has(itemId)) {
|
|
729
788
|
ifc.itemToFederatedIdMapping.set(itemId, pcd.itemToFederatedIdMapping.get(itemId));
|
|
730
789
|
}
|
|
790
|
+
if (familyOnly) {
|
|
791
|
+
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
792
|
+
if (addIds.includes(itemId) || deleteIds.includes(itemId) || updateIds.includes(itemId)
|
|
793
|
+
|| (projectItem && new Date(projectItem.updatedOn) > pcd.sinceDate)) {
|
|
794
|
+
ifc.preDevelopmentOptionChanges.push(itemId);
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
ifc.colorUnchanged.push(itemId);
|
|
798
|
+
}
|
|
799
|
+
if (projectItem && !projectItem?.roles && aItem?.item?.roles) {
|
|
800
|
+
projectItem.roles = aItem?.item?.roles;
|
|
801
|
+
}
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
731
804
|
if (itemId === itemFamilyId) {
|
|
732
805
|
if (addIds.includes(itemId))
|
|
733
806
|
ifc.familyAdd = true;
|
|
@@ -766,12 +839,17 @@ class BaseProcessPublishAssortment {
|
|
|
766
839
|
for (const [itemId, delEntity] of assortmentItemDeleteMap) {
|
|
767
840
|
const itemFamilyId = delEntity?.item?.itemFamilyId;
|
|
768
841
|
const item = delEntity?.item;
|
|
769
|
-
|
|
842
|
+
const familyInScope = pcd.releasedForDevelopmentItemIds.includes(itemFamilyId);
|
|
843
|
+
const itemInScope = pcd.releasedForDevelopmentItemIds.includes(itemId);
|
|
844
|
+
const familyOnly = !itemInScope && familyInScope && itemId !== itemFamilyId
|
|
845
|
+
&& this.publishReleasedFamilyWithPreDevelopmentOptions();
|
|
846
|
+
if (!familyInScope || (!itemInScope && !familyOnly)) {
|
|
770
847
|
continue;
|
|
771
848
|
}
|
|
772
849
|
else {
|
|
773
850
|
if (!item) {
|
|
774
|
-
console.error('Failed to find deleted item entity
|
|
851
|
+
console.error('Failed to find deleted item entity');
|
|
852
|
+
console.error(' itemFamilyId: ' + itemFamilyId + ' -itemId: ' + itemId);
|
|
775
853
|
continue;
|
|
776
854
|
}
|
|
777
855
|
}
|
|
@@ -795,11 +873,15 @@ class BaseProcessPublishAssortment {
|
|
|
795
873
|
if (itemId === itemFamilyId) {
|
|
796
874
|
ifc.familyDelete = true;
|
|
797
875
|
}
|
|
876
|
+
else if (familyOnly) {
|
|
877
|
+
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
878
|
+
ifc.preDevelopmentOptionChanges.push(itemId);
|
|
879
|
+
}
|
|
798
880
|
else {
|
|
799
881
|
ifc.colorDeletes.push(itemId);
|
|
800
882
|
}
|
|
801
883
|
} //End deletes for loop
|
|
802
|
-
if (Logger.isDebugOn()) {
|
|
884
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
803
885
|
console.debug('returning size: ' + itemFamilyChanges.size);
|
|
804
886
|
for (const [key, value] of itemFamilyChanges) {
|
|
805
887
|
console.debug(key + ' => ' + value);
|
|
@@ -808,6 +890,7 @@ class BaseProcessPublishAssortment {
|
|
|
808
890
|
return itemFamilyChanges;
|
|
809
891
|
}
|
|
810
892
|
async getEventsForPublishChangeData(publishChangeData) {
|
|
893
|
+
console.info('getEventsForPublishChangeData-start');
|
|
811
894
|
const seasonalPayloads = [];
|
|
812
895
|
for (const itemFamilyChange of publishChangeData.itemFamilyChanges.values()) {
|
|
813
896
|
const events = await this.getEventsForItemFamilyChanges(itemFamilyChange, publishChangeData.assortmentId, publishChangeData.seasonFed, publishChangeData.itemToFederatedIdMapping);
|
|
@@ -815,8 +898,6 @@ class BaseProcessPublishAssortment {
|
|
|
815
898
|
seasonalPayloads.push(...events);
|
|
816
899
|
}
|
|
817
900
|
}
|
|
818
|
-
console.info('getEventsForPublishChangeData: itemFamilies: '
|
|
819
|
-
+ publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
|
|
820
901
|
return seasonalPayloads;
|
|
821
902
|
}
|
|
822
903
|
/**Returns the events for a given ItemFamilyChanges object
|
|
@@ -836,12 +917,15 @@ class BaseProcessPublishAssortment {
|
|
|
836
917
|
* @returns
|
|
837
918
|
*/
|
|
838
919
|
async getEventsForItemFamilyChanges(itemFamilyChanges, assortmentId, seasonFed, itemToFederatedIdMapping) {
|
|
920
|
+
console.info('getEventsForItemFamilyChanges()');
|
|
839
921
|
const events = [];
|
|
840
922
|
const LCSSeason = Object.assign({}, seasonFed);
|
|
841
923
|
const assortment = await this.getAssortment(assortmentId);
|
|
842
924
|
const projectItem = this.getProjectItem(itemFamilyChanges, itemFamilyChanges.itemFamilyId);
|
|
843
925
|
const familyAssortmentItem = itemFamilyChanges.familyAdd || itemFamilyChanges.familyUpdate || itemFamilyChanges.familyDelete
|
|
844
926
|
|| itemFamilyChanges.colorAdds.length > 0 || itemFamilyChanges.colorUpdates.length > 0 || itemFamilyChanges.colorDeletes.length > 0
|
|
927
|
+
|| itemFamilyChanges.preDevelopmentOptionChanges.length > 0
|
|
928
|
+
|| (this.alwaysPublishReleasedFamilyWithPreDevelopmentOptions() && itemFamilyChanges.hasPreDevelopmentOnlyOptions)
|
|
845
929
|
|| (projectItem && this.updatedSinceDate(projectItem, itemFamilyChanges.sinceDate));
|
|
846
930
|
//familyItemRemoved is used when adding the first option to an assortment
|
|
847
931
|
//and will have updates for the family item.
|
|
@@ -1874,3 +1874,259 @@ describe('sendToFlexPLM / handleVibeIQFile / sendPublishPayloadEvent', () => {
|
|
|
1874
1874
|
});
|
|
1875
1875
|
});
|
|
1876
1876
|
});
|
|
1877
|
+
describe('publishReleasedFamilyWithPreDevelopmentOptions', () => {
|
|
1878
|
+
const FAMILY_ID = 'AERfdvdAt1HHEVvQ';
|
|
1879
|
+
const LIME_ID = 'limeGreenItemId__';
|
|
1880
|
+
const MAROON_ID = 'maroonItemId_____';
|
|
1881
|
+
const SINCE = new Date('2026-09-03T16:54:20.778Z');
|
|
1882
|
+
const BEFORE_SINCE = '2026-09-01T00:00:00.000Z';
|
|
1883
|
+
const AFTER_SINCE = '2026-09-03T17:00:00.000Z';
|
|
1884
|
+
/** The full family item, as it appears on the family's own assortment row. */
|
|
1885
|
+
const fullFamilyItem = {
|
|
1886
|
+
id: FAMILY_ID, itemFamilyId: FAMILY_ID, name: 'Sweatpant', itemNumber: 11,
|
|
1887
|
+
lifecycleStage: 'released', roles: ['family'], richFamilyOnlyField: 'PRESENT'
|
|
1888
|
+
};
|
|
1889
|
+
/** The partial family item, as hydrated onto an option row. */
|
|
1890
|
+
const partialFamilyItem = {
|
|
1891
|
+
id: FAMILY_ID, itemFamilyId: FAMILY_ID, name: 'Sweatpant', itemNumber: 11,
|
|
1892
|
+
lifecycleStage: 'released', roles: ['family']
|
|
1893
|
+
};
|
|
1894
|
+
const familyRow = () => ({
|
|
1895
|
+
entityType: 'assortment-item',
|
|
1896
|
+
id: 'ai-' + FAMILY_ID,
|
|
1897
|
+
itemId: FAMILY_ID,
|
|
1898
|
+
item: { ...fullFamilyItem },
|
|
1899
|
+
projectItem: { id: 'proj:' + FAMILY_ID, itemId: FAMILY_ID, updatedOn: BEFORE_SINCE, roles: ['family'] }
|
|
1900
|
+
});
|
|
1901
|
+
const optionRow = (itemId, optionName, projectItemUpdatedOn) => ({
|
|
1902
|
+
entityType: 'assortment-item',
|
|
1903
|
+
id: 'ai-' + itemId,
|
|
1904
|
+
itemId,
|
|
1905
|
+
item: {
|
|
1906
|
+
id: itemId, itemFamilyId: FAMILY_ID, name: 'Sweatpant', optionName,
|
|
1907
|
+
optionGroup: 'color', lifecycleStage: 'concept', roles: ['color', 'option'],
|
|
1908
|
+
itemFamily: { ...partialFamilyItem }
|
|
1909
|
+
},
|
|
1910
|
+
projectItem: { id: 'proj:' + itemId, itemId, updatedOn: projectItemUpdatedOn, roles: ['color', 'option'] },
|
|
1911
|
+
familyProjectItem: { id: 'proj:' + FAMILY_ID, itemId: FAMILY_ID, updatedOn: BEFORE_SINCE, roles: ['family'] }
|
|
1912
|
+
});
|
|
1913
|
+
const emptyDetail = { adds: [], deletes: [], updates: [], familyItemsRemoved: [] };
|
|
1914
|
+
const baseConfig = { identifierAtts: { LCSProduct: ['itemNumber'] }, itemPreDevelopmentLifecycleStages: ['concept'] };
|
|
1915
|
+
const makePpa2 = (flagOn, alwaysOn) => {
|
|
1916
|
+
const cfg = {
|
|
1917
|
+
...baseConfig,
|
|
1918
|
+
publishReleasedFamilyWithPreDevelopmentOptions: flagOn,
|
|
1919
|
+
alwaysPublishReleasedFamilyWithPreDevelopmentOptions: alwaysOn
|
|
1920
|
+
};
|
|
1921
|
+
const mfu = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
1922
|
+
return new base_process_publish_assortment_1.BaseProcessPublishAssortment(cfg, new data_converter_1.DataConverter(cfg, mfu), mfu);
|
|
1923
|
+
};
|
|
1924
|
+
const makePpa = (flagOn) => makePpa2(flagOn, false);
|
|
1925
|
+
const makePcd = (releasedIds) => {
|
|
1926
|
+
const sf = { entityReference: 'assortment:11EZML5dMn3tREuT', objectClass: 'LCSSeason' };
|
|
1927
|
+
const pcd = new publish_change_data_1.PublishChangeData('11EZML5dMn3tREuT', sf, 'apc1', SINCE);
|
|
1928
|
+
pcd.itemToFederatedIdMapping = new Map();
|
|
1929
|
+
pcd.releasedForDevelopmentItemIds = releasedIds;
|
|
1930
|
+
return pcd;
|
|
1931
|
+
};
|
|
1932
|
+
const eventSeasonFed = { entityReference: 'assortment:a1', objectClass: 'LCSSeason' };
|
|
1933
|
+
const stubEventDeps = (ppa) => {
|
|
1934
|
+
jest.spyOn(ppa, 'getAssortment').mockResolvedValue({ id: 'a1', publishToFlexPLM: true });
|
|
1935
|
+
jest.spyOn(ppa, 'getSeasonalData').mockResolvedValue({});
|
|
1936
|
+
jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockImplementation(async (...args) => args[2]);
|
|
1937
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['itemNumber']);
|
|
1938
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
|
|
1939
|
+
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(true);
|
|
1940
|
+
};
|
|
1941
|
+
afterEach(() => { jest.restoreAllMocks(); });
|
|
1942
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
1943
|
+
// getReleasedForDevelopmentItemAndFamilyIds
|
|
1944
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
1945
|
+
it('should keep a released family out of scope when all options are pre-development and the flag is off', () => {
|
|
1946
|
+
const fullChange = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE),
|
|
1947
|
+
optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE)] };
|
|
1948
|
+
expect(makePpa(false).getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toEqual([]);
|
|
1949
|
+
});
|
|
1950
|
+
it('flag on - released family enters scope; concept options do NOT', () => {
|
|
1951
|
+
const fullChange = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE),
|
|
1952
|
+
optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE)] };
|
|
1953
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toEqual([FAMILY_ID]);
|
|
1954
|
+
});
|
|
1955
|
+
it('flag on - concept family with concept options still stays out of scope', () => {
|
|
1956
|
+
const row = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
1957
|
+
row.item.itemFamily = { ...partialFamilyItem, lifecycleStage: 'concept' };
|
|
1958
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems: [row] }, [])).toEqual([]);
|
|
1959
|
+
});
|
|
1960
|
+
it('flag on - family reached only through a delete record', () => {
|
|
1961
|
+
const deleted = optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE);
|
|
1962
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems: [] }, [deleted]))
|
|
1963
|
+
.toEqual([FAMILY_ID]);
|
|
1964
|
+
});
|
|
1965
|
+
it('flag on - two concept options of the same family add the family only once', () => {
|
|
1966
|
+
const fullChange = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE),
|
|
1967
|
+
optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE)] };
|
|
1968
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toHaveLength(1);
|
|
1969
|
+
});
|
|
1970
|
+
it('flag on - option row with no hydrated itemFamily is skipped without throwing', () => {
|
|
1971
|
+
const bare = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
1972
|
+
delete bare.item.itemFamily;
|
|
1973
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems: [bare] }, [])).toEqual([]);
|
|
1974
|
+
});
|
|
1975
|
+
it('released family WITH its own assortment row is in scope with or without the flag', () => {
|
|
1976
|
+
const rows = { assortmentItems: [familyRow(), optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE)] };
|
|
1977
|
+
expect(makePpa(false).getReleasedForDevelopmentItemAndFamilyIds(rows, [])).toEqual([FAMILY_ID]);
|
|
1978
|
+
expect(makePpa(true).getReleasedForDevelopmentItemAndFamilyIds(rows, [])).toEqual([FAMILY_ID]);
|
|
1979
|
+
});
|
|
1980
|
+
it('string flag values from app config are honoured', () => {
|
|
1981
|
+
const fullChange = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE)] };
|
|
1982
|
+
expect(makePpa('true').getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toEqual([FAMILY_ID]);
|
|
1983
|
+
expect(makePpa('false').getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toEqual([]);
|
|
1984
|
+
});
|
|
1985
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
1986
|
+
// getItemFamilyChanges
|
|
1987
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
1988
|
+
it('ACTION 2: deleting a concept option marks the released family for republish', () => {
|
|
1989
|
+
const ppa = makePpa(true);
|
|
1990
|
+
const remaining = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
1991
|
+
const deleted = optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE);
|
|
1992
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
1993
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, ppa.getFullChangeAssortmentMap({ assortmentItems: [remaining] }), ppa.getDeleteChangesAssortmentMap([deleted]));
|
|
1994
|
+
expect(pcd.itemFamilyChanges.size).toEqual(1);
|
|
1995
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
1996
|
+
expect(ifc.preDevelopmentOptionChanges).toEqual([MAROON_ID]);
|
|
1997
|
+
expect(ifc.hasPreDevelopmentOnlyOptions).toBe(true);
|
|
1998
|
+
expect(ifc.colorDeletes).toEqual([]);
|
|
1999
|
+
expect(ifc.colorAdds).toEqual([]);
|
|
2000
|
+
expect(ifc.itemFamilyObject.id).toEqual(FAMILY_ID);
|
|
2001
|
+
});
|
|
2002
|
+
it('adding a concept option marks the family, not a colorAdd', () => {
|
|
2003
|
+
const ppa = makePpa(true);
|
|
2004
|
+
const added = optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE);
|
|
2005
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2006
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, { ...emptyDetail, adds: [{ id: MAROON_ID }] }, ppa.getFullChangeAssortmentMap({ assortmentItems: [added] }), new Map());
|
|
2007
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2008
|
+
expect(ifc.preDevelopmentOptionChanges).toEqual([MAROON_ID]);
|
|
2009
|
+
expect(ifc.colorAdds).toEqual([]);
|
|
2010
|
+
});
|
|
2011
|
+
it('ACTION 1: nothing changed - family bucket exists but signals no change', () => {
|
|
2012
|
+
const ppa = makePpa(true);
|
|
2013
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2014
|
+
const rows = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE),
|
|
2015
|
+
optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE)] };
|
|
2016
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, ppa.getFullChangeAssortmentMap(rows), new Map());
|
|
2017
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2018
|
+
expect(ifc.preDevelopmentOptionChanges).toEqual([]);
|
|
2019
|
+
expect(ifc.colorUnchanged.sort()).toEqual([LIME_ID, MAROON_ID].sort());
|
|
2020
|
+
});
|
|
2021
|
+
it('REGRESSION: flag off - concept option rows produce no ItemFamilyChanges at all', () => {
|
|
2022
|
+
const ppa = makePpa(false);
|
|
2023
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2024
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, ppa.getFullChangeAssortmentMap({ assortmentItems: [optionRow(LIME_ID, 'Lime Green', AFTER_SINCE)] }), new Map());
|
|
2025
|
+
expect(pcd.itemFamilyChanges.size).toEqual(0);
|
|
2026
|
+
});
|
|
2027
|
+
it('REGRESSION: a released option is still a colorAdd, never a preDevelopmentOptionChange', () => {
|
|
2028
|
+
const ppa = makePpa(true);
|
|
2029
|
+
const released = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
2030
|
+
released.item.lifecycleStage = 'development';
|
|
2031
|
+
const pcd = makePcd([FAMILY_ID, LIME_ID]);
|
|
2032
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, { ...emptyDetail, adds: [{ id: LIME_ID }] }, ppa.getFullChangeAssortmentMap({ assortmentItems: [released] }), new Map());
|
|
2033
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2034
|
+
expect(ifc.colorAdds).toEqual([LIME_ID]);
|
|
2035
|
+
expect(ifc.preDevelopmentOptionChanges).toEqual([]);
|
|
2036
|
+
expect(ifc.hasPreDevelopmentOnlyOptions).toBe(false);
|
|
2037
|
+
});
|
|
2038
|
+
it('itemFamilyObject prefers the full family item when the OPTION row is iterated first', () => {
|
|
2039
|
+
const ppa = makePpa(true);
|
|
2040
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2041
|
+
const map = new Map();
|
|
2042
|
+
map.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2043
|
+
map.set(FAMILY_ID, familyRow());
|
|
2044
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, map, new Map());
|
|
2045
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2046
|
+
expect(ifc.itemFamilyObject.richFamilyOnlyField).toEqual('PRESENT');
|
|
2047
|
+
});
|
|
2048
|
+
it('itemFamilyObject is the full family item when the FAMILY row is iterated first', () => {
|
|
2049
|
+
const ppa = makePpa(true);
|
|
2050
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2051
|
+
const map = new Map();
|
|
2052
|
+
map.set(FAMILY_ID, familyRow());
|
|
2053
|
+
map.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2054
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, map, new Map());
|
|
2055
|
+
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2056
|
+
expect(ifc.itemFamilyObject.richFamilyOnlyField).toEqual('PRESENT');
|
|
2057
|
+
});
|
|
2058
|
+
it('delete record with a missing item entity is skipped safely', () => {
|
|
2059
|
+
const ppa = makePpa(true);
|
|
2060
|
+
const pcd = makePcd([FAMILY_ID]);
|
|
2061
|
+
const delMap = new Map();
|
|
2062
|
+
delMap.set('GHOST', { itemId: 'GHOST', item: undefined });
|
|
2063
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, new Map(), delMap);
|
|
2064
|
+
expect(pcd.itemFamilyChanges.size).toEqual(0);
|
|
2065
|
+
});
|
|
2066
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
2067
|
+
// getEventsForItemFamilyChanges
|
|
2068
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
2069
|
+
it('emits exactly one LCSProductSeasonLink and zero LCSSKUSeasonLink', async () => {
|
|
2070
|
+
const ppa = makePpa(true);
|
|
2071
|
+
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2072
|
+
ifc.itemFamilyObject = fullFamilyItem;
|
|
2073
|
+
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
2074
|
+
ifc.preDevelopmentOptionChanges.push(MAROON_ID);
|
|
2075
|
+
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2076
|
+
stubEventDeps(ppa);
|
|
2077
|
+
const events = await ppa.getEventsForItemFamilyChanges(ifc, 'a1', eventSeasonFed, new Map());
|
|
2078
|
+
expect(events).toHaveLength(1);
|
|
2079
|
+
expect(events[0].objectClass).toBe('LCSProductSeasonLink');
|
|
2080
|
+
expect(events[0].eventType).toBe('UPSERT_ON_SEASON');
|
|
2081
|
+
expect(events[0].entityReference).toBe('item:' + FAMILY_ID);
|
|
2082
|
+
expect(events[0].LCSProduct['itemNumber']).toBe(11);
|
|
2083
|
+
});
|
|
2084
|
+
it('REGRESSION: an ItemFamilyChanges with no changes still emits nothing', async () => {
|
|
2085
|
+
const ppa = makePpa(true);
|
|
2086
|
+
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2087
|
+
ifc.itemFamilyObject = fullFamilyItem;
|
|
2088
|
+
ifc.colorUnchanged.push(LIME_ID, MAROON_ID);
|
|
2089
|
+
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2090
|
+
stubEventDeps(ppa);
|
|
2091
|
+
expect(await ppa.getEventsForItemFamilyChanges(ifc, 'a1', eventSeasonFed, new Map())).toHaveLength(0);
|
|
2092
|
+
});
|
|
2093
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
2094
|
+
// alwaysPublishReleasedFamilyWithPreDevelopmentOptions
|
|
2095
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
2096
|
+
const unchangedFamilyIfc = () => {
|
|
2097
|
+
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2098
|
+
ifc.itemFamilyObject = fullFamilyItem;
|
|
2099
|
+
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
2100
|
+
ifc.colorUnchanged.push(LIME_ID, MAROON_ID);
|
|
2101
|
+
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2102
|
+
return ifc;
|
|
2103
|
+
};
|
|
2104
|
+
it('ACTION 1 with always-flag: unchanged family still emits one LCSProductSeasonLink', async () => {
|
|
2105
|
+
const ppa = makePpa2(true, true);
|
|
2106
|
+
stubEventDeps(ppa);
|
|
2107
|
+
const events = await ppa.getEventsForItemFamilyChanges(unchangedFamilyIfc(), 'a1', eventSeasonFed, new Map());
|
|
2108
|
+
expect(events).toHaveLength(1);
|
|
2109
|
+
expect(events[0].objectClass).toBe('LCSProductSeasonLink');
|
|
2110
|
+
});
|
|
2111
|
+
it('always-flag off: unchanged family emits nothing', async () => {
|
|
2112
|
+
const ppa = makePpa2(true, false);
|
|
2113
|
+
stubEventDeps(ppa);
|
|
2114
|
+
expect(await ppa.getEventsForItemFamilyChanges(unchangedFamilyIfc(), 'a1', eventSeasonFed, new Map()))
|
|
2115
|
+
.toHaveLength(0);
|
|
2116
|
+
});
|
|
2117
|
+
it('always-flag cannot act alone when the primary flag is off', async () => {
|
|
2118
|
+
const ppa = makePpa2(false, true);
|
|
2119
|
+
stubEventDeps(ppa);
|
|
2120
|
+
expect(await ppa.getEventsForItemFamilyChanges(unchangedFamilyIfc(), 'a1', eventSeasonFed, new Map()))
|
|
2121
|
+
.toHaveLength(0);
|
|
2122
|
+
});
|
|
2123
|
+
it('REGRESSION: always-flag does not republish an ordinary unchanged released family', async () => {
|
|
2124
|
+
const ppa = makePpa2(true, true);
|
|
2125
|
+
stubEventDeps(ppa);
|
|
2126
|
+
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2127
|
+
ifc.itemFamilyObject = fullFamilyItem;
|
|
2128
|
+
ifc.colorUnchanged.push(LIME_ID);
|
|
2129
|
+
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2130
|
+
expect(await ppa.getEventsForItemFamilyChanges(ifc, 'a1', eventSeasonFed, new Map())).toHaveLength(0);
|
|
2131
|
+
});
|
|
2132
|
+
});
|
|
@@ -20,6 +20,8 @@ export declare class ConfigDefaults {
|
|
|
20
20
|
processAsItem: boolean;
|
|
21
21
|
};
|
|
22
22
|
useDistinctRestEndPointForImages: boolean;
|
|
23
|
+
publishReleasedFamilyWithPreDevelopmentOptions: boolean;
|
|
24
|
+
alwaysPublishReleasedFamilyWithPreDevelopmentOptions: boolean;
|
|
23
25
|
csrfEndpoint: string;
|
|
24
26
|
vibeEventEndpoint: string;
|
|
25
27
|
payloadDefaultAsArray: boolean;
|
|
@@ -67,6 +67,8 @@ class ConfigDefaults {
|
|
|
67
67
|
processAsItem: false,
|
|
68
68
|
},
|
|
69
69
|
useDistinctRestEndPointForImages: false,
|
|
70
|
+
publishReleasedFamilyWithPreDevelopmentOptions: false,
|
|
71
|
+
alwaysPublishReleasedFamilyWithPreDevelopmentOptions: false,
|
|
70
72
|
csrfEndpoint: '/servlet/rest/security/csrf',
|
|
71
73
|
vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
|
|
72
74
|
payloadDefaultAsArray: true,
|
|
@@ -393,3 +393,50 @@ describe('all tests', () => {
|
|
|
393
393
|
});
|
|
394
394
|
});
|
|
395
395
|
});
|
|
396
|
+
describe('publishReleasedFamilyWithPreDevelopmentOptions config', () => {
|
|
397
|
+
const config = {
|
|
398
|
+
apiHost: 'http://test.com',
|
|
399
|
+
userName: 'vibeiq',
|
|
400
|
+
password: 'vibeiq',
|
|
401
|
+
plmEnviornment: 'SB'
|
|
402
|
+
};
|
|
403
|
+
it('publishReleasedFamilyWithPreDevelopmentOptions-get default', async () => {
|
|
404
|
+
const startConfig = Object.assign({}, config);
|
|
405
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
406
|
+
expect(fcConfig['publishReleasedFamilyWithPreDevelopmentOptions']).toBe(false);
|
|
407
|
+
});
|
|
408
|
+
it('alwaysPublishReleasedFamilyWithPreDevelopmentOptions-get default', async () => {
|
|
409
|
+
const startConfig = Object.assign({}, config);
|
|
410
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
411
|
+
expect(fcConfig['alwaysPublishReleasedFamilyWithPreDevelopmentOptions']).toBe(false);
|
|
412
|
+
});
|
|
413
|
+
it('publishReleasedFamilyWithPreDevelopmentOptions-set boolean true', async () => {
|
|
414
|
+
const startConfig = Object.assign({}, config);
|
|
415
|
+
startConfig['publishReleasedFamilyWithPreDevelopmentOptions'] = true;
|
|
416
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
417
|
+
expect(config_defaults_1.ConfigDefaults.isPropertyTrue(fcConfig['publishReleasedFamilyWithPreDevelopmentOptions'])).toBe(true);
|
|
418
|
+
});
|
|
419
|
+
it('publishReleasedFamilyWithPreDevelopmentOptions-set string true from app config', async () => {
|
|
420
|
+
const startConfig = Object.assign({}, config);
|
|
421
|
+
startConfig['publishReleasedFamilyWithPreDevelopmentOptions'] = 'true';
|
|
422
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
423
|
+
expect(config_defaults_1.ConfigDefaults.isPropertyTrue(fcConfig['publishReleasedFamilyWithPreDevelopmentOptions'])).toBe(true);
|
|
424
|
+
});
|
|
425
|
+
it('publishReleasedFamilyWithPreDevelopmentOptions-string false stays off', async () => {
|
|
426
|
+
const startConfig = Object.assign({}, config);
|
|
427
|
+
startConfig['publishReleasedFamilyWithPreDevelopmentOptions'] = 'false';
|
|
428
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
429
|
+
expect(config_defaults_1.ConfigDefaults.isPropertyTrue(fcConfig['publishReleasedFamilyWithPreDevelopmentOptions'])).toBe(false);
|
|
430
|
+
});
|
|
431
|
+
it('both flags can be delivered through complexConfig', async () => {
|
|
432
|
+
const startConfig = Object.assign({}, config, {
|
|
433
|
+
complexConfig: {
|
|
434
|
+
publishReleasedFamilyWithPreDevelopmentOptions: true,
|
|
435
|
+
alwaysPublishReleasedFamilyWithPreDevelopmentOptions: 'true'
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
|
|
439
|
+
expect(config_defaults_1.ConfigDefaults.isPropertyTrue(fcConfig['publishReleasedFamilyWithPreDevelopmentOptions'])).toBe(true);
|
|
440
|
+
expect(config_defaults_1.ConfigDefaults.isPropertyTrue(fcConfig['alwaysPublishReleasedFamilyWithPreDevelopmentOptions'])).toBe(true);
|
|
441
|
+
});
|
|
442
|
+
});
|
|
@@ -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.858e2cc",
|
|
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
|
+
}
|