@contrail/flexplm 1.7.8 → 1.8.0-alpha.4767312

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/README.md CHANGED
@@ -1 +1,14 @@
1
- Core model for contrail, which establishes the base peristence services (without dependency on a persistence implementaiton) and core NestJS elements such as the InvocationContext.
1
+ Core model for contrail, which establishes the base peristence services (without dependency on a persistence implementaiton) and core NestJS elements such as the InvocationContext.
2
+
3
+ ## LCSCountry defaults
4
+ `LCSCountry` <-> VibeIQ `custom-entity:country` works with built-in defaults for the type mapping. The built-in conventions are:
5
+
6
+ | Item | Default |
7
+ |---|---|
8
+ | VibeIQ type path | `custom-entity:country` (exact, lowercase; matched case-sensitively in both directions) |
9
+ | FlexPLM class / type path | `LCSCountry` / `Country` |
10
+ | Identifier property | `vibeIQIdentifier` (same key in both systems; no rekey runs without a mapping) |
11
+ | Informational property | `name` |
12
+
13
+ A mapping section for `LCSCountry` overrides these defaults when selected by the type-conversion routing. If your mapping defines `typeConversion.vibe2flex['custom-entity'].getMapKey`, make it return `'LCSCountry'` for Country entities to apply that section's outbound overrides; an empty key bypasses the section.
14
+
@@ -362,6 +362,10 @@ export interface MappingSection {
362
362
  * }
363
363
  *
364
364
  * @example
365
+ * // FlexPLM creates Countries (VibeIQ creates them by default).
366
+ * LCSCountry: { isInboundCreatable: () => true }
367
+ *
368
+ * @example
365
369
  * // add-to-project flow — Flex drives project-item creation.
366
370
  * // Paired with isOutboundCreatable: () => false to make
367
371
  * // outbound update-only.
@@ -707,6 +711,7 @@ interface MappingFileBase {
707
711
  * | `item` | `LCSProduct` / `LCSSKU` | Syncs once `lifecycleStage` is out of `concept`. Sends Primary Viewable as the thumbnail (no other images). Creates `LCSProduct` / `LCSSKU` if missing; auto-retries `LCSSKU` creation when the parent `LCSProduct` isn't ready yet. |
708
712
  * | `color` | `LCSColor` | Syncs on create / update. Sends Primary Viewable as the thumbnail. Creates `LCSColor` if missing. |
709
713
  * | `custom-entity` | `LCSRevisableEntity` | Syncs on create / update. Creates `LCSRevisableEntity` if missing. Workflows should gate on the custom-entity subtype so only the intended subtypes publish. |
714
+ * | `custom-entity` (`custom-entity:country`) | `LCSCountry` | Built-in defaults for type path (`Country`), identifier (`vibeIQIdentifier`, same key in both systems) and name; the mapping section overrides each default. Syncs on create / update. Creates `LCSCountry` if missing. Workflows should gate on the custom-entity subtype so only the intended subtypes publish. |
710
715
  * | `project-item` | `LCSProductSeasonLink` / `LCSSKUSeasonLink` | Syncs on plan publish. Requires the assortment to have `publishToFlexPLM=true` and `flexPLMSeasonName` set. Adds the Product / SKU to the `LCSSeason` if not already linked. Only project-items changed since the last publish are sent (configurable to reach further back). Processing on the FlexPLM side is async via a Windchill Queue. |
711
716
  * | `item:material` | `LCSMaterial` | Supported via custom mapping section + `typeConversion`. Route `item:material` typePath to a mapping section that declares `getClass: () => 'LCSMaterial'` and `getSoftType: () => 'Material'`. See {@link TypeConversion} for the `LCSMaterial.processAsItem` config flag that pairs with this. |
712
717
  * | `custom-entity` | `LCSLast` / `LCSLifecycleManaged` | **Not yet supported.** |
@@ -724,6 +729,7 @@ interface MappingFileBase {
724
729
  * | `LCSLifecycleManaged` | `custom-entity` | Configurable to create new custom-entities; updates existing. |
725
730
  * | `LCSMaterial` | `custom-entity` (default) or `item:material` | Default: custom-entity. To route to `item:material` instead, set `{ "LCSMaterial": { "processAsItem": true } }` in the connector app config and write a mapping section with `getClass: () => 'item'` + `getSoftType: () => 'item:material'`. Wire it via `flex2vibe.LCSMaterial.getMapKey` returning that section's name. |
726
731
  * | `LCSRevisableEntity` | `custom-entity` | Configurable to create new custom-entities; updates existing. |
732
+ * | `LCSCountry` | `custom-entity` (`custom-entity:country`) | Built-in defaults for type path and identifier (`vibeIQIdentifier`, same key in both systems). Updates existing Countries; configurable to create new ones with `isInboundCreatable` (for example `LCSCountry: { isInboundCreatable: () => true }`). |
727
733
  * | (any) | `item` (direct, no link) | **Not yet supported.** |
728
734
  *
729
735
  * @example
@@ -924,4 +924,61 @@ describe('conversion-utils', () => {
924
924
  expect(results).toBeTruthy();
925
925
  });
926
926
  });
927
+ describe('Country with no mapping section (SDK defaults)', () => {
928
+ const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
929
+ const mapWithoutCountry = {
930
+ typeConversion: {
931
+ vibe2flex: { 'custom-entity': { getMapKey: () => '' } },
932
+ flex2vibe: {}
933
+ }
934
+ };
935
+ let spy;
936
+ beforeEach(() => {
937
+ spy = jest.spyOn(mapFileUtil, 'getMapFile').mockImplementation(async () => mapWithoutCountry);
938
+ });
939
+ afterEach(() => {
940
+ spy.mockRestore();
941
+ });
942
+ it('should use the defaults when sending a Country from VibeIQ to FlexPLM', async () => {
943
+ const entity = { entityType: 'custom-entity', typePath: 'custom-entity:country' };
944
+ expect(await type_conversion_utils_1.TypeConversionUtils.getObjectClass(TRANSFORM_MAP_FILE, mapFileUtil, entity)).toEqual('LCSCountry');
945
+ expect(await type_conversion_utils_1.TypeConversionUtils.getObjectTypePath(TRANSFORM_MAP_FILE, mapFileUtil, entity)).toEqual('Country');
946
+ expect(await type_conversion_utils_1.TypeConversionUtils.getIdentifierProperties(TRANSFORM_MAP_FILE, mapFileUtil, entity)).toEqual(['vibeIQIdentifier']);
947
+ expect(await type_conversion_utils_1.TypeConversionUtils.getInformationalProperties(TRANSFORM_MAP_FILE, mapFileUtil, entity)).toEqual(['name']);
948
+ });
949
+ it('should use the defaults when receiving a Country from FlexPLM into VibeIQ', async () => {
950
+ const object = { flexPLMObjectClass: 'LCSCountry', flexPLMTypePath: 'Country' };
951
+ expect(await type_conversion_utils_1.TypeConversionUtils.getMapKeyFromObject(TRANSFORM_MAP_FILE, mapFileUtil, object, type_conversion_utils_1.TypeConversionUtils.FLEX2VIBE_DIRECTION)).toEqual('LCSCountry');
952
+ expect(await type_conversion_utils_1.TypeConversionUtils.getEntityClassFromObject(TRANSFORM_MAP_FILE, mapFileUtil, object)).toEqual('custom-entity');
953
+ expect(await type_conversion_utils_1.TypeConversionUtils.getEntityTypePathFromObject(TRANSFORM_MAP_FILE, mapFileUtil, object)).toEqual('custom-entity:country');
954
+ expect(await type_conversion_utils_1.TypeConversionUtils.getIdentifierPropertiesFromObject(TRANSFORM_MAP_FILE, mapFileUtil, object)).toEqual(['vibeIQIdentifier']);
955
+ expect(await type_conversion_utils_1.TypeConversionUtils.getInformationalPropertiesFromObject(TRANSFORM_MAP_FILE, mapFileUtil, object)).toEqual(['name']);
956
+ });
957
+ it('should let a mapping section override the Country defaults', async () => {
958
+ const overriding = {
959
+ typeConversion: { vibe2flex: { 'custom-entity': { getMapKey: () => 'LCSCountry' } }, flex2vibe: {} },
960
+ LCSCountry: { getIdentifierProperties: () => ['isoCode'] }
961
+ };
962
+ spy.mockImplementation(async () => overriding);
963
+ const entity = { entityType: 'custom-entity', typePath: 'custom-entity:country' };
964
+ expect(await type_conversion_utils_1.TypeConversionUtils.getIdentifierProperties(TRANSFORM_MAP_FILE, mapFileUtil, entity)).toEqual(['isoCode']);
965
+ });
966
+ it('should leave the section key of a Country unchanged when the org custom entity routing returns an empty key', async () => {
967
+ const country = { entityType: 'custom-entity', typePath: 'custom-entity:country' };
968
+ expect(await type_conversion_utils_1.TypeConversionUtils.getMapKey(TRANSFORM_MAP_FILE, mapFileUtil, country, type_conversion_utils_1.TypeConversionUtils.VIBE2FLEX_DIRECTION)).toEqual('');
969
+ });
970
+ it('should not allow inbound creation of a Country when the mapping has no isInboundCreatable gate', async () => {
971
+ const object = { flexPLMObjectClass: 'LCSCountry', flexPLMTypePath: 'Country' };
972
+ expect(await type_conversion_utils_1.TypeConversionUtils.isInboundCreatableFromObject(TRANSFORM_MAP_FILE, mapFileUtil, object)).toBe(false);
973
+ });
974
+ it('should allow inbound creation of a Country when the LCSCountry section sets isInboundCreatable', async () => {
975
+ spy.mockImplementation(async () => ({ ...mapWithoutCountry, LCSCountry: { isInboundCreatable: () => true } }));
976
+ const object = { flexPLMObjectClass: 'LCSCountry', flexPLMTypePath: 'Country' };
977
+ expect(await type_conversion_utils_1.TypeConversionUtils.isInboundCreatableFromObject(TRANSFORM_MAP_FILE, mapFileUtil, object)).toBe(true);
978
+ });
979
+ it('should allow outbound creation of a Country by default', async () => {
980
+ const entity = { entityType: 'custom-entity', typePath: 'custom-entity:country' };
981
+ expect(await type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity)).toBe(true);
982
+ });
983
+ });
927
984
  });
@@ -3,6 +3,11 @@ export declare class TypeDefaults {
3
3
  static NO_OBJECT_CLASS: string;
4
4
  static NO_TYPE_PATH: string;
5
5
  static processLCSMaterialAsItem: boolean;
6
+ static COUNTRY_OBJECT_CLASS: string;
7
+ static COUNTRY_VIBE_TYPE_PATH: string;
8
+ static COUNTRY_FLEX_TYPE_PATH: string;
9
+ static COUNTRY_IDENTIFIER_PROPERTY: string;
10
+ static COUNTRY_INFORMATIONAL_PROPERTY: string;
6
11
  constructor();
7
12
  /** Applies values from the resolved config to TypeDefaults static state.
8
13
  * Currently toggles whether LCSMaterial is treated as an item (new) or a
@@ -14,6 +19,13 @@ export declare class TypeDefaults {
14
19
  */
15
20
  static applyConfig(config: any): void;
16
21
  static isPropertyTrue(value: any): boolean;
22
+ /** True when the VibeIQ entity is a Country: a custom-entity whose typePath is
23
+ * exactly 'custom-entity:country' (compared case-sensitively, as on inbound).
24
+ *
25
+ * @param entity VibeIQ entity
26
+ * @returns boolean
27
+ */
28
+ static isCountryEntity(entity: any): boolean;
17
29
  /**Takes in full entity and returs the default FlexPLM
18
30
  * object class.
19
31
  *
@@ -18,6 +18,16 @@ class TypeDefaults {
18
18
  static isPropertyTrue(value) {
19
19
  return value === true || (typeof value === 'string' && value.toLowerCase() === 'true');
20
20
  }
21
+ /** True when the VibeIQ entity is a Country: a custom-entity whose typePath is
22
+ * exactly 'custom-entity:country' (compared case-sensitively, as on inbound).
23
+ *
24
+ * @param entity VibeIQ entity
25
+ * @returns boolean
26
+ */
27
+ static isCountryEntity(entity) {
28
+ const typePath = entity?.['typePath'];
29
+ return typeof typePath === 'string' && typePath === TypeDefaults.COUNTRY_VIBE_TYPE_PATH;
30
+ }
21
31
  /**Takes in full entity and returs the default FlexPLM
22
32
  * object class.
23
33
  *
@@ -47,7 +57,7 @@ class TypeDefaults {
47
57
  objectClass = 'LCSColor';
48
58
  }
49
59
  else if ('custom-entity' === entityType) {
50
- objectClass = 'LCSRevisableEntity';
60
+ objectClass = TypeDefaults.isCountryEntity(entity) ? TypeDefaults.COUNTRY_OBJECT_CLASS : 'LCSRevisableEntity';
51
61
  }
52
62
  else if ('project' === entityType) {
53
63
  objectClass = 'LCSSeason';
@@ -80,7 +90,7 @@ class TypeDefaults {
80
90
  typePath = 'Color';
81
91
  break;
82
92
  case 'custom-entity':
83
- typePath = 'Revisable Entity';
93
+ typePath = TypeDefaults.isCountryEntity(entity) ? TypeDefaults.COUNTRY_FLEX_TYPE_PATH : 'Revisable Entity';
84
94
  break;
85
95
  case 'project':
86
96
  typePath = 'Season';
@@ -122,9 +132,11 @@ class TypeDefaults {
122
132
  }
123
133
  break;
124
134
  case 'color':
125
- case 'custom-entity':
126
135
  identifierProps.push('name');
127
136
  break;
137
+ case 'custom-entity':
138
+ identifierProps.push(TypeDefaults.isCountryEntity(entity) ? TypeDefaults.COUNTRY_IDENTIFIER_PROPERTY : 'name');
139
+ break;
128
140
  }
129
141
  return identifierProps;
130
142
  }
@@ -149,6 +161,9 @@ class TypeDefaults {
149
161
  else if (['project', 'assortment'].includes(entityType)) {
150
162
  properties.push('name');
151
163
  }
164
+ else if (TypeDefaults.isCountryEntity(entity)) {
165
+ properties.push(TypeDefaults.COUNTRY_INFORMATIONAL_PROPERTY);
166
+ }
152
167
  return properties;
153
168
  }
154
169
  /** Returns the VibeIQ entity type from an entity.
@@ -186,8 +201,8 @@ class TypeDefaults {
186
201
  ? ['LCSProduct', 'LCSSKU', 'LCSMaterial']
187
202
  : ['LCSProduct', 'LCSSKU'];
188
203
  const customEntityClasses = TypeDefaults.processLCSMaterialAsItem
189
- ? ['LCSRevisableEntity', 'LCSLifecycleManaged', 'LCSLast']
190
- : ['LCSRevisableEntity', 'LCSLifecycleManaged', 'LCSLast', 'LCSMaterial'];
204
+ ? ['LCSRevisableEntity', 'LCSLifecycleManaged', 'LCSLast', TypeDefaults.COUNTRY_OBJECT_CLASS]
205
+ : ['LCSRevisableEntity', 'LCSLifecycleManaged', 'LCSLast', 'LCSMaterial', TypeDefaults.COUNTRY_OBJECT_CLASS];
191
206
  if (itemClasses.includes(objectClass)) {
192
207
  entityClass = 'item';
193
208
  }
@@ -237,6 +252,9 @@ class TypeDefaults {
237
252
  case 'SeasonGroup':
238
253
  typePath = 'assortment';
239
254
  break;
255
+ case 'LCSCountry':
256
+ typePath = TypeDefaults.COUNTRY_VIBE_TYPE_PATH;
257
+ break;
240
258
  }
241
259
  if (typePath === '')
242
260
  throw Error(TypeDefaults.NO_TYPE_PATH);
@@ -271,6 +289,9 @@ class TypeDefaults {
271
289
  case 'SeasonGroup':
272
290
  identifierProps.push('seasonGroupName');
273
291
  break;
292
+ case 'LCSCountry':
293
+ identifierProps.push(TypeDefaults.COUNTRY_IDENTIFIER_PROPERTY);
294
+ break;
274
295
  case 'LCSColor':
275
296
  case 'LCSRevisableEntity':
276
297
  case 'LCSLifecycleManaged':
@@ -302,6 +323,9 @@ class TypeDefaults {
302
323
  else if (['LCSSeason', 'SeasonGroup'].includes(objectClass)) {
303
324
  properties.push('name');
304
325
  }
326
+ else if (TypeDefaults.COUNTRY_OBJECT_CLASS === objectClass) {
327
+ properties.push(TypeDefaults.COUNTRY_INFORMATIONAL_PROPERTY);
328
+ }
305
329
  return properties;
306
330
  }
307
331
  static getObjectClass(object) {
@@ -313,3 +337,8 @@ TypeDefaults.NO_ENTITY_TYPE = 'Not able to determine the entity type of the enti
313
337
  TypeDefaults.NO_OBJECT_CLASS = 'Please ensure that the flexPLMObjectClass property is provided.';
314
338
  TypeDefaults.NO_TYPE_PATH = 'Please ensure that the flexPLMTypePath property is provided.';
315
339
  TypeDefaults.processLCSMaterialAsItem = false;
340
+ TypeDefaults.COUNTRY_OBJECT_CLASS = 'LCSCountry';
341
+ TypeDefaults.COUNTRY_VIBE_TYPE_PATH = 'custom-entity:country';
342
+ TypeDefaults.COUNTRY_FLEX_TYPE_PATH = 'Country';
343
+ TypeDefaults.COUNTRY_IDENTIFIER_PROPERTY = 'vibeIQIdentifier';
344
+ TypeDefaults.COUNTRY_INFORMATIONAL_PROPERTY = 'name';
@@ -42,6 +42,28 @@ describe('Type Defaults', () => {
42
42
  const objectClass = type_defaults_1.TypeDefaults.getDefaultObjectClass(newData);
43
43
  expect(objectClass).toEqual('LCSColor');
44
44
  });
45
+ it('should return LCSCountry for a Country custom entity', () => {
46
+ const newData = {
47
+ entityType: 'custom-entity',
48
+ typePath: 'custom-entity:country'
49
+ };
50
+ const objectClass = type_defaults_1.TypeDefaults.getDefaultObjectClass(newData);
51
+ expect(objectClass).toEqual('LCSCountry');
52
+ });
53
+ it('should return LCSRevisableEntity when the Country typePath uses different casing', () => {
54
+ const newData = {
55
+ entityType: 'custom-entity',
56
+ typePath: 'custom-entity:Country'
57
+ };
58
+ expect(type_defaults_1.TypeDefaults.getDefaultObjectClass(newData)).toEqual('LCSRevisableEntity');
59
+ });
60
+ it('should return LCSRevisableEntity for a custom entity that is not a Country', () => {
61
+ const newData = {
62
+ entityType: 'custom-entity',
63
+ typePath: 'custom-entity:exchangeRate'
64
+ };
65
+ expect(type_defaults_1.TypeDefaults.getDefaultObjectClass(newData)).toEqual('LCSRevisableEntity');
66
+ });
45
67
  it('LCSRevisableEntity', () => {
46
68
  const newData = {
47
69
  entityType: 'custom-entity'
@@ -120,6 +142,20 @@ describe('Type Defaults', () => {
120
142
  const typePath = type_defaults_1.TypeDefaults.getDefaultObjectTypePath(newData);
121
143
  expect(typePath).toEqual('Color');
122
144
  });
145
+ it('should return Country as the FlexPLM type path for a Country custom entity', () => {
146
+ const newData = {
147
+ entityType: 'custom-entity',
148
+ typePath: 'custom-entity:country'
149
+ };
150
+ expect(type_defaults_1.TypeDefaults.getDefaultObjectTypePath(newData)).toEqual('Country');
151
+ });
152
+ it('should return Revisable Entity as the type path for a custom entity that is not a Country', () => {
153
+ const newData = {
154
+ entityType: 'custom-entity',
155
+ typePath: 'custom-entity:exchangeRate'
156
+ };
157
+ expect(type_defaults_1.TypeDefaults.getDefaultObjectTypePath(newData)).toEqual('Revisable Entity');
158
+ });
123
159
  it('custom-entity', () => {
124
160
  const newData = {
125
161
  entityType: 'custom-entity'
@@ -219,6 +255,21 @@ describe('Type Defaults', () => {
219
255
  expect(defaultIdentifiers).toContain('name');
220
256
  expect(defaultIdentifiers).toHaveLength(1);
221
257
  });
258
+ it('should return vibeIQIdentifier as the identifier for a Country custom entity', () => {
259
+ const newData = {
260
+ entityType: 'custom-entity',
261
+ typePath: 'custom-entity:country'
262
+ };
263
+ const defaultIdentifiers = type_defaults_1.TypeDefaults.getDefaultIdentifierProperties(newData);
264
+ expect(defaultIdentifiers).toEqual(['vibeIQIdentifier']);
265
+ });
266
+ it('should return name as the identifier for a custom entity that is not a Country', () => {
267
+ const newData = {
268
+ entityType: 'custom-entity',
269
+ typePath: 'custom-entity:exchangeRate'
270
+ };
271
+ expect(type_defaults_1.TypeDefaults.getDefaultIdentifierProperties(newData)).toEqual(['name']);
272
+ });
222
273
  it('custom-entity', () => {
223
274
  const newData = {
224
275
  entityType: 'custom-entity'
@@ -270,6 +321,20 @@ describe('Type Defaults', () => {
270
321
  const defaultIdentifiers = type_defaults_1.TypeDefaults.getDefaultInformationalProperties(newData);
271
322
  expect(defaultIdentifiers).toHaveLength(0);
272
323
  });
324
+ it('should return name as the informational property for a Country custom entity', () => {
325
+ const newData = {
326
+ entityType: 'custom-entity',
327
+ typePath: 'custom-entity:country'
328
+ };
329
+ expect(type_defaults_1.TypeDefaults.getDefaultInformationalProperties(newData)).toEqual(['name']);
330
+ });
331
+ it('should return no informational properties for a custom entity that is not a Country', () => {
332
+ const newData = {
333
+ entityType: 'custom-entity',
334
+ typePath: 'custom-entity:exchangeRate'
335
+ };
336
+ expect(type_defaults_1.TypeDefaults.getDefaultInformationalProperties(newData)).toEqual([]);
337
+ });
273
338
  it('custom-entity', () => {
274
339
  const newData = {
275
340
  entityType: 'custom-entity'
@@ -278,6 +343,19 @@ describe('Type Defaults', () => {
278
343
  expect(defaultIdentifiers).toHaveLength(0);
279
344
  });
280
345
  });
346
+ describe('isCountryEntity', () => {
347
+ it('should return true for the custom-entity:country type path', () => {
348
+ expect(type_defaults_1.TypeDefaults.isCountryEntity({ typePath: 'custom-entity:country' })).toBe(true);
349
+ });
350
+ it('should return false when the typePath differs only in casing', () => {
351
+ expect(type_defaults_1.TypeDefaults.isCountryEntity({ typePath: 'custom-entity:Country' })).toBe(false);
352
+ });
353
+ it('should return false for another subtype, a missing typePath, or an undefined entity', () => {
354
+ expect(type_defaults_1.TypeDefaults.isCountryEntity({ typePath: 'custom-entity:other' })).toBe(false);
355
+ expect(type_defaults_1.TypeDefaults.isCountryEntity({})).toBe(false);
356
+ expect(type_defaults_1.TypeDefaults.isCountryEntity(undefined)).toBe(false);
357
+ });
358
+ });
281
359
  describe('getEntityType', () => {
282
360
  it('entityType', () => {
283
361
  const entityType = 'item';
@@ -355,6 +433,24 @@ describe('Type Defaults', () => {
355
433
  const entityClass = type_defaults_1.TypeDefaults.getDefaultEntityClass(object);
356
434
  expect(entityClass).toBe('assortment');
357
435
  });
436
+ it('should return custom-entity as the entity class for LCSCountry', () => {
437
+ const object = {
438
+ flexPLMObjectClass: 'LCSCountry'
439
+ };
440
+ expect(type_defaults_1.TypeDefaults.getDefaultEntityClass(object)).toBe('custom-entity');
441
+ });
442
+ it('should return custom-entity for LCSCountry when LCSMaterial processAsItem is true', () => {
443
+ const object = {
444
+ flexPLMObjectClass: 'LCSCountry'
445
+ };
446
+ type_defaults_1.TypeDefaults.applyConfig({ LCSMaterial: { processAsItem: true } });
447
+ try {
448
+ expect(type_defaults_1.TypeDefaults.getDefaultEntityClass(object)).toBe('custom-entity');
449
+ }
450
+ finally {
451
+ type_defaults_1.TypeDefaults.applyConfig({});
452
+ }
453
+ });
358
454
  it('custom-entity - LCSRevisableEntity', () => {
359
455
  const object = {
360
456
  flexPLMObjectClass: 'LCSRevisableEntity'
@@ -419,6 +515,12 @@ describe('Type Defaults', () => {
419
515
  const typePath = type_defaults_1.TypeDefaults.getDefaultEntityTypePath(object);
420
516
  expect(typePath).toBe('project-item');
421
517
  });
518
+ it('should return custom-entity:country as the VibeIQ type path for LCSCountry', () => {
519
+ const object = {
520
+ flexPLMObjectClass: 'LCSCountry'
521
+ };
522
+ expect(type_defaults_1.TypeDefaults.getDefaultEntityTypePath(object)).toBe('custom-entity:country');
523
+ });
422
524
  it('LCSColor', () => {
423
525
  const object = {
424
526
  flexPLMObjectClass: 'LCSColor'
@@ -501,6 +603,12 @@ describe('Type Defaults', () => {
501
603
  expect(defaultIdentifiers).toContain('name');
502
604
  expect(defaultIdentifiers).toHaveLength(1);
503
605
  });
606
+ it('should return vibeIQIdentifier as the identifier for an LCSCountry object', () => {
607
+ const object = {
608
+ flexPLMObjectClass: 'LCSCountry'
609
+ };
610
+ expect(type_defaults_1.TypeDefaults.getDefaultIdentifierPropertiesFromObject(object)).toEqual(['vibeIQIdentifier']);
611
+ });
504
612
  it('LCSRevisableEntity', () => {
505
613
  const object = {
506
614
  flexPLMObjectClass: 'LCSRevisableEntity'
@@ -541,6 +649,12 @@ describe('Type Defaults', () => {
541
649
  });
542
650
  }); //getDefaultIdentifierPropertiesFromObject
543
651
  describe('getDefaultInformationalPropertiesFromObject', () => {
652
+ it('should return name as the informational property for an LCSCountry object', () => {
653
+ const object = {
654
+ flexPLMObjectClass: 'LCSCountry'
655
+ };
656
+ expect(type_defaults_1.TypeDefaults.getDefaultInformationalPropertiesFromObject(object)).toEqual(['name']);
657
+ });
544
658
  it('LCSProduct', () => {
545
659
  const object = {
546
660
  flexPLMObjectClass: 'LCSProduct'
@@ -4,6 +4,7 @@ exports.TypeUtils = void 0;
4
4
  const app_framework_1 = require("@contrail/app-framework");
5
5
  const sdk_1 = require("@contrail/sdk");
6
6
  const type_conversion_utils_1 = require("./type-conversion-utils");
7
+ const type_defaults_1 = require("./type-defaults");
7
8
  class TypeUtils {
8
9
  constructor() {
9
10
  this.typesObj = new sdk_1.Types();
@@ -14,6 +15,11 @@ class TypeUtils {
14
15
  id,
15
16
  relations: ['typeProperties', 'typeInterfaces']
16
17
  });
18
+ // The API answers 200 with an empty body for a missing/deleted type, which the SDK returns as null.
19
+ if (!type) {
20
+ throw new Error('getTypeById: type not found for typeId: ' + id
21
+ + '. The entity references a type that no longer exists.');
22
+ }
17
23
  return type;
18
24
  }
19
25
  async getByRootAndPath(options) {
@@ -52,7 +58,7 @@ class TypeUtils {
52
58
  objectClass = 'LCSColor';
53
59
  }
54
60
  else if ('custom-entity' === entityType) {
55
- objectClass = 'LCSRevisableEntity';
61
+ objectClass = type_defaults_1.TypeDefaults.isCountryEntity(newData) ? type_defaults_1.TypeDefaults.COUNTRY_OBJECT_CLASS : 'LCSRevisableEntity';
56
62
  }
57
63
  return objectClass;
58
64
  }
@@ -89,7 +95,7 @@ class TypeUtils {
89
95
  root: 'color'
90
96
  };
91
97
  }
92
- else if (['LCSBusinessObject', 'LCSRevisableEntity', 'LCSLast'].includes(objectClass)) {
98
+ else if (['LCSBusinessObject', 'LCSRevisableEntity', 'LCSLast', type_defaults_1.TypeDefaults.COUNTRY_OBJECT_CLASS].includes(objectClass)) {
93
99
  //TODO need to write logic using mapping file
94
100
  tco = {
95
101
  root: 'custom-entity'
@@ -47,6 +47,31 @@ describe('TypeUtils.getEventObjectClass() tests', () => {
47
47
  const objectClass = tu.getEventObjectClass(entityType, newData);
48
48
  expect(objectClass).toEqual('LCSRevisableEntity');
49
49
  });
50
+ it('should return LCSCountry for a custom entity with the custom-entity:country typePath', () => {
51
+ const entityType = 'custom-entity';
52
+ const newData = { typePath: 'custom-entity:country' };
53
+ const objectClass = tu.getEventObjectClass(entityType, newData);
54
+ expect(objectClass).toEqual('LCSCountry');
55
+ });
56
+ it('should return LCSRevisableEntity when the Country typePath uses different casing', () => {
57
+ const objectClass = tu.getEventObjectClass('custom-entity', { typePath: 'custom-entity:Country' });
58
+ expect(objectClass).toEqual('LCSRevisableEntity');
59
+ });
60
+ it('should return LCSRevisableEntity for a custom entity that is not a Country', () => {
61
+ const objectClass = tu.getEventObjectClass('custom-entity', { typePath: 'custom-entity:exchangeRate' });
62
+ expect(objectClass).toEqual('LCSRevisableEntity');
63
+ });
64
+ });
65
+ describe('TypeUtils.getEntityTypeClientOptions() tests', () => {
66
+ const tu = new type_utils_1.TypeUtils();
67
+ it('should resolve LCSCountry to the custom-entity root type', () => {
68
+ const options = tu.getEntityTypeClientOptions('LCSCountry', {});
69
+ expect(options).toEqual({ root: 'custom-entity' });
70
+ });
71
+ it('should keep resolving LCSRevisableEntity to the custom-entity root type', () => {
72
+ const options = tu.getEntityTypeClientOptions('LCSRevisableEntity', {});
73
+ expect(options).toEqual({ root: 'custom-entity' });
74
+ });
50
75
  });
51
76
  describe('filterProperties-item', () => {
52
77
  const itemType = {
@@ -190,3 +215,16 @@ describe('filterProperties-color', () => {
190
215
  expect(filteredProps.length).toEqual(len);
191
216
  });
192
217
  });
218
+ describe('TypeUtils.getTypeById() tests', () => {
219
+ it('should throw an error containing the missing type id when the type does not exist', async () => {
220
+ const typeUtils = new type_utils_1.TypeUtils();
221
+ typeUtils['typesObj'] = { getType: jest.fn().mockResolvedValue(null) };
222
+ await expect(typeUtils.getTypeById('missingTypeId')).rejects.toThrow('type not found for typeId: missingTypeId');
223
+ });
224
+ it('should return the same type object when the type exists', async () => {
225
+ const typeUtils = new type_utils_1.TypeUtils();
226
+ const type = { id: 'typeId', typeProperties: [] };
227
+ typeUtils['typesObj'] = { getType: jest.fn().mockResolvedValue(type) };
228
+ expect(await typeUtils.getTypeById('typeId')).toBe(type);
229
+ });
230
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/flexplm",
3
- "version": "1.7.8",
3
+ "version": "1.8.0-alpha.4767312",
4
4
  "description": "Library used for integration with flexplm.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",