@contrail/flexplm 1.7.4-alpha.b088cfc → 1.7.4-alpha.b869ea5

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,7 +12,6 @@ export declare class ItemFamilyChanges {
12
12
  colorDeletes: string[];
13
13
  colorUpdates: string[];
14
14
  colorUnchanged: string[];
15
- colorSuppressed: string[];
16
15
  assortmentItemFullChangeMap: Map<string, any>;
17
16
  assortmentItemDeleteMap: Map<string, any>;
18
17
  itemToFederatedIdMapping: Map<string, string>;
@@ -14,7 +14,6 @@ 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
18
17
  this.assortmentItemFullChangeMap = new Map();
19
18
  this.assortmentItemDeleteMap = new Map();
20
19
  this.itemToFederatedIdMapping = new Map();
@@ -33,7 +32,6 @@ class ItemFamilyChanges {
33
32
  s += ' colorDeletes: [' + this.colorDeletes + ']\n';
34
33
  s += ' colorUpdates: [' + this.colorUpdates + ']\n';
35
34
  s += ' colorUnchanged: [' + this.colorUnchanged + ']\n';
36
- s += ' colorSuppressed: [' + this.colorSuppressed + ']\n';
37
35
  s += 'assortmentItemFullChangeMap:\n';
38
36
  s += 'size: ' + this.assortmentItemFullChangeMap.size + '\n[';
39
37
  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;
@@ -46,13 +46,13 @@ class BaseProcessPublishAssortment {
46
46
  console.log(message);
47
47
  const output = {
48
48
  results: { message, event },
49
- skip_await: true,
49
+ skip_await: true
50
50
  };
51
51
  return output;
52
52
  }
53
53
  apcHistory = await this.getApcHistory(assortmentId);
54
54
  const sinceDate = await this.getSinceDate(assortmentId, assortmentPublishChangeId, apcHistory);
55
- // Get detail information
55
+ //Get detail information
56
56
  const assortmentPublishChange = await this.downloadAssortmentPublishChange(assortmentId, assortmentPublishChangeId);
57
57
  publisher = this.getPublisher(assortmentPublishChange);
58
58
  const changeDetail = await this.downloadHydratedChangeDetail(assortmentPublishChange);
@@ -91,7 +91,7 @@ class BaseProcessPublishAssortment {
91
91
  versionComments: apc?.versionComments,
92
92
  publishDate: apc?.createdOn,
93
93
  publisher,
94
- versionHistoryNumber,
94
+ versionHistoryNumber
95
95
  };
96
96
  return publishInfo;
97
97
  }
@@ -111,15 +111,15 @@ class BaseProcessPublishAssortment {
111
111
  return 'unknown';
112
112
  }
113
113
  const createdOnDate = new Date(createdOnString);
114
- createdOnDate.setMonth(createdOnDate.getMonth() - 1); // subtract 1 month
114
+ createdOnDate.setMonth(createdOnDate.getMonth() - 1); //subtract 1 month
115
115
  const createdOnStringMinus1 = createdOnDate.toISOString();
116
116
  const createdOn = 'ISGREATERTHAN ' + createdOnStringMinus1;
117
117
  const snapshots = await new sdk_1.Entities().get({
118
118
  entityName: 'entity-snapshot',
119
119
  criteria: {
120
120
  entityReference,
121
- createdOn,
122
- },
121
+ createdOn
122
+ }
123
123
  });
124
124
  const assortmentPublishChangeId = apc?.id;
125
125
  const snapshot = snapshots.find(snap => snap?.assortmentChangeId === assortmentPublishChangeId);
@@ -169,7 +169,7 @@ class BaseProcessPublishAssortment {
169
169
  }
170
170
  this.assortment = await new sdk_1.Entities().get({
171
171
  entityName: 'assortment',
172
- id: assortmentId,
172
+ id: assortmentId
173
173
  });
174
174
  return this.assortment;
175
175
  }
@@ -214,9 +214,9 @@ class BaseProcessPublishAssortment {
214
214
  return sdk_1.Request.request(resource, {
215
215
  method: 'GET',
216
216
  headers: {
217
- 'Accept': 'application/json',
218
- 'Content-Type': 'application/json',
219
- },
217
+ Accept: 'application/json',
218
+ 'Content-Type': 'application/json'
219
+ }
220
220
  });
221
221
  }
222
222
  updatedSinceDate(entity, sinceDate) {
@@ -287,7 +287,7 @@ class BaseProcessPublishAssortment {
287
287
  }
288
288
  }
289
289
  }
290
- dateArray.sort((a, b) => Date.parse(a) - Date.parse(b));
290
+ dateArray.sort((a, b) => { return Date.parse(a) - Date.parse(b); });
291
291
  const arrIndex = (dateArray.length - pastPublishCount > 0) ? dateArray.length - pastPublishCount : 0;
292
292
  sinceDate = dateArray[arrIndex];
293
293
  return sinceDate;
@@ -308,9 +308,9 @@ class BaseProcessPublishAssortment {
308
308
  return await sdk_1.Request.request(resourceString, {
309
309
  method: 'GET',
310
310
  Headers: {
311
- 'Accept': 'application/json',
312
- 'Content-Type': 'application/json',
313
- },
311
+ Accept: 'application/json',
312
+ 'Content-Type': 'application/json'
313
+ }
314
314
  });
315
315
  }
316
316
  async downloadHydratedChangeDetail(assortmentPublishChange) {
@@ -358,7 +358,7 @@ class BaseProcessPublishAssortment {
358
358
  console.info('sinceDateMilliseconds: ' + sinceDateMilliseconds);
359
359
  console.info('apcDateMilliseconds: ' + previousApcDate);
360
360
  }
361
- // if only 1 apc, no processing needed
361
+ //if only 1 apc, no processing needed
362
362
  if (sinceDateMilliseconds !== previousApcDate) {
363
363
  console.info('sinceDateMilliseconds !== apcDateMilliseconds');
364
364
  const currentAssortmentItemIds = this.getBaselineItemIds(assortmentBaseline);
@@ -391,31 +391,6 @@ class BaseProcessPublishAssortment {
391
391
  console.info('getDeleteChanges()-currentAssortmentItemIds: ' + currentAssortmentItemIds);
392
392
  console.info('getDeleteChanges()-deleteIds: ' + deleteIds);
393
393
  }
394
- // ---- ET-243: a family row removed while the family still has option rows is reported
395
- // in neither the delete data nor the publish-change detail. Derive it by diffing the
396
- // baseline as of sinceDate against the current baseline. ----
397
- const priorApc = apcHistory.find(apc => apc?.id !== assortmentPublishChange?.id
398
- && Date.parse(apc?.createdOn) === sinceDateMilliseconds)
399
- || apcHistory[previousApcIndex];
400
- const priorBaseline = priorApc?.assortmentBaselineDownloadLink
401
- ? await this.downloadAssortmentBaseline(priorApc)
402
- : await this.downloadAssortmentBaseline(await this.downloadAssortmentPublishChange(priorApc?.assortmentId, priorApc?.id));
403
- const currentItemIds = this.getBaselineItemIds(assortmentBaseline);
404
- const collectedIds = apcDeletes.map(dItem => dItem?.itemId);
405
- for (const aItem of priorBaseline?.assortmentItems || []) {
406
- const itemId = aItem?.itemId;
407
- const item = aItem?.item;
408
- if (!itemId || !item)
409
- continue;
410
- if (itemId !== item?.itemFamilyId)
411
- continue; // family rows only
412
- if (currentItemIds.includes(itemId))
413
- continue; // still in the assortment
414
- if (collectedIds.includes(itemId))
415
- continue; // already reported as a delete
416
- console.info('getDeleteChanges(): adding removed family row: ' + itemId);
417
- apcDeletes.push(aItem);
418
- }
419
394
  return apcDeletes;
420
395
  }
421
396
  getBaselineItemIds(assortmentBaseline) {
@@ -469,7 +444,7 @@ class BaseProcessPublishAssortment {
469
444
  previousBaseline = await this.downloadAssortmentBaseline(previousApc);
470
445
  }
471
446
  const deleteIds = apc?.detail?.deletes.map(dItem => dItem?.id);
472
- // 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
473
448
  const deleteArray = previousBaseline?.assortmentItems.filter(aItem => deleteIds.includes(aItem?.itemId));
474
449
  console.info('deleteArray.length: ' + deleteArray.length);
475
450
  return deleteArray;
@@ -520,18 +495,13 @@ class BaseProcessPublishAssortment {
520
495
  const itemFamilyId = item?.itemFamilyId;
521
496
  if (meetsCriteria) {
522
497
  releasedForDevelopmentItemIds.push(itemId);
523
- }
524
- // ---- NEW (Step 5): family resolution moved OUT of the meetsCriteria block.
525
- // A family whose options are ALL pre-development has no other route into this
526
- // list when it has no assortment-item row of its own. ----
527
- if (!itemFamilySet.has(itemFamilyId) && itemId !== itemFamilyId) {
528
- const familyItem = item?.itemFamily;
529
- if (familyItem) {
530
- if (this.meetsCriteria({ item: familyItem })
531
- && !releasedForDevelopmentItemIds.includes(itemFamilyId)) {
498
+ if (!itemFamilySet.has(itemFamilyId) && itemId !== itemFamilyId) {
499
+ const familyItem = item?.itemFamily;
500
+ const familyItemMeetsCriteria = this.meetsCriteria({ item: familyItem });
501
+ if (familyItemMeetsCriteria) {
532
502
  releasedForDevelopmentItemIds.push(itemFamilyId);
533
503
  }
534
- itemFamilySet.add(itemFamilyId); // only mark resolved when we had data to judge
504
+ itemFamilySet.add(itemFamilyId);
535
505
  }
536
506
  }
537
507
  }
@@ -569,7 +539,7 @@ class BaseProcessPublishAssortment {
569
539
  console.info('processPublish-start');
570
540
  const event = {
571
541
  assortmentId: pcd.assortmentId,
572
- assortmentPublishChangeId: pcd.assortmentPublishChangeId,
542
+ assortmentPublishChangeId: pcd.assortmentPublishChangeId
573
543
  };
574
544
  const assortmentItemFullChangeMap = this.getFullChangeAssortmentMap(fullChange);
575
545
  const assortmentItemDeleteMap = this.getDeleteChangesAssortmentMap(deleteChanges);
@@ -584,7 +554,7 @@ class BaseProcessPublishAssortment {
584
554
  console.log(message);
585
555
  return {
586
556
  results: { message, event },
587
- skip_await: true,
557
+ skip_await: true
588
558
  };
589
559
  }
590
560
  const results = await this.sendEvents(events);
@@ -629,7 +599,7 @@ class BaseProcessPublishAssortment {
629
599
  taskId: this.config.taskId,
630
600
  eventType,
631
601
  objectClass: 'LCSSeason',
632
- events,
602
+ events
633
603
  };
634
604
  const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
635
605
  const result = await this.sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent);
@@ -675,13 +645,13 @@ class BaseProcessPublishAssortment {
675
645
  taskId: this.config.taskId,
676
646
  eventType,
677
647
  objectClass: 'LCSSeason',
678
- events,
648
+ events
679
649
  };
680
650
  await fileHandle.writeFile(JSON.stringify(outboundPublishEvent));
681
651
  results = {
682
652
  message: 'Successfully Saved File',
683
653
  outboundPublishEvent,
684
- fileLocation: dirName + fileName,
654
+ fileLocation: dirName + fileName
685
655
  };
686
656
  }
687
657
  catch (e) {
@@ -702,7 +672,7 @@ class BaseProcessPublishAssortment {
702
672
  objectClass: 'LCSSeason',
703
673
  eventsFileId: uploadFile.id,
704
674
  eventsDownloadLink: uploadFile.downloadUrl,
705
- eventsAdminDownloadLink: uploadFile.adminDownloadUrl,
675
+ eventsAdminDownloadLink: uploadFile.adminDownloadUrl
706
676
  };
707
677
  if (sendMode === 'vibeiqfile') {
708
678
  const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
@@ -716,7 +686,7 @@ class BaseProcessPublishAssortment {
716
686
  await this.sendPublishPayloadEvent(outboundPublishEvent);
717
687
  return {
718
688
  message: 'Successfully Uploaded File.',
719
- outboundPublishEvent,
689
+ outboundPublishEvent
720
690
  };
721
691
  }
722
692
  }
@@ -732,11 +702,11 @@ class BaseProcessPublishAssortment {
732
702
  originSystemType: 'VibeIQ',
733
703
  objectClass: 'AssortmentPublishedToFlexPLM',
734
704
  outboundPublishEvent,
735
- initialEvent,
705
+ initialEvent
736
706
  };
737
707
  await new sdk_1.Entities().create({
738
708
  entityName: 'external-event',
739
- object: eventBody,
709
+ object: eventBody
740
710
  });
741
711
  }
742
712
  getCurrentDateString() {
@@ -760,7 +730,7 @@ class BaseProcessPublishAssortment {
760
730
  for (const [itemId, aItem] of assortmentItemFullChangeMap) {
761
731
  const projectItem = aItem?.projectItem;
762
732
  const itemFamilyId = aItem?.item?.itemFamilyId;
763
- if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId)) {
733
+ if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId) || !pcd.releasedForDevelopmentItemIds.includes(itemId)) {
764
734
  continue;
765
735
  }
766
736
  const ifc = itemFamilyChanges.get(itemFamilyId) || new item_family_changes_1.ItemFamilyChanges(itemFamilyId, pcd.sinceDate);
@@ -772,20 +742,6 @@ class BaseProcessPublishAssortment {
772
742
  if (pcd.itemToFederatedIdMapping.has(itemId)) {
773
743
  ifc.itemToFederatedIdMapping.set(itemId, pcd.itemToFederatedIdMapping.get(itemId));
774
744
  }
775
- // ---- NEW Step to enable ItemFamily publish ET-243: option is pre-development. It must not produce an
776
- // LCSSKUSeasonLink, but a change to it still makes the family publishable. ----
777
- if (!pcd.releasedForDevelopmentItemIds.includes(itemId)) {
778
- const changedInWindow = addIds.includes(itemId)
779
- || deleteIds.includes(itemId)
780
- || updateIds.includes(itemId)
781
- || this.updatedSinceDate(projectItem, pcd.sinceDate)
782
- || this.updatedSinceDate(aItem?.item, pcd.sinceDate);
783
- if (changedInWindow) {
784
- ifc.colorSuppressed.push(itemId);
785
- }
786
- continue;
787
- }
788
- // NEW Step - ends
789
745
  if (itemId === itemFamilyId) {
790
746
  if (addIds.includes(itemId))
791
747
  ifc.familyAdd = true;
@@ -801,12 +757,6 @@ class BaseProcessPublishAssortment {
801
757
  ifc.familyUpdate = true;
802
758
  }
803
759
  }
804
- // NEW Step to enable ItemFamily publish ET-243: item-level edits (incl. lifecycleStage) must publish
805
- // the family. Guarded so the "first option added" flow stays suppressed. ----
806
- if (!ifc.familyItemRemoved && this.updatedSinceDate(aItem?.item, pcd.sinceDate)) {
807
- ifc.familyUpdate = true;
808
- }
809
- // NEW Step - ends
810
760
  if (projectItem && !projectItem?.roles && aItem?.item?.roles) {
811
761
  projectItem.roles = aItem?.item?.roles;
812
762
  }
@@ -863,7 +813,7 @@ class BaseProcessPublishAssortment {
863
813
  else {
864
814
  ifc.colorDeletes.push(itemId);
865
815
  }
866
- } // End deletes for loop
816
+ } //End deletes for loop
867
817
  if (app_framework_1.Logger.isDebugOn()) {
868
818
  console.debug('returning size: ' + itemFamilyChanges.size);
869
819
  for (const [key, value] of itemFamilyChanges) {
@@ -905,13 +855,13 @@ class BaseProcessPublishAssortment {
905
855
  const LCSSeason = Object.assign({}, seasonFed);
906
856
  const assortment = await this.getAssortment(assortmentId);
907
857
  const projectItem = this.getProjectItem(itemFamilyChanges, itemFamilyChanges.itemFamilyId);
908
- // ---- NEW Step to enable ItemFamily publish ET-243: added color suppressed length
909
858
  const familyAssortmentItem = itemFamilyChanges.familyAdd || itemFamilyChanges.familyUpdate || itemFamilyChanges.familyDelete
910
859
  || itemFamilyChanges.colorAdds.length > 0 || itemFamilyChanges.colorUpdates.length > 0 || itemFamilyChanges.colorDeletes.length > 0
911
- || itemFamilyChanges.colorSuppressed.length > 0
912
860
  || (projectItem && this.updatedSinceDate(projectItem, itemFamilyChanges.sinceDate));
861
+ //familyItemRemoved is used when adding the first option to an assortment
862
+ //and will have updates for the family item.
913
863
  if (familyAssortmentItem) {
914
- // Product-season add
864
+ //Product-season add
915
865
  const entityReference = itemFamilyChanges.itemFamilyId;
916
866
  const prodEntityData = (itemFamilyChanges.assortmentItemFullChangeMap.has(entityReference))
917
867
  ? itemFamilyChanges.assortmentItemFullChangeMap.get(entityReference)?.item
@@ -927,7 +877,7 @@ class BaseProcessPublishAssortment {
927
877
  entityReference: 'item:' + entityReference,
928
878
  LCSSeason,
929
879
  LCSProduct,
930
- data: seasonalData,
880
+ data: seasonalData
931
881
  };
932
882
  if ('ASSORTMENT' === projectItem.addedFromSource && projectItem.addedFromAssortment) {
933
883
  const carriedFromSeason = await this.getCarriedFromSeason(projectItem);
@@ -937,9 +887,9 @@ class BaseProcessPublishAssortment {
937
887
  }
938
888
  events.push(psUpsert);
939
889
  }
940
- // colorway-season adds
941
- // colorAdds
942
- // colorUpdates
890
+ //colorway-season adds
891
+ //colorAdds
892
+ //colorUpdates
943
893
  const colorwayChanges = [];
944
894
  colorwayChanges.push(...itemFamilyChanges.colorAdds);
945
895
  colorwayChanges.push(...itemFamilyChanges.colorUpdates);
@@ -962,7 +912,7 @@ class BaseProcessPublishAssortment {
962
912
  entityReference: 'item:' + entityReference,
963
913
  LCSSeason,
964
914
  LCSSKU,
965
- data: seasonalData,
915
+ data: seasonalData
966
916
  };
967
917
  if ('ASSORTMENT' === projectItem.addedFromSource && projectItem.addedFromAssortment) {
968
918
  const carriedFromSeason = await this.getCarriedFromSeason(projectItem);
@@ -978,10 +928,10 @@ class BaseProcessPublishAssortment {
978
928
  let projectItem = {};
979
929
  if (itemFamilyChanges?.assortmentItemFullChangeMap.has(id)) {
980
930
  const aItem = itemFamilyChanges?.assortmentItemFullChangeMap.get(id);
981
- projectItem = aItem?.projectItem ?? {};
931
+ projectItem = aItem?.projectItem;
982
932
  }
983
933
  /////////////////////////////////////////////////////////////////////////////
984
- // Get from option assortmentItem because family not in assortmentItemFullChangeMap:start
934
+ //Get from option assortmentItem because family not in assortmentItemFullChangeMap:start
985
935
  /////////////////////////////////////////////////////////////////////////////
986
936
  if (id === itemFamilyChanges.itemFamilyId && Object.keys(projectItem).length == 0) {
987
937
  for (const asstItem of itemFamilyChanges.assortmentItemFullChangeMap.values()) {
@@ -992,7 +942,7 @@ class BaseProcessPublishAssortment {
992
942
  }
993
943
  }
994
944
  /////////////////////////////////////////////////////////////////////////////
995
- // Get from option assortmentItem because family not in assortmentItemFullChangeMap:end
945
+ //Get from option assortmentItem because family not in assortmentItemFullChangeMap:end
996
946
  /////////////////////////////////////////////////////////////////////////////
997
947
  return projectItem;
998
948
  }
@@ -1042,7 +992,7 @@ class BaseProcessPublishAssortment {
1042
992
  if (this.cache.carriedFromSeason[addedFromAssortment]) {
1043
993
  return this.cache.carriedFromSeason[addedFromAssortment];
1044
994
  }
1045
- let seasonFederation;
995
+ let seasonFederation = undefined;
1046
996
  const assortmentEntity = await this.getAssormentEntityFromId(addedFromAssortment);
1047
997
  if (assortmentEntity) {
1048
998
  seasonFederation = await this.getSeasonFederationFromAssortment(assortmentEntity);
@@ -1055,7 +1005,7 @@ class BaseProcessPublishAssortment {
1055
1005
  try {
1056
1006
  assortment = await new sdk_1.Entities().get({
1057
1007
  entityName: 'assortment',
1058
- id: assortmentId,
1008
+ id: assortmentId
1059
1009
  });
1060
1010
  }
1061
1011
  catch (e) {
@@ -1069,7 +1019,7 @@ class BaseProcessPublishAssortment {
1069
1019
  let seasonObj = {
1070
1020
  entityReference: 'assortment:' + assortment.id,
1071
1021
  objectClass: 'LCSSeason',
1072
- flexPLMTypePath,
1022
+ flexPLMTypePath
1073
1023
  };
1074
1024
  try {
1075
1025
  const assortmentObj = await this.dc.getFlexPLMObjectData(assortment, [], true);
@@ -921,39 +921,6 @@ describe('getDeleteChanges', () => {
921
921
  expect(itemIds).toContain('qNCQmTFcFxwbT-oG');
922
922
  spy.mockRestore();
923
923
  });
924
- const stubDownloads = (ppa, history, priorBaseline) => {
925
- jest.spyOn(ppa, 'downloadDeleteChanges').mockImplementation(async (apc) => {
926
- if (!apc)
927
- return [];
928
- return history.find(h => h['id'] === apc['id'])?.['deleteData'] || [];
929
- });
930
- jest.spyOn(ppa, 'downloadAssortmentBaseline').mockResolvedValue(priorBaseline);
931
- };
932
- const famRow = (id, lifecycleStage = 'development') => ({
933
- itemId: id,
934
- item: { id, itemFamilyId: id, lifecycleStage, roles: ['family'] },
935
- projectItem: { id: 'pi-' + id }
936
- });
937
- const optRow = (id, familyId) => ({
938
- itemId: id,
939
- item: { id, itemFamilyId: familyId, lifecycleStage: 'development', roles: ['color', 'option'] }
940
- });
941
- it('adds a family row that left the assortment while its option rows remain', async () => {
942
- const history = new Array(...mockData_1.apc_2bOWR2j9R0QThDVu_delete_history);
943
- const apcIndex = history.length - 1;
944
- const apc = history[apcIndex];
945
- const apcDate = new Date(history[apcIndex - 1]['createdOn']);
946
- const ppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
947
- const fam = famRow('FAM1');
948
- const opt = optRow('OPT1', 'FAM1');
949
- stubDownloads(ppa, history, { assortmentItems: [fam, opt] });
950
- const deleteChanges = await ppa.getDeleteChanges(apc, history, { assortmentItems: [opt] }, apcDate);
951
- const itemIds = deleteChanges.map(d => d['itemId']);
952
- expect(itemIds).toContain('FAM1');
953
- expect(itemIds).not.toContain('OPT1');
954
- expect(deleteChanges.find(d => d['itemId'] === 'FAM1')['projectItem']['id']).toEqual('pi-FAM1');
955
- jest.restoreAllMocks();
956
- });
957
924
  });
958
925
  describe('buildDeleteChanges', () => {
959
926
  const config = {};
@@ -1370,163 +1337,6 @@ describe('getItemFamilyChanges', () => {
1370
1337
  expect(ifc.colorDeletes.length).toEqual(1);
1371
1338
  });
1372
1339
  });
1373
- describe('getItemFamilyChanges - lifecycle-suppressed options', () => {
1374
- const familyId = 'AERfdvdAt1HHEVvQ';
1375
- const maroonId = 'YwWKVoL_vGN8khLy';
1376
- const limeId = 'rsmWhWiXRNv_XBJG';
1377
- const sinceDate = new Date('2026-08-01T00:00:00.000Z');
1378
- const afterSince = '2026-08-26T00:00:00.000Z';
1379
- const beforeSince = '2026-05-11T00:00:00.000Z';
1380
- const noDetailChanges = () => ({ adds: [], deletes: [], updates: [], familyItemsRemoved: [] });
1381
- const buildPpa = () => {
1382
- const config = { itemPreDevelopmentLifecycleStages: ['concept'] };
1383
- const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
1384
- const dc = new data_converter_1.DataConverter(config, mapFileUtil);
1385
- return new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1386
- };
1387
- const buildPcd = (releasedIds) => {
1388
- const seasonFed = {
1389
- entityReference: 'assortment:11EZML5dMn3tREuT',
1390
- objectClass: 'LCSSeason'
1391
- };
1392
- const pcd = new publish_change_data_1.PublishChangeData('11EZML5dMn3tREuT', seasonFed, 'Pg3y2x5IclYxk3hM', sinceDate);
1393
- pcd.itemToFederatedIdMapping = new Map();
1394
- releasedIds.forEach(id => pcd.releasedForDevelopmentItemIds.push(id));
1395
- return pcd;
1396
- };
1397
- const familyItem = (updatedOn = beforeSince, lifecycleStage = 'released') => ({
1398
- id: familyId, itemFamilyId: familyId, lifecycleStage, name: 'Sweatpant', updatedOn
1399
- });
1400
- const familyRow = (updatedOn = beforeSince, lifecycleStage = 'released') => ({
1401
- itemId: familyId,
1402
- item: familyItem(updatedOn, lifecycleStage),
1403
- projectItem: { id: 'pi-' + familyId, updatedOn: beforeSince }
1404
- });
1405
- const optionRow = (id, updatedOn, lifecycleStage = 'concept') => ({
1406
- itemId: id,
1407
- item: { id, itemFamilyId: familyId, lifecycleStage, updatedOn, itemFamily: familyItem() },
1408
- projectItem: { id: 'pi-' + id, updatedOn: beforeSince },
1409
- familyProjectItem: { id: 'pi-' + familyId, updatedOn: beforeSince }
1410
- });
1411
- it('records changed concept options as suppressed, not as colour updates', () => {
1412
- const ppa = buildPpa();
1413
- const fullChangeMap = ppa.getFullChangeAssortmentMap({
1414
- assortmentItems: [familyRow(), optionRow(maroonId, afterSince), optionRow(limeId, afterSince)]
1415
- });
1416
- const pcd = buildPcd([familyId]);
1417
- pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
1418
- expect(pcd.itemFamilyChanges.size).toEqual(1);
1419
- const ifc = pcd.itemFamilyChanges.get(familyId);
1420
- expect(ifc.colorSuppressed).toEqual([maroonId, limeId]);
1421
- expect(ifc.colorAdds.length).toEqual(0);
1422
- expect(ifc.colorUpdates.length).toEqual(0);
1423
- expect(ifc.colorDeletes.length).toEqual(0);
1424
- expect(ifc.familyUpdate).toBe(false); // family itself unchanged
1425
- });
1426
- it('keeps the family reachable when only concept option rows are in the baseline', () => {
1427
- const ppa = buildPpa();
1428
- const assortmentItems = [optionRow(maroonId, afterSince), optionRow(limeId, afterSince)];
1429
- const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems });
1430
- // derive the gate the way production does, rather than seeding it
1431
- const releasedIds = ppa.getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems }, []);
1432
- expect(releasedIds).toEqual([familyId]);
1433
- const pcd = buildPcd(releasedIds);
1434
- pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
1435
- expect(pcd.itemFamilyChanges.size).toEqual(1);
1436
- const ifc = pcd.itemFamilyChanges.get(familyId);
1437
- expect(ifc.itemFamilyObject.id).toEqual(familyId);
1438
- expect(ifc.colorSuppressed.length).toEqual(2);
1439
- expect(ppa.getProjectItem(ifc, familyId)).toEqual({ id: 'pi-' + familyId, updatedOn: beforeSince });
1440
- });
1441
- it('does not suppress-record an unchanged concept option', () => {
1442
- const ppa = buildPpa();
1443
- const fullChangeMap = ppa.getFullChangeAssortmentMap({
1444
- assortmentItems: [familyRow(), optionRow(maroonId, beforeSince)]
1445
- });
1446
- const pcd = buildPcd([familyId]);
1447
- pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
1448
- const ifc = pcd.itemFamilyChanges.get(familyId);
1449
- expect(ifc.colorSuppressed.length).toEqual(0);
1450
- });
1451
- it('picks up a concept option listed in the change detail even when timestamps are stale', () => {
1452
- const ppa = buildPpa();
1453
- const fullChangeMap = ppa.getFullChangeAssortmentMap({
1454
- assortmentItems: [familyRow(), optionRow(maroonId, beforeSince)]
1455
- });
1456
- const details = noDetailChanges();
1457
- details.updates = [{ id: maroonId }];
1458
- const pcd = buildPcd([familyId]);
1459
- pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
1460
- expect(pcd.itemFamilyChanges.get(familyId).colorSuppressed).toEqual([maroonId]);
1461
- });
1462
- it('skips everything when the family itself is pre-development', () => {
1463
- const ppa = buildPpa();
1464
- const fullChangeMap = ppa.getFullChangeAssortmentMap({
1465
- assortmentItems: [familyRow(afterSince, 'concept'), optionRow(maroonId, afterSince)]
1466
- });
1467
- const pcd = buildPcd([]); // nothing released
1468
- pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
1469
- expect(pcd.itemFamilyChanges.size).toEqual(0);
1470
- });
1471
- it('still classifies released options normally', () => {
1472
- const ppa = buildPpa();
1473
- const fullChangeMap = ppa.getFullChangeAssortmentMap({
1474
- assortmentItems: [familyRow(), optionRow(maroonId, afterSince, 'released')]
1475
- });
1476
- const details = noDetailChanges();
1477
- details.adds = [{ id: maroonId }];
1478
- const pcd = buildPcd([familyId, maroonId]);
1479
- pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
1480
- const ifc = pcd.itemFamilyChanges.get(familyId);
1481
- expect(ifc.colorAdds).toEqual([maroonId]);
1482
- expect(ifc.colorSuppressed.length).toEqual(0);
1483
- });
1484
- it('publishes a family-only row whose lifecycle just flipped (item-level edit)', () => {
1485
- const ppa = buildPpa();
1486
- const fullChangeMap = ppa.getFullChangeAssortmentMap({
1487
- assortmentItems: [familyRow(afterSince)] // item.updatedOn moved, projectItem did not
1488
- });
1489
- const pcd = buildPcd([familyId]);
1490
- pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
1491
- const ifc = pcd.itemFamilyChanges.get(familyId);
1492
- expect(ifc.familyUpdate).toBe(true); // fails without Step 3
1493
- expect(ifc.colorSuppressed.length).toEqual(0);
1494
- });
1495
- it('leaves an unchanged family-only row with no flags', () => {
1496
- const ppa = buildPpa();
1497
- const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [familyRow()] });
1498
- const pcd = buildPcd([familyId]);
1499
- pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
1500
- const ifc = pcd.itemFamilyChanges.get(familyId);
1501
- expect(ifc.familyAdd).toBe(false);
1502
- expect(ifc.familyUpdate).toBe(false);
1503
- expect(ifc.familyDelete).toBe(false);
1504
- expect(ifc.colorSuppressed.length).toEqual(0);
1505
- });
1506
- it('does not set familyUpdate when the family item was removed', () => {
1507
- const ppa = buildPpa();
1508
- const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [familyRow(afterSince)] });
1509
- const details = noDetailChanges();
1510
- details.familyItemsRemoved = [{ itemId: familyId }]; // note: itemId, not id
1511
- const pcd = buildPcd([familyId]);
1512
- pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
1513
- const ifc = pcd.itemFamilyChanges.get(familyId);
1514
- expect(ifc.familyItemRemoved).toBe(true);
1515
- expect(ifc.familyUpdate).toBe(false); // fails if the !familyItemRemoved guard is dropped
1516
- });
1517
- it('flags familyDelete when the family row left the assortment but option rows remain', () => {
1518
- const ppa = buildPpa();
1519
- const assortmentItems = [optionRow(maroonId, beforeSince), optionRow(limeId, beforeSince, 'released')];
1520
- const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems });
1521
- const deleteMap = new Map([[familyId, familyRow()]]);
1522
- const pcd = buildPcd([familyId, limeId]);
1523
- pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, deleteMap);
1524
- const ifc = pcd.itemFamilyChanges.get(familyId);
1525
- expect(ifc.familyDelete).toBe(true);
1526
- expect(ifc.colorDeletes.length).toEqual(0); // no SKU event
1527
- expect(ppa.getProjectItem(ifc, familyId).id).toEqual('pi-' + familyId); // family's own projectItem
1528
- });
1529
- });
1530
1340
  describe('getProjectItem', () => {
1531
1341
  const config = {
1532
1342
  identifierAtts: {
@@ -1690,70 +1500,6 @@ describe('meetsCriteria', () => {
1690
1500
  expect(results).toBeTruthy();
1691
1501
  });
1692
1502
  });
1693
- describe('getReleasedForDevelopmentItemAndFamilyIds', () => {
1694
- const familyId = 'AERfdvdAt1HHEVvQ';
1695
- const maroonId = 'YwWKVoL_vGN8khLy';
1696
- const limeId = 'rsmWhWiXRNv_XBJG';
1697
- const buildPpa = (preDev = ['concept']) => {
1698
- const config = {
1699
- identifierAtts: { LCSSeason: ['flexPLMSeasonName'] },
1700
- itemPreDevelopmentLifecycleStages: preDev
1701
- };
1702
- const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
1703
- const dc = new data_converter_1.DataConverter(config, mapFileUtil);
1704
- return new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1705
- };
1706
- const familyItem = (lifecycleStage = 'released') => ({
1707
- id: familyId, itemFamilyId: familyId, lifecycleStage, name: 'Sweatpant'
1708
- });
1709
- // option row as the hydrated baseline actually delivers it: itemFamily present
1710
- const optionRow = (id, lifecycleStage = 'concept', family = familyItem()) => ({
1711
- itemId: id,
1712
- item: { id, itemFamilyId: familyId, lifecycleStage, itemFamily: family }
1713
- });
1714
- const familyRow = (lifecycleStage = 'released') => ({
1715
- itemId: familyId,
1716
- item: familyItem(lifecycleStage)
1717
- });
1718
- const run = (ppa, assortmentItems) => ppa.getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems }, []);
1719
- it('surfaces the family when every option is pre-development', () => {
1720
- const ppa = buildPpa();
1721
- expect(run(ppa, [optionRow(maroonId), optionRow(limeId)])).toEqual([familyId]);
1722
- });
1723
- it('does not duplicate the family id when a family row and a released option both exist', () => {
1724
- const ppa = buildPpa();
1725
- const ids = run(ppa, [familyRow(), optionRow(limeId, 'released')]);
1726
- expect(ids.filter(id => id === familyId)).toHaveLength(1);
1727
- });
1728
- it('still resolves the family when an earlier row lacks itemFamily hydration', () => {
1729
- const ppa = buildPpa();
1730
- const noFamilyHydration = {
1731
- itemId: maroonId,
1732
- item: { id: maroonId, itemFamilyId: familyId, lifecycleStage: 'concept' }
1733
- };
1734
- expect(run(ppa, [noFamilyHydration, optionRow(limeId)])).toEqual([familyId]);
1735
- });
1736
- it('returns nothing when the family itself is pre-development', () => {
1737
- const ppa = buildPpa();
1738
- expect(run(ppa, [optionRow(maroonId, 'concept', familyItem('concept'))])).toEqual([]);
1739
- });
1740
- it('includes a family-only row exactly once', () => {
1741
- const ppa = buildPpa();
1742
- expect(run(ppa, [familyRow()])).toEqual([familyId]);
1743
- });
1744
- it('includes a released option and its family, excluding the concept option', () => {
1745
- const ppa = buildPpa();
1746
- const ids = run(ppa, [optionRow(maroonId, 'concept'), optionRow(limeId, 'released')]);
1747
- expect(ids).toContain(familyId);
1748
- expect(ids).toContain(limeId);
1749
- expect(ids).not.toContain(maroonId);
1750
- });
1751
- it('treats the stage list as case-sensitive (documents the Concept vs concept trap)', () => {
1752
- const ppa = buildPpa(['Concept']); // as mis-configured in praveen-dev-1
1753
- const ids = run(ppa, [optionRow(maroonId), optionRow(limeId)]);
1754
- expect(ids).toEqual([maroonId, familyId, limeId]); // filter inert: everything qualifies
1755
- });
1756
- });
1757
1503
  describe('getEventsForItemFamilyChanges - conditional eventType', () => {
1758
1504
  const config = {
1759
1505
  identifierAtts: {
@@ -1959,34 +1705,6 @@ describe('getEventsForItemFamilyChanges - conditional eventType', () => {
1959
1705
  // Verify SKU event called with assortment entity
1960
1706
  expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenNthCalledWith(2, undefined, mapFileUtil, colorProjectItem, { item: itemData, assortment });
1961
1707
  });
1962
- it('should emit only an LCSProductSeasonLink when the family row was removed', async () => {
1963
- const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1964
- const itemFamilyId = 'AERfdvdAt1HHEVvQ';
1965
- const prodEntityData = { id: itemFamilyId, identifier: 'FAM-11', itemNumber: 'FAM-11' };
1966
- const projectItem = { id: 'proj-fam', updatedOn: '2022-12-01T00:00:00.000Z' };
1967
- const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
1968
- const seasonFed = {
1969
- entityReference: 'assortment:assort123',
1970
- objectClass: 'LCSSeason',
1971
- flexPLMSeasonName: 'Fall 2023'
1972
- };
1973
- const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
1974
- itemFamilyChanges.familyDelete = true; // the only trigger
1975
- itemFamilyChanges.colorUnchanged.push('rsmWhWiXRNv_XBJG'); // surviving option must not emit
1976
- itemFamilyChanges.itemFamilyObject = prodEntityData;
1977
- jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
1978
- jest.spyOn(bppa, 'getProjectItem').mockReturnValue(projectItem);
1979
- jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
1980
- jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
1981
- jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
1982
- jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
1983
- jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(false);
1984
- const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
1985
- expect(events).toHaveLength(1);
1986
- expect(events[0].objectClass).toBe('LCSProductSeasonLink');
1987
- expect(events[0].eventType).toBe('UPDATE_ON_SEASON');
1988
- expect(events[0].entityReference).toBe('item:' + itemFamilyId);
1989
- });
1990
1708
  });
1991
1709
  describe('sendToFlexPLM / handleVibeIQFile / sendPublishPayloadEvent', () => {
1992
1710
  const config = {
@@ -63,7 +63,6 @@ class FlexPLMConnect {
63
63
  const nonce_key = body.items[0].attributes.nonce_key;
64
64
  const nonce = body.items[0].attributes.nonce;
65
65
  console.info('nonce_key: ' + nonce_key);
66
- console.info('nonce: ' + nonce);
67
66
  return {
68
67
  nonce_key,
69
68
  nonce
@@ -89,7 +88,10 @@ class FlexPLMConnect {
89
88
  console.info('Request:');
90
89
  console.info('vibeEventsURL: ' + vibeEventsURL);
91
90
  const logOptions = JSON.parse(JSON.stringify(xferOptions));
92
- logOptions['headers']['Authorization'] = logOptions?.headers?.Authorization.substring(0, 9);
91
+ if (logOptions?.headers?.Authorization)
92
+ logOptions.headers.Authorization = '***masked***';
93
+ if (csrf?.nonce_key)
94
+ logOptions.headers[csrf.nonce_key] = '***masked***';
93
95
  console.info('csrfOptions: ' + JSON.stringify(logOptions));
94
96
  console.info('Making call to xfer data to FlexPLM');
95
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/flexplm",
3
- "version": "1.7.4-alpha.b088cfc",
3
+ "version": "1.7.4-alpha.b869ea5",
4
4
  "description": "Library used for integration with flexplm.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",