@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.
@@ -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: assortmentId: ' + assortmentId
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.error('getPublishInfo failed: ' + e2.message);
77
+ console.log('catch e2: ' + e2.message);
79
78
  }
80
- console.error('process failed: assortmentId: ' + assortmentId + ': ' + e.message);
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) => { return Date.parse(a) - Date.parse(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.error('Error hydratedDetailDownloadLink: ' + e.message);
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.error('Error assortmentBaselineDownloadLink: ' + e.message);
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
- console.info('getDeleteChanges(): apc: ' + assortmentPublishChange?.id
352
- + ', index: ' + currentAPCIndex + ', previousApcCreatedOn: ' + apcCreatedOn);
353
- //if only 1 apc, no processing needed
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
- scannedCount++;
382
+ console.info(dItemId);
375
383
  if (!currentAssortmentItemIds.includes(dItemId) && !deleteIds.includes(dItemId)) {
376
- addedCount++;
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(): baselineItems: ' + currentAssortmentItemIds.length
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.error('Error deleteDataDownloadLink: ' + e.message);
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('buildDeleteChanges(): no deleteDataDownloadLink on apc ' + apc?.id
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
- if (!itemFamilySet.has(itemFamilyId) && itemId !== itemFamilyId) {
488
- const familyItem = item?.itemFamily;
489
- const familyItemMeetsCriteria = this.meetsCriteria({ item: familyItem });
490
- if (familyItemMeetsCriteria) {
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) || !pcd.releasedForDevelopmentItemIds.includes(itemId)) {
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: itemFamilyId: ' + itemFamilyId + ', itemId: ' + itemId);
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 is used when adding the first option to an assortment
848
- //and will have updates for the family item.
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 = undefined;
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.a05d6ab",
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
+ }