@contrail/flexplm 1.1.67-alpha.2 → 1.1.67-alpha.4

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/CHANGELOG.md CHANGED
@@ -11,6 +11,7 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
11
11
 
12
12
  ### Added
13
13
  - Added EventShortMessageStatus status values for adding items to projects functionality.
14
+ - Added `TypeConversionUtils.isOutboundCreatableFromEntity` method to determine if VibeIQ entities are creatable in FlexPLM (defaults to true).
14
15
 
15
16
  ### Changed
16
17
  - Added optional `context` parameter to `TypeConversionUtils.isInboundCreatableFromObject` method to allow passing context data to map file `isInboundCreatable` functions.
@@ -82,7 +82,7 @@ export interface ItemPayloadType extends EntityPayloadType {
82
82
  LCSProduct?: ProductFederation;
83
83
  }
84
84
  export interface SeasonalPayload extends EntityPayloadType {
85
- eventType: 'UPSERT_ON_SEASON' | 'REMOVE_FROM_SEASON';
85
+ eventType: 'UPSERT_ON_SEASON' | 'UPDATE_ON_SEASON' | 'REMOVE_FROM_SEASON';
86
86
  objectClass: 'LCSProductSeasonLink' | 'LCSSKUSeasonLink';
87
87
  LCSSeason: SeasonFederation;
88
88
  LCSProduct?: ProductFederation;
@@ -759,6 +759,7 @@ class BaseProcessPublishAssortment {
759
759
  console.info('getEventsForItemFamilyChanges()');
760
760
  const events = [];
761
761
  const LCSSeason = Object.assign({}, seasonFed);
762
+ const assortment = await this.getAssortment(assortmentId);
762
763
  const projectItem = this.getProjectItem(itemFamilyChanges, itemFamilyChanges.itemFamilyId);
763
764
  const familyAssortmentItem = itemFamilyChanges.familyAdd || itemFamilyChanges.familyUpdate || itemFamilyChanges.familyDelete
764
765
  || itemFamilyChanges.colorAdds.length > 0 || itemFamilyChanges.colorUpdates.length > 0 || itemFamilyChanges.colorDeletes.length > 0
@@ -771,8 +772,10 @@ class BaseProcessPublishAssortment {
771
772
  let seasonalData = await this.getSeasonalData(projectItem);
772
773
  seasonalData = await map_utils_1.MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonalData, 'LCSProductSeasonLink', 'vibe2flex');
773
774
  const LCSProduct = await this.getProductFederation(entityReference, itemToFederatedIdMapping, prodEntityData);
775
+ const isCreatable = await type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity(this.transformMapFile, this.mapFileUtil, projectItem, { item: prodEntityData, assortment });
776
+ const eventType = isCreatable ? 'UPSERT_ON_SEASON' : 'UPDATE_ON_SEASON';
774
777
  const psUpsert = {
775
- eventType: 'UPSERT_ON_SEASON',
778
+ eventType,
776
779
  objectClass: 'LCSProductSeasonLink',
777
780
  entityReference: 'item:' + entityReference,
778
781
  LCSSeason,
@@ -801,8 +804,10 @@ class BaseProcessPublishAssortment {
801
804
  let seasonalData = await this.getSeasonalData(projectItem);
802
805
  seasonalData = await map_utils_1.MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonalData, 'LCSSKUSeasonLink', 'vibe2flex');
803
806
  const LCSSKU = await this.getSKUFederation(entityReference, itemToFederatedIdMapping, item);
807
+ const isCreatable = await type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity(this.transformMapFile, this.mapFileUtil, projectItem, { item, assortment });
808
+ const eventType = isCreatable ? 'UPSERT_ON_SEASON' : 'UPDATE_ON_SEASON';
804
809
  const csUpsert = {
805
- eventType: 'UPSERT_ON_SEASON',
810
+ eventType,
806
811
  objectClass: 'LCSSKUSeasonLink',
807
812
  entityReference: 'item:' + entityReference,
808
813
  LCSSeason,
@@ -1467,3 +1467,204 @@ describe('meetsCriteria', () => {
1467
1467
  expect(results).toBeTruthy();
1468
1468
  });
1469
1469
  });
1470
+ describe('getEventsForItemFamilyChanges - conditional eventType', () => {
1471
+ const config = {
1472
+ identifierAtts: {
1473
+ LCSSeason: ['flexPLMSeasonName']
1474
+ },
1475
+ itemPreDevelopmentLifecycleStages: ['concept']
1476
+ };
1477
+ const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
1478
+ const dc = new data_converter_1.DataConverter(config, mapFileUtil);
1479
+ afterEach(() => {
1480
+ jest.restoreAllMocks();
1481
+ });
1482
+ it('should use UPSERT_ON_SEASON for LCSProductSeasonLink when isOutboundCreatableFromEntity returns true', async () => {
1483
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1484
+ const itemFamilyId = 'family123';
1485
+ const prodEntityData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
1486
+ const projectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
1487
+ const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
1488
+ const seasonFed = {
1489
+ entityReference: 'assortment:assort123',
1490
+ objectClass: 'LCSSeason',
1491
+ flexPLMSeasonName: 'Fall 2023'
1492
+ };
1493
+ const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
1494
+ itemFamilyChanges.familyAdd = true;
1495
+ itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, {
1496
+ item: prodEntityData,
1497
+ projectItem
1498
+ });
1499
+ jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
1500
+ jest.spyOn(bppa, 'getProjectItem').mockReturnValue(projectItem);
1501
+ jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
1502
+ jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
1503
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
1504
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
1505
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(true);
1506
+ const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
1507
+ expect(events).toHaveLength(1);
1508
+ expect(events[0].eventType).toBe('UPSERT_ON_SEASON');
1509
+ expect(events[0].objectClass).toBe('LCSProductSeasonLink');
1510
+ expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenCalledWith(undefined, mapFileUtil, projectItem, { item: prodEntityData, assortment });
1511
+ });
1512
+ it('should use UPDATE_ON_SEASON for LCSProductSeasonLink when isOutboundCreatableFromEntity returns false', async () => {
1513
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1514
+ const itemFamilyId = 'family123';
1515
+ const prodEntityData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
1516
+ const projectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
1517
+ const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
1518
+ const seasonFed = {
1519
+ entityReference: 'assortment:assort123',
1520
+ objectClass: 'LCSSeason',
1521
+ flexPLMSeasonName: 'Fall 2023'
1522
+ };
1523
+ const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
1524
+ itemFamilyChanges.familyUpdate = true;
1525
+ itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, {
1526
+ item: prodEntityData,
1527
+ projectItem
1528
+ });
1529
+ jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
1530
+ jest.spyOn(bppa, 'getProjectItem').mockReturnValue(projectItem);
1531
+ jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
1532
+ jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
1533
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
1534
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
1535
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(false);
1536
+ const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
1537
+ expect(events).toHaveLength(1);
1538
+ expect(events[0].eventType).toBe('UPDATE_ON_SEASON');
1539
+ expect(events[0].objectClass).toBe('LCSProductSeasonLink');
1540
+ expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenCalledWith(undefined, mapFileUtil, projectItem, { item: prodEntityData, assortment });
1541
+ });
1542
+ it('should use UPSERT_ON_SEASON for LCSSKUSeasonLink when isOutboundCreatableFromEntity returns true', async () => {
1543
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1544
+ const itemFamilyId = 'family123';
1545
+ const colorId = 'color456';
1546
+ const familyItemData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
1547
+ const itemData = { id: colorId, identifier: 'COLOR-456', itemNumber: 'COLOR-456' };
1548
+ const familyProjectItem = { id: 'proj0', updatedOn: '2023-01-14T00:00:00.000Z' };
1549
+ const projectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
1550
+ const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
1551
+ const seasonFed = {
1552
+ entityReference: 'assortment:assort123',
1553
+ objectClass: 'LCSSeason',
1554
+ flexPLMSeasonName: 'Fall 2023'
1555
+ };
1556
+ const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
1557
+ itemFamilyChanges.colorAdds.push(colorId);
1558
+ itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, {
1559
+ item: familyItemData,
1560
+ projectItem: familyProjectItem
1561
+ });
1562
+ itemFamilyChanges.assortmentItemFullChangeMap.set(colorId, {
1563
+ item: itemData,
1564
+ projectItem
1565
+ });
1566
+ jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
1567
+ jest.spyOn(bppa, 'getProjectItem')
1568
+ .mockReturnValueOnce(familyProjectItem)
1569
+ .mockReturnValueOnce(projectItem);
1570
+ jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
1571
+ jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
1572
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
1573
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
1574
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity')
1575
+ .mockResolvedValueOnce(true)
1576
+ .mockResolvedValueOnce(true);
1577
+ const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
1578
+ expect(events).toHaveLength(2);
1579
+ const skuEvent = events.find(e => e.objectClass === 'LCSSKUSeasonLink');
1580
+ expect(skuEvent.eventType).toBe('UPSERT_ON_SEASON');
1581
+ expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenCalledWith(undefined, mapFileUtil, projectItem, { item: itemData, assortment });
1582
+ });
1583
+ it('should use UPDATE_ON_SEASON for LCSSKUSeasonLink when isOutboundCreatableFromEntity returns false', async () => {
1584
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1585
+ const itemFamilyId = 'family123';
1586
+ const colorId = 'color456';
1587
+ const familyItemData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
1588
+ const itemData = { id: colorId, identifier: 'COLOR-456', itemNumber: 'COLOR-456' };
1589
+ const familyProjectItem = { id: 'proj0', updatedOn: '2023-01-14T00:00:00.000Z' };
1590
+ const projectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
1591
+ const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
1592
+ const seasonFed = {
1593
+ entityReference: 'assortment:assort123',
1594
+ objectClass: 'LCSSeason',
1595
+ flexPLMSeasonName: 'Fall 2023'
1596
+ };
1597
+ const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
1598
+ itemFamilyChanges.colorUpdates.push(colorId);
1599
+ itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, {
1600
+ item: familyItemData,
1601
+ projectItem: familyProjectItem
1602
+ });
1603
+ itemFamilyChanges.assortmentItemFullChangeMap.set(colorId, {
1604
+ item: itemData,
1605
+ projectItem
1606
+ });
1607
+ jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
1608
+ jest.spyOn(bppa, 'getProjectItem')
1609
+ .mockReturnValueOnce(familyProjectItem)
1610
+ .mockReturnValueOnce(projectItem);
1611
+ jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
1612
+ jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
1613
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
1614
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
1615
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity')
1616
+ .mockResolvedValueOnce(false)
1617
+ .mockResolvedValueOnce(false);
1618
+ const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
1619
+ expect(events).toHaveLength(2);
1620
+ const skuEvent = events.find(e => e.objectClass === 'LCSSKUSeasonLink');
1621
+ expect(skuEvent.eventType).toBe('UPDATE_ON_SEASON');
1622
+ expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenCalledWith(undefined, mapFileUtil, projectItem, { item: itemData, assortment });
1623
+ });
1624
+ it('should create both product and SKU events with correct eventTypes based on isOutboundCreatableFromEntity', async () => {
1625
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1626
+ const itemFamilyId = 'family123';
1627
+ const colorId = 'color456';
1628
+ const prodEntityData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
1629
+ const itemData = { id: colorId, identifier: 'COLOR-456', itemNumber: 'COLOR-456' };
1630
+ const familyProjectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
1631
+ const colorProjectItem = { id: 'proj2', updatedOn: '2023-01-15T00:00:00.000Z' };
1632
+ const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
1633
+ const seasonFed = {
1634
+ entityReference: 'assortment:assort123',
1635
+ objectClass: 'LCSSeason',
1636
+ flexPLMSeasonName: 'Fall 2023'
1637
+ };
1638
+ const itemFamilyChanges = new item_family_changes_1.ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
1639
+ itemFamilyChanges.familyAdd = true;
1640
+ itemFamilyChanges.colorAdds.push(colorId);
1641
+ itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, {
1642
+ item: prodEntityData,
1643
+ projectItem: familyProjectItem
1644
+ });
1645
+ itemFamilyChanges.assortmentItemFullChangeMap.set(colorId, {
1646
+ item: itemData,
1647
+ projectItem: colorProjectItem
1648
+ });
1649
+ jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
1650
+ jest.spyOn(bppa, 'getProjectItem')
1651
+ .mockReturnValueOnce(familyProjectItem)
1652
+ .mockReturnValueOnce(colorProjectItem);
1653
+ jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
1654
+ jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
1655
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
1656
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
1657
+ jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity')
1658
+ .mockResolvedValueOnce(true)
1659
+ .mockResolvedValueOnce(false);
1660
+ const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
1661
+ expect(events).toHaveLength(2);
1662
+ const productEvent = events.find(e => e.objectClass === 'LCSProductSeasonLink');
1663
+ const skuEvent = events.find(e => e.objectClass === 'LCSSKUSeasonLink');
1664
+ expect(productEvent.eventType).toBe('UPSERT_ON_SEASON');
1665
+ expect(skuEvent.eventType).toBe('UPDATE_ON_SEASON');
1666
+ expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenCalledTimes(2);
1667
+ expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenNthCalledWith(1, undefined, mapFileUtil, familyProjectItem, { item: prodEntityData, assortment });
1668
+ expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenNthCalledWith(2, undefined, mapFileUtil, colorProjectItem, { item: itemData, assortment });
1669
+ });
1670
+ });
@@ -134,6 +134,9 @@ exports.mapping = {
134
134
  }
135
135
  },
136
136
  packaging: {
137
+ isOutboundCreatable: (entity, context) => {
138
+ return false;
139
+ },
137
140
  vibe2flex: {
138
141
  transformOrder: [{ processor: 'REKEY', rekeyDelete: true, rekeyTransformersKey: 'rekey' }],
139
142
  rekey: {
@@ -160,6 +163,12 @@ exports.mapping = {
160
163
  }
161
164
  return true;
162
165
  },
166
+ isOutboundCreatable: (entity, context) => {
167
+ if (context && context.skipPrefix) {
168
+ return false;
169
+ }
170
+ return true;
171
+ },
163
172
  vibe2flex: {
164
173
  transformOrder: [{ processor: 'REKEY', rekeyDelete: true, rekeyTransformersKey: 'rekey' }],
165
174
  rekey: {
@@ -16,5 +16,6 @@ export declare class TypeConversionUtils {
16
16
  static getInformationalPropertiesFromObject(fileId: any, mapFileUtil: MapFileUtil, object: any): Promise<string[]>;
17
17
  static getMapKeyFromObject(fileId: any, mapFileUtil: MapFileUtil, object: any, direction: string): Promise<string>;
18
18
  static isInboundCreatableFromObject(fileId: string, mapFileUtil: MapFileUtil, object: any, context?: any): Promise<boolean>;
19
+ static isOutboundCreatableFromEntity(fileId: string, mapFileUtil: MapFileUtil, entity: any, context?: any): Promise<boolean>;
19
20
  static getObjectType(object: any): any;
20
21
  }
@@ -191,6 +191,27 @@ class TypeConversionUtils {
191
191
  }
192
192
  return isInboundCreatable;
193
193
  }
194
+ static async isOutboundCreatableFromEntity(fileId, mapFileUtil, entity, context) {
195
+ let isOutboundCreatable = true;
196
+ if (!fileId) {
197
+ return isOutboundCreatable;
198
+ }
199
+ let mapKey;
200
+ try {
201
+ mapKey = await this.getMapKey(fileId, mapFileUtil, entity, TypeConversionUtils.VIBE2FLEX_DIRECTION);
202
+ }
203
+ catch {
204
+ return isOutboundCreatable;
205
+ }
206
+ if (!mapKey) {
207
+ return isOutboundCreatable;
208
+ }
209
+ const mapData = await map_utils_1.MapUtil.getFullMapSection(fileId, mapFileUtil, mapKey);
210
+ if (mapData && mapData['isOutboundCreatable']) {
211
+ isOutboundCreatable = await mapData['isOutboundCreatable'](entity, context);
212
+ }
213
+ return isOutboundCreatable;
214
+ }
194
215
  static getObjectType(object) {
195
216
  let objectType = object['flexPLMObjectClass'];
196
217
  return objectType;
@@ -616,4 +616,93 @@ describe('conversion-utils', () => {
616
616
  }
617
617
  });
618
618
  });
619
+ describe('isOutboundCreatableFromEntity', () => {
620
+ const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
621
+ it('should return false for custom-entity:pack', async () => {
622
+ const spy = jest.spyOn(mapFileUtil, 'getMapFile')
623
+ .mockImplementation(async () => {
624
+ return mapping;
625
+ });
626
+ const entity = {
627
+ entityType: 'custom-entity',
628
+ typePath: 'custom-entity:pack'
629
+ };
630
+ try {
631
+ const results = await type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity);
632
+ expect(results).toBeFalsy();
633
+ }
634
+ finally {
635
+ spy.mockRestore();
636
+ }
637
+ });
638
+ it('should return true for custom-entity:prefix', async () => {
639
+ const spy = jest.spyOn(mapFileUtil, 'getMapFile')
640
+ .mockImplementation(async () => {
641
+ return mapping;
642
+ });
643
+ const entity = {
644
+ entityType: 'custom-entity',
645
+ typePath: 'custom-entity:prefix'
646
+ };
647
+ try {
648
+ const results = await type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity);
649
+ expect(results).toBeTruthy();
650
+ }
651
+ finally {
652
+ spy.mockRestore();
653
+ }
654
+ });
655
+ it('should pass context to isOutboundCreatable function', async () => {
656
+ const spy = jest.spyOn(mapFileUtil, 'getMapFile')
657
+ .mockImplementation(async () => {
658
+ return mapping;
659
+ });
660
+ const entity = {
661
+ entityType: 'custom-entity',
662
+ typePath: 'custom-entity:prefix'
663
+ };
664
+ const context = { skipPrefix: true };
665
+ try {
666
+ const results = await type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity, context);
667
+ expect(results).toBeFalsy();
668
+ }
669
+ finally {
670
+ spy.mockRestore();
671
+ }
672
+ });
673
+ it('should work without context parameter', async () => {
674
+ const spy = jest.spyOn(mapFileUtil, 'getMapFile')
675
+ .mockImplementation(async () => {
676
+ return mapping;
677
+ });
678
+ const entity = {
679
+ entityType: 'custom-entity',
680
+ typePath: 'custom-entity:prefix'
681
+ };
682
+ try {
683
+ const results = await type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity);
684
+ expect(results).toBeTruthy();
685
+ }
686
+ finally {
687
+ spy.mockRestore();
688
+ }
689
+ });
690
+ it('should default to true when no mapping exists', async () => {
691
+ const spy = jest.spyOn(mapFileUtil, 'getMapFile')
692
+ .mockImplementation(async () => {
693
+ return mapping;
694
+ });
695
+ const entity = {
696
+ entityType: 'custom-entity',
697
+ typePath: 'custom-entity:catName'
698
+ };
699
+ try {
700
+ const results = await type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity);
701
+ expect(results).toBeTruthy();
702
+ }
703
+ finally {
704
+ spy.mockRestore();
705
+ }
706
+ });
707
+ });
619
708
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/flexplm",
3
- "version": "1.1.67-alpha.2",
3
+ "version": "1.1.67-alpha.4",
4
4
  "description": "Library used for integration with flexplm.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -93,7 +93,7 @@ export interface ItemPayloadType extends EntityPayloadType {
93
93
  ///////// Custom seasonalPayload: start
94
94
  /////////////////////////////////////////////////////////////////////////////
95
95
  export interface SeasonalPayload extends EntityPayloadType {
96
- eventType: 'UPSERT_ON_SEASON' | 'REMOVE_FROM_SEASON';
96
+ eventType: 'UPSERT_ON_SEASON' | 'UPDATE_ON_SEASON' | 'REMOVE_FROM_SEASON';
97
97
  objectClass: 'LCSProductSeasonLink' | 'LCSSKUSeasonLink';
98
98
  LCSSeason : SeasonFederation;
99
99
  LCSProduct? : ProductFederation;
@@ -1713,4 +1713,280 @@ describe('meetsCriteria', () =>{
1713
1713
  expect(results).toBeTruthy();
1714
1714
  });
1715
1715
 
1716
+ });
1717
+
1718
+ describe('getEventsForItemFamilyChanges - conditional eventType', () => {
1719
+ const config = {
1720
+ identifierAtts: {
1721
+ LCSSeason: ['flexPLMSeasonName']
1722
+ },
1723
+ itemPreDevelopmentLifecycleStages: ['concept']
1724
+ } as unknown as FCConfig;
1725
+ const mapFileUtil = new MapFileUtil(new Entities());
1726
+ const dc = new DataConverter(config, mapFileUtil);
1727
+
1728
+ afterEach(() => {
1729
+ jest.restoreAllMocks();
1730
+ });
1731
+
1732
+ it('should use UPSERT_ON_SEASON for LCSProductSeasonLink when isOutboundCreatableFromEntity returns true', async () => {
1733
+ const bppa = new BaseProcessPublishAssortment(config, dc, mapFileUtil);
1734
+
1735
+ const itemFamilyId = 'family123';
1736
+ const prodEntityData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
1737
+ const projectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
1738
+ const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
1739
+ const seasonFed: SeasonFederation = {
1740
+ entityReference: 'assortment:assort123',
1741
+ objectClass: 'LCSSeason',
1742
+ flexPLMSeasonName: 'Fall 2023'
1743
+ };
1744
+
1745
+ const itemFamilyChanges = new ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
1746
+ itemFamilyChanges.familyAdd = true;
1747
+ itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, {
1748
+ item: prodEntityData,
1749
+ projectItem
1750
+ });
1751
+
1752
+ jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
1753
+ jest.spyOn(bppa, 'getProjectItem').mockReturnValue(projectItem);
1754
+ jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
1755
+ jest.spyOn(MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
1756
+ jest.spyOn(TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
1757
+ jest.spyOn(TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
1758
+ jest.spyOn(TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(true);
1759
+
1760
+ const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
1761
+
1762
+ expect(events).toHaveLength(1);
1763
+ expect(events[0].eventType).toBe('UPSERT_ON_SEASON');
1764
+ expect(events[0].objectClass).toBe('LCSProductSeasonLink');
1765
+ expect(TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenCalledWith(
1766
+ undefined,
1767
+ mapFileUtil,
1768
+ projectItem,
1769
+ { item: prodEntityData, assortment }
1770
+ );
1771
+ });
1772
+
1773
+ it('should use UPDATE_ON_SEASON for LCSProductSeasonLink when isOutboundCreatableFromEntity returns false', async () => {
1774
+ const bppa = new BaseProcessPublishAssortment(config, dc, mapFileUtil);
1775
+
1776
+ const itemFamilyId = 'family123';
1777
+ const prodEntityData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
1778
+ const projectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
1779
+ const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
1780
+ const seasonFed: SeasonFederation = {
1781
+ entityReference: 'assortment:assort123',
1782
+ objectClass: 'LCSSeason',
1783
+ flexPLMSeasonName: 'Fall 2023'
1784
+ };
1785
+
1786
+ const itemFamilyChanges = new ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
1787
+ itemFamilyChanges.familyUpdate = true;
1788
+ itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, {
1789
+ item: prodEntityData,
1790
+ projectItem
1791
+ });
1792
+
1793
+ jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
1794
+ jest.spyOn(bppa, 'getProjectItem').mockReturnValue(projectItem);
1795
+ jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
1796
+ jest.spyOn(MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
1797
+ jest.spyOn(TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
1798
+ jest.spyOn(TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
1799
+ jest.spyOn(TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(false);
1800
+
1801
+ const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
1802
+
1803
+ expect(events).toHaveLength(1);
1804
+ expect(events[0].eventType).toBe('UPDATE_ON_SEASON');
1805
+ expect(events[0].objectClass).toBe('LCSProductSeasonLink');
1806
+ expect(TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenCalledWith(
1807
+ undefined,
1808
+ mapFileUtil,
1809
+ projectItem,
1810
+ { item: prodEntityData, assortment }
1811
+ );
1812
+ });
1813
+
1814
+ it('should use UPSERT_ON_SEASON for LCSSKUSeasonLink when isOutboundCreatableFromEntity returns true', async () => {
1815
+ const bppa = new BaseProcessPublishAssortment(config, dc, mapFileUtil);
1816
+
1817
+ const itemFamilyId = 'family123';
1818
+ const colorId = 'color456';
1819
+ const familyItemData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
1820
+ const itemData = { id: colorId, identifier: 'COLOR-456', itemNumber: 'COLOR-456' };
1821
+ const familyProjectItem = { id: 'proj0', updatedOn: '2023-01-14T00:00:00.000Z' };
1822
+ const projectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
1823
+ const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
1824
+ const seasonFed: SeasonFederation = {
1825
+ entityReference: 'assortment:assort123',
1826
+ objectClass: 'LCSSeason',
1827
+ flexPLMSeasonName: 'Fall 2023'
1828
+ };
1829
+
1830
+ const itemFamilyChanges = new ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
1831
+ itemFamilyChanges.colorAdds.push(colorId);
1832
+ itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, {
1833
+ item: familyItemData,
1834
+ projectItem: familyProjectItem
1835
+ });
1836
+ itemFamilyChanges.assortmentItemFullChangeMap.set(colorId, {
1837
+ item: itemData,
1838
+ projectItem
1839
+ });
1840
+
1841
+ jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
1842
+ jest.spyOn(bppa, 'getProjectItem')
1843
+ .mockReturnValueOnce(familyProjectItem)
1844
+ .mockReturnValueOnce(projectItem);
1845
+ jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
1846
+ jest.spyOn(MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
1847
+ jest.spyOn(TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
1848
+ jest.spyOn(TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
1849
+ jest.spyOn(TypeConversionUtils, 'isOutboundCreatableFromEntity')
1850
+ .mockResolvedValueOnce(true) // for family
1851
+ .mockResolvedValueOnce(true); // for SKU
1852
+
1853
+ const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
1854
+
1855
+ // Should have both family and SKU events because familyAssortmentItem is true when colorAdds.length > 0
1856
+ expect(events).toHaveLength(2);
1857
+
1858
+ const skuEvent = events.find(e => e.objectClass === 'LCSSKUSeasonLink');
1859
+ expect(skuEvent.eventType).toBe('UPSERT_ON_SEASON');
1860
+ expect(TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenCalledWith(
1861
+ undefined,
1862
+ mapFileUtil,
1863
+ projectItem,
1864
+ { item: itemData, assortment }
1865
+ );
1866
+ });
1867
+
1868
+ it('should use UPDATE_ON_SEASON for LCSSKUSeasonLink when isOutboundCreatableFromEntity returns false', async () => {
1869
+ const bppa = new BaseProcessPublishAssortment(config, dc, mapFileUtil);
1870
+
1871
+ const itemFamilyId = 'family123';
1872
+ const colorId = 'color456';
1873
+ const familyItemData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
1874
+ const itemData = { id: colorId, identifier: 'COLOR-456', itemNumber: 'COLOR-456' };
1875
+ const familyProjectItem = { id: 'proj0', updatedOn: '2023-01-14T00:00:00.000Z' };
1876
+ const projectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
1877
+ const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
1878
+ const seasonFed: SeasonFederation = {
1879
+ entityReference: 'assortment:assort123',
1880
+ objectClass: 'LCSSeason',
1881
+ flexPLMSeasonName: 'Fall 2023'
1882
+ };
1883
+
1884
+ const itemFamilyChanges = new ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
1885
+ itemFamilyChanges.colorUpdates.push(colorId);
1886
+ itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, {
1887
+ item: familyItemData,
1888
+ projectItem: familyProjectItem
1889
+ });
1890
+ itemFamilyChanges.assortmentItemFullChangeMap.set(colorId, {
1891
+ item: itemData,
1892
+ projectItem
1893
+ });
1894
+
1895
+ jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
1896
+ jest.spyOn(bppa, 'getProjectItem')
1897
+ .mockReturnValueOnce(familyProjectItem)
1898
+ .mockReturnValueOnce(projectItem);
1899
+ jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
1900
+ jest.spyOn(MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
1901
+ jest.spyOn(TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
1902
+ jest.spyOn(TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
1903
+ jest.spyOn(TypeConversionUtils, 'isOutboundCreatableFromEntity')
1904
+ .mockResolvedValueOnce(false) // for family
1905
+ .mockResolvedValueOnce(false); // for SKU
1906
+
1907
+ const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
1908
+
1909
+ // Should have both family and SKU events because familyAssortmentItem is true when colorUpdates.length > 0
1910
+ expect(events).toHaveLength(2);
1911
+
1912
+ const skuEvent = events.find(e => e.objectClass === 'LCSSKUSeasonLink');
1913
+ expect(skuEvent.eventType).toBe('UPDATE_ON_SEASON');
1914
+ expect(TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenCalledWith(
1915
+ undefined,
1916
+ mapFileUtil,
1917
+ projectItem,
1918
+ { item: itemData, assortment }
1919
+ );
1920
+ });
1921
+
1922
+ it('should create both product and SKU events with correct eventTypes based on isOutboundCreatableFromEntity', async () => {
1923
+ const bppa = new BaseProcessPublishAssortment(config, dc, mapFileUtil);
1924
+
1925
+ const itemFamilyId = 'family123';
1926
+ const colorId = 'color456';
1927
+ const prodEntityData = { id: itemFamilyId, identifier: 'FAM-123', itemNumber: 'FAM-123' };
1928
+ const itemData = { id: colorId, identifier: 'COLOR-456', itemNumber: 'COLOR-456' };
1929
+ const familyProjectItem = { id: 'proj1', updatedOn: '2023-01-15T00:00:00.000Z' };
1930
+ const colorProjectItem = { id: 'proj2', updatedOn: '2023-01-15T00:00:00.000Z' };
1931
+ const assortment = { id: 'assort123', name: 'Fall 2023 Assortment', publishToFlexPLM: true };
1932
+ const seasonFed: SeasonFederation = {
1933
+ entityReference: 'assortment:assort123',
1934
+ objectClass: 'LCSSeason',
1935
+ flexPLMSeasonName: 'Fall 2023'
1936
+ };
1937
+
1938
+ const itemFamilyChanges = new ItemFamilyChanges(itemFamilyId, new Date('2023-01-01T00:00:00.000Z'));
1939
+ itemFamilyChanges.familyAdd = true;
1940
+ itemFamilyChanges.colorAdds.push(colorId);
1941
+ itemFamilyChanges.assortmentItemFullChangeMap.set(itemFamilyId, {
1942
+ item: prodEntityData,
1943
+ projectItem: familyProjectItem
1944
+ });
1945
+ itemFamilyChanges.assortmentItemFullChangeMap.set(colorId, {
1946
+ item: itemData,
1947
+ projectItem: colorProjectItem
1948
+ });
1949
+
1950
+ jest.spyOn(bppa, 'getAssortment').mockResolvedValue(assortment);
1951
+ jest.spyOn(bppa, 'getProjectItem')
1952
+ .mockReturnValueOnce(familyProjectItem)
1953
+ .mockReturnValueOnce(colorProjectItem);
1954
+ jest.spyOn(bppa, 'getSeasonalData').mockResolvedValue({ someData: 'value' });
1955
+ jest.spyOn(MapUtil, 'applyTransformMap').mockResolvedValue({ transformedData: 'value' });
1956
+ jest.spyOn(TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['identifier']);
1957
+ jest.spyOn(TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
1958
+ // Product is creatable (true), SKU is not (false)
1959
+ jest.spyOn(TypeConversionUtils, 'isOutboundCreatableFromEntity')
1960
+ .mockResolvedValueOnce(true)
1961
+ .mockResolvedValueOnce(false);
1962
+
1963
+ const events = await bppa.getEventsForItemFamilyChanges(itemFamilyChanges, 'assort123', seasonFed, new Map());
1964
+
1965
+ expect(events).toHaveLength(2);
1966
+
1967
+ const productEvent = events.find(e => e.objectClass === 'LCSProductSeasonLink');
1968
+ const skuEvent = events.find(e => e.objectClass === 'LCSSKUSeasonLink');
1969
+
1970
+ expect(productEvent.eventType).toBe('UPSERT_ON_SEASON');
1971
+ expect(skuEvent.eventType).toBe('UPDATE_ON_SEASON');
1972
+
1973
+ expect(TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenCalledTimes(2);
1974
+ // Verify product event called with assortment entity
1975
+ expect(TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenNthCalledWith(
1976
+ 1,
1977
+ undefined,
1978
+ mapFileUtil,
1979
+ familyProjectItem,
1980
+ { item: prodEntityData, assortment }
1981
+ );
1982
+ // Verify SKU event called with assortment entity
1983
+ expect(TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenNthCalledWith(
1984
+ 2,
1985
+ undefined,
1986
+ mapFileUtil,
1987
+ colorProjectItem,
1988
+ { item: itemData, assortment }
1989
+ );
1990
+ });
1991
+
1716
1992
  });
@@ -906,7 +906,7 @@ export class BaseProcessPublishAssortment {
906
906
  console.info('getEventsForItemFamilyChanges()');
907
907
  const events: SeasonalPayload[] = [];
908
908
  const LCSSeason = Object.assign({}, seasonFed);
909
-
909
+ const assortment = await this.getAssortment(assortmentId);
910
910
  const projectItem = this.getProjectItem(itemFamilyChanges, itemFamilyChanges.itemFamilyId);
911
911
  const familyAssortmentItem = itemFamilyChanges.familyAdd || itemFamilyChanges.familyUpdate || itemFamilyChanges.familyDelete
912
912
  || itemFamilyChanges.colorAdds.length > 0 || itemFamilyChanges.colorUpdates.length > 0 || itemFamilyChanges.colorDeletes.length > 0
@@ -925,8 +925,12 @@ export class BaseProcessPublishAssortment {
925
925
  seasonalData = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonalData, 'LCSProductSeasonLink', 'vibe2flex');
926
926
 
927
927
  const LCSProduct: ProductFederation = await this.getProductFederation(entityReference, itemToFederatedIdMapping, prodEntityData);
928
+
929
+ const isCreatable = await TypeConversionUtils.isOutboundCreatableFromEntity(this.transformMapFile, this.mapFileUtil, projectItem, { item: prodEntityData, assortment });
930
+ const eventType = isCreatable ? 'UPSERT_ON_SEASON' : 'UPDATE_ON_SEASON';
931
+
928
932
  const psUpsert: SeasonalPayload = {
929
- eventType: 'UPSERT_ON_SEASON',
933
+ eventType,
930
934
  objectClass: 'LCSProductSeasonLink',
931
935
  entityReference: 'item:' + entityReference,
932
936
  LCSSeason,
@@ -962,8 +966,12 @@ export class BaseProcessPublishAssortment {
962
966
  seasonalData = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonalData, 'LCSSKUSeasonLink', 'vibe2flex');
963
967
 
964
968
  const LCSSKU: SkuFederation = await this.getSKUFederation(entityReference, itemToFederatedIdMapping, item);
969
+
970
+ const isCreatable = await TypeConversionUtils.isOutboundCreatableFromEntity(this.transformMapFile, this.mapFileUtil, projectItem, { item, assortment });
971
+ const eventType = isCreatable ? 'UPSERT_ON_SEASON' : 'UPDATE_ON_SEASON';
972
+
965
973
  const csUpsert: SeasonalPayload = {
966
- eventType: 'UPSERT_ON_SEASON',
974
+ eventType,
967
975
  objectClass: 'LCSSKUSeasonLink',
968
976
  entityReference: 'item:' + entityReference,
969
977
  LCSSeason,
@@ -2,9 +2,9 @@ export enum EventShortMessageStatus {
2
2
  SUCCESS = 'Success',
3
3
  FAILURE = 'Failure',
4
4
  CREATED = 'Created',
5
- ITEM_FAMILY_ID_MISSING = "Item_family_id_missing",
6
- ITEM_FAMILY_NOT_FOUND = "Item_family_not_found",
7
- PROJECT_ITEM_NOT_FOUND = "Project_item_not_found",
5
+ ITEM_FAMILY_ID_MISSING = 'Item_family_id_missing',
6
+ ITEM_FAMILY_NOT_FOUND = 'Item_family_not_found',
7
+ PROJECT_ITEM_NOT_FOUND = 'Project_item_not_found',
8
8
  MISSING_IDENTIFIER_PROPERTIES = 'Missing_identifier_properties',
9
9
  MISSING_INPUT = 'Missing_input',
10
10
  NOT_CREATABLE = 'Not_creatable',
@@ -139,6 +139,9 @@ exports.mapping = {
139
139
  },
140
140
 
141
141
  packaging: {
142
+ isOutboundCreatable: (entity, context) => {
143
+ return false;
144
+ },
142
145
  vibe2flex: {
143
146
  transformOrder: [{ processor: 'REKEY', rekeyDelete: true, rekeyTransformersKey: 'rekey' }],
144
147
  rekey: {
@@ -166,6 +169,12 @@ exports.mapping = {
166
169
  }
167
170
  return true;
168
171
  },
172
+ isOutboundCreatable: (entity, context) => {
173
+ if (context && context.skipPrefix) {
174
+ return false;
175
+ }
176
+ return true;
177
+ },
169
178
  vibe2flex: {
170
179
  transformOrder: [{ processor: 'REKEY', rekeyDelete: true, rekeyTransformersKey: 'rekey' }],
171
180
  rekey: {
@@ -680,4 +680,105 @@ describe('conversion-utils', () => {
680
680
  });
681
681
 
682
682
  });
683
+
684
+ describe('isOutboundCreatableFromEntity', () =>{
685
+ const mapFileUtil = new MapFileUtil(new Entities());
686
+
687
+ it('should return false for custom-entity:pack', async () =>{
688
+ const spy = jest.spyOn(mapFileUtil, 'getMapFile')
689
+ .mockImplementation(async () =>{
690
+ return mapping;
691
+ });
692
+ const entity = {
693
+ entityType: 'custom-entity',
694
+ typePath: 'custom-entity:pack'
695
+ }
696
+
697
+ try{
698
+ const results = await TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity);
699
+ expect(results).toBeFalsy();
700
+
701
+ } finally {
702
+ spy.mockRestore();
703
+ }
704
+ });
705
+
706
+ it('should return true for custom-entity:prefix', async () =>{
707
+ const spy = jest.spyOn(mapFileUtil, 'getMapFile')
708
+ .mockImplementation(async () =>{
709
+ return mapping;
710
+ });
711
+ const entity = {
712
+ entityType: 'custom-entity',
713
+ typePath: 'custom-entity:prefix'
714
+ }
715
+
716
+ try{
717
+ const results = await TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity);
718
+ expect(results).toBeTruthy();
719
+
720
+ } finally {
721
+ spy.mockRestore();
722
+ }
723
+ });
724
+
725
+ it('should pass context to isOutboundCreatable function', async () =>{
726
+ const spy = jest.spyOn(mapFileUtil, 'getMapFile')
727
+ .mockImplementation(async () =>{
728
+ return mapping;
729
+ });
730
+ const entity = {
731
+ entityType: 'custom-entity',
732
+ typePath: 'custom-entity:prefix'
733
+ }
734
+ const context = { skipPrefix: true };
735
+
736
+ try{
737
+ const results = await TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity, context);
738
+ expect(results).toBeFalsy();
739
+
740
+ } finally {
741
+ spy.mockRestore();
742
+ }
743
+ });
744
+
745
+ it('should work without context parameter', async () =>{
746
+ const spy = jest.spyOn(mapFileUtil, 'getMapFile')
747
+ .mockImplementation(async () =>{
748
+ return mapping;
749
+ });
750
+ const entity = {
751
+ entityType: 'custom-entity',
752
+ typePath: 'custom-entity:prefix'
753
+ }
754
+
755
+ try{
756
+ const results = await TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity);
757
+ expect(results).toBeTruthy();
758
+
759
+ } finally {
760
+ spy.mockRestore();
761
+ }
762
+ });
763
+
764
+ it('should default to true when no mapping exists', async () =>{
765
+ const spy = jest.spyOn(mapFileUtil, 'getMapFile')
766
+ .mockImplementation(async () =>{
767
+ return mapping;
768
+ });
769
+ const entity = {
770
+ entityType: 'custom-entity',
771
+ typePath: 'custom-entity:catName'
772
+ }
773
+
774
+ try{
775
+ const results = await TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity);
776
+ expect(results).toBeTruthy();
777
+
778
+ } finally {
779
+ spy.mockRestore();
780
+ }
781
+ });
782
+
783
+ });
683
784
  });
@@ -351,6 +351,32 @@ export class TypeConversionUtils {
351
351
  return isInboundCreatable;
352
352
  }
353
353
 
354
+ static async isOutboundCreatableFromEntity(fileId: string, mapFileUtil: MapFileUtil, entity: any, context?: any): Promise<boolean> {
355
+
356
+ let isOutboundCreatable = true;
357
+
358
+ if (!fileId) {
359
+ return isOutboundCreatable;
360
+ }
361
+
362
+ let mapKey: string | undefined;
363
+ try {
364
+ mapKey = await this.getMapKey(fileId, mapFileUtil, entity, TypeConversionUtils.VIBE2FLEX_DIRECTION);
365
+ } catch {
366
+ return isOutboundCreatable;
367
+ }
368
+
369
+ if (!mapKey) {
370
+ return isOutboundCreatable;
371
+ }
372
+
373
+ const mapData = await MapUtil.getFullMapSection(fileId, mapFileUtil, mapKey);
374
+ if (mapData && mapData['isOutboundCreatable']) {
375
+ isOutboundCreatable = await mapData['isOutboundCreatable'](entity, context);
376
+ }
377
+ return isOutboundCreatable;
378
+ }
379
+
354
380
  static getObjectType(object:any) {
355
381
  let objectType = object['flexPLMObjectClass'];
356
382
  return objectType;