@contrail/flexplm 1.8.0-alpha.562c8dd → 1.8.0-alpha.881e578
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 +3 -8
- package/lib/interfaces/mapping-file.d.ts +6 -9
- package/lib/util/type-conversion-utils.d.ts +0 -19
- package/lib/util/type-conversion-utils.js +0 -50
- package/lib/util/type-conversion-utils.spec.js +13 -78
- package/lib/util/type-defaults.d.ts +1 -1
- package/lib/util/type-defaults.js +2 -2
- package/lib/util/type-defaults.spec.js +5 -2
- package/lib/util/type-utils.js +5 -0
- package/lib/util/type-utils.spec.js +15 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
Core model for contrail, which establishes the base
|
|
1
|
+
Core model for contrail, which establishes the base persistence services (without dependency on a persistence implementaiton) and core NestJS elements such as the InvocationContext.
|
|
2
2
|
|
|
3
3
|
## LCSCountry defaults
|
|
4
4
|
`LCSCountry` <-> VibeIQ `custom-entity:country` works with built-in defaults for the type mapping. The built-in conventions are:
|
|
5
5
|
|
|
6
6
|
| Item | Default |
|
|
7
7
|
|---|---|
|
|
8
|
-
| VibeIQ type path | `custom-entity:country` (case-
|
|
8
|
+
| VibeIQ type path | `custom-entity:country` (exact, lowercase; matched case-sensitively in both directions) |
|
|
9
9
|
| FlexPLM class / type path | `LCSCountry` / `Country` |
|
|
10
10
|
| Identifier property | `vibeIQIdentifier` (same key in both systems; no rekey runs without a mapping) |
|
|
11
11
|
| Informational property | `name` |
|
|
12
12
|
|
|
13
|
-
A mapping section for `LCSCountry` overrides
|
|
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
14
|
|
|
15
|
-
```ts
|
|
16
|
-
LCSCountry: { isInboundCreatable: () => true, isOutboundCreatable: () => false }, // FlexPLM creates
|
|
17
|
-
LCSCountry: { isInboundCreatable: () => false, isOutboundCreatable: () => true }, // VibeIQ creates
|
|
18
|
-
```
|
|
19
|
-
Read the declared origin with `TypeConversionUtils.getOriginSystem(...)`.
|
|
@@ -340,13 +340,6 @@ export interface MappingSection {
|
|
|
340
340
|
* update existing entities.
|
|
341
341
|
* - Conditional — inspect the row and allow / disallow per record.
|
|
342
342
|
*
|
|
343
|
-
* On the **`LCSCountry`** section this pair declares which system creates
|
|
344
|
-
* Countries: `isInboundCreatable: () => true` together with
|
|
345
|
-
* `isOutboundCreatable: () => false` means FlexPLM creates them (the
|
|
346
|
-
* reverse pair means VibeIQ creates them). Both functions must be present
|
|
347
|
-
* and opposite; otherwise the origin is undeclared and the connector treats
|
|
348
|
-
* Country as read-only. See `TypeConversionUtils.getOriginSystem`.
|
|
349
|
-
*
|
|
350
343
|
* On the **season-link** sections (`LCSProductSeasonLink` /
|
|
351
344
|
* `LCSSKUSeasonLink`), `() => true` enables the add-to-project
|
|
352
345
|
* flow: FlexPLM-driven inbound events can add an item to a Vibe
|
|
@@ -369,6 +362,10 @@ export interface MappingSection {
|
|
|
369
362
|
* }
|
|
370
363
|
*
|
|
371
364
|
* @example
|
|
365
|
+
* // FlexPLM creates Countries (VibeIQ creates them by default).
|
|
366
|
+
* LCSCountry: { isInboundCreatable: () => true }
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
372
369
|
* // add-to-project flow — Flex drives project-item creation.
|
|
373
370
|
* // Paired with isOutboundCreatable: () => false to make
|
|
374
371
|
* // outbound update-only.
|
|
@@ -714,7 +711,7 @@ interface MappingFileBase {
|
|
|
714
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. |
|
|
715
712
|
* | `color` | `LCSColor` | Syncs on create / update. Sends Primary Viewable as the thumbnail. Creates `LCSColor` if missing. |
|
|
716
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. |
|
|
717
|
-
* | `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
|
|
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. |
|
|
718
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. |
|
|
719
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. |
|
|
720
717
|
* | `custom-entity` | `LCSLast` / `LCSLifecycleManaged` | **Not yet supported.** |
|
|
@@ -732,7 +729,7 @@ interface MappingFileBase {
|
|
|
732
729
|
* | `LCSLifecycleManaged` | `custom-entity` | Configurable to create new custom-entities; updates existing. |
|
|
733
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. |
|
|
734
731
|
* | `LCSRevisableEntity` | `custom-entity` | Configurable to create new custom-entities; updates existing. |
|
|
735
|
-
* | `LCSCountry` | `custom-entity` (`custom-entity:country`) | Built-in defaults for type path and identifier
|
|
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 }`). |
|
|
736
733
|
* | (any) | `item` (direct, no link) | **Not yet supported.** |
|
|
737
734
|
*
|
|
738
735
|
* @example
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { MapFileUtil } from '@contrail/transform-data';
|
|
2
|
-
/** The system that originates (creates) an entity type: 'flexplm' or 'vibeiq'. */
|
|
3
|
-
export type OriginSystem = 'flexplm' | 'vibeiq';
|
|
4
2
|
/** This class is helper functions to get the data for converting
|
|
5
3
|
* VibeIQ entities to / from FlexPLM Objects
|
|
6
4
|
*/
|
|
@@ -149,23 +147,6 @@ export declare class TypeConversionUtils {
|
|
|
149
147
|
static getMapKeyFromObject(fileId: any, mapFileUtil: MapFileUtil, object: any, direction: string): Promise<string | undefined>;
|
|
150
148
|
static isInboundCreatableFromObject(fileId: string, mapFileUtil: MapFileUtil, object: any, context?: any): Promise<boolean>;
|
|
151
149
|
static isOutboundCreatableFromEntity(fileId: string, mapFileUtil: MapFileUtil, entity: any, context?: any): Promise<boolean>;
|
|
152
|
-
/** Returns which system originates (creates) an entity type, declared in its mapping section
|
|
153
|
-
* with the isInboundCreatable / isOutboundCreatable pair:
|
|
154
|
-
* - isInboundCreatable true and isOutboundCreatable false -> 'flexplm' (FlexPLM creates)
|
|
155
|
-
* - isInboundCreatable false and isOutboundCreatable true -> 'vibeiq' (VibeIQ creates)
|
|
156
|
-
* Anything else is undeclared and returns undefined: no mapping file, no section, either function
|
|
157
|
-
* missing, both true, both false, or an error while resolving. Unlike isInboundCreatableFromObject /
|
|
158
|
-
* isOutboundCreatableFromEntity, there are NO defaults: the origin must be stated explicitly,
|
|
159
|
-
* so callers can fail safe (treat the entity as read-only) when it is not.
|
|
160
|
-
*
|
|
161
|
-
* @param fileId id for mapFile
|
|
162
|
-
* @param mapFileUtil class to get mapfile
|
|
163
|
-
* @param entityOrObject VibeIQ entity (vibe2flex) or FlexPLM object (flex2vibe)
|
|
164
|
-
* @param direction VIBE2FLEX_DIRECTION or FLEX2VIBE_DIRECTION; selects how the map section key is resolved
|
|
165
|
-
* @param context optional context object passed to the mapping functions
|
|
166
|
-
* @returns Promise<'flexplm' | 'vibeiq' | undefined>
|
|
167
|
-
*/
|
|
168
|
-
static getOriginSystem(fileId: string, mapFileUtil: MapFileUtil, entityOrObject: any, direction: string, context?: any): Promise<OriginSystem | undefined>;
|
|
169
150
|
/** Takes in a FlexPLM object and determines whether inbound
|
|
170
151
|
* images should be synced. In most cases, the creation owning system
|
|
171
152
|
* will also control image syncing. Defaults to false if no mapping exists.
|
|
@@ -143,11 +143,6 @@ class TypeConversionUtils {
|
|
|
143
143
|
const mappingData = await mapFileUtil.getMappingSection(transformMapFile, 'typeConversion', direction);
|
|
144
144
|
if (mappingData && mappingData[type] && mappingData[type]['getMapKey']) {
|
|
145
145
|
const mapKey = await mappingData[type]['getMapKey'](entity);
|
|
146
|
-
// An org's custom-entity routing usually returns '' for the subtypes it does not list.
|
|
147
|
-
// Country has a built-in section key, so its section is found without a typeConversion entry.
|
|
148
|
-
if (!mapKey && type_defaults_1.TypeDefaults.isCountryEntity(entity)) {
|
|
149
|
-
return type_defaults_1.TypeDefaults.COUNTRY_OBJECT_CLASS;
|
|
150
|
-
}
|
|
151
146
|
return mapKey;
|
|
152
147
|
}
|
|
153
148
|
}
|
|
@@ -365,51 +360,6 @@ class TypeConversionUtils {
|
|
|
365
360
|
}
|
|
366
361
|
return isOutboundCreatable;
|
|
367
362
|
}
|
|
368
|
-
/** Returns which system originates (creates) an entity type, declared in its mapping section
|
|
369
|
-
* with the isInboundCreatable / isOutboundCreatable pair:
|
|
370
|
-
* - isInboundCreatable true and isOutboundCreatable false -> 'flexplm' (FlexPLM creates)
|
|
371
|
-
* - isInboundCreatable false and isOutboundCreatable true -> 'vibeiq' (VibeIQ creates)
|
|
372
|
-
* Anything else is undeclared and returns undefined: no mapping file, no section, either function
|
|
373
|
-
* missing, both true, both false, or an error while resolving. Unlike isInboundCreatableFromObject /
|
|
374
|
-
* isOutboundCreatableFromEntity, there are NO defaults: the origin must be stated explicitly,
|
|
375
|
-
* so callers can fail safe (treat the entity as read-only) when it is not.
|
|
376
|
-
*
|
|
377
|
-
* @param fileId id for mapFile
|
|
378
|
-
* @param mapFileUtil class to get mapfile
|
|
379
|
-
* @param entityOrObject VibeIQ entity (vibe2flex) or FlexPLM object (flex2vibe)
|
|
380
|
-
* @param direction VIBE2FLEX_DIRECTION or FLEX2VIBE_DIRECTION; selects how the map section key is resolved
|
|
381
|
-
* @param context optional context object passed to the mapping functions
|
|
382
|
-
* @returns Promise<'flexplm' | 'vibeiq' | undefined>
|
|
383
|
-
*/
|
|
384
|
-
static async getOriginSystem(fileId, mapFileUtil, entityOrObject, direction, context) {
|
|
385
|
-
if (!fileId || !entityOrObject) {
|
|
386
|
-
return undefined;
|
|
387
|
-
}
|
|
388
|
-
try {
|
|
389
|
-
const mapKey = (TypeConversionUtils.VIBE2FLEX_DIRECTION === direction)
|
|
390
|
-
? await this.getMapKey(fileId, mapFileUtil, entityOrObject, direction)
|
|
391
|
-
: await this.getMapKeyFromObject(fileId, mapFileUtil, entityOrObject, direction);
|
|
392
|
-
if (!mapKey) {
|
|
393
|
-
return undefined;
|
|
394
|
-
}
|
|
395
|
-
const mapData = await map_utils_1.MapUtil.getFullMapSection(fileId, mapFileUtil, mapKey);
|
|
396
|
-
if (!mapData || typeof mapData['isInboundCreatable'] !== 'function' || typeof mapData['isOutboundCreatable'] !== 'function') {
|
|
397
|
-
return undefined;
|
|
398
|
-
}
|
|
399
|
-
const inbound = !!(await mapData['isInboundCreatable'](entityOrObject, context));
|
|
400
|
-
const outbound = !!(await mapData['isOutboundCreatable'](entityOrObject, context));
|
|
401
|
-
if (inbound && !outbound) {
|
|
402
|
-
return 'flexplm';
|
|
403
|
-
}
|
|
404
|
-
if (!inbound && outbound) {
|
|
405
|
-
return 'vibeiq';
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
catch (e) {
|
|
409
|
-
console.error('TypeConversionUtils.getOriginSystem: unable to read the origin from the mapping file: ' + e?.message);
|
|
410
|
-
}
|
|
411
|
-
return undefined;
|
|
412
|
-
}
|
|
413
363
|
/** Takes in a FlexPLM object and determines whether inbound
|
|
414
364
|
* images should be synced. In most cases, the creation owning system
|
|
415
365
|
* will also control image syncing. Defaults to false if no mapping exists.
|
|
@@ -963,87 +963,22 @@ describe('conversion-utils', () => {
|
|
|
963
963
|
const entity = { entityType: 'custom-entity', typePath: 'custom-entity:country' };
|
|
964
964
|
expect(await type_conversion_utils_1.TypeConversionUtils.getIdentifierProperties(TRANSFORM_MAP_FILE, mapFileUtil, entity)).toEqual(['isoCode']);
|
|
965
965
|
});
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
const countryEntity = { entityType: 'custom-entity', typePath: 'custom-entity:country' };
|
|
970
|
-
const countryObject = { flexPLMObjectClass: 'LCSCountry', flexPLMTypePath: 'Country' };
|
|
971
|
-
let spy;
|
|
972
|
-
// Org-style routing: custom-entity subtypes that are not listed return ''
|
|
973
|
-
const typeConversion = {
|
|
974
|
-
vibe2flex: { 'custom-entity': { getMapKey: () => '' } },
|
|
975
|
-
flex2vibe: {},
|
|
976
|
-
};
|
|
977
|
-
function useMap(countrySection) {
|
|
978
|
-
const map = { typeConversion };
|
|
979
|
-
if (countrySection) {
|
|
980
|
-
map.LCSCountry = countrySection;
|
|
981
|
-
}
|
|
982
|
-
spy.mockImplementation(async () => map);
|
|
983
|
-
}
|
|
984
|
-
beforeEach(() => {
|
|
985
|
-
spy = jest.spyOn(mapFileUtil, 'getMapFile');
|
|
986
|
-
});
|
|
987
|
-
afterEach(() => {
|
|
988
|
-
spy.mockRestore();
|
|
989
|
-
});
|
|
990
|
-
it('should return flexplm when inbound is creatable and outbound is not, in both directions', async () => {
|
|
991
|
-
useMap({ isInboundCreatable: () => true, isOutboundCreatable: () => false });
|
|
992
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, countryEntity, type_conversion_utils_1.TypeConversionUtils.VIBE2FLEX_DIRECTION)).toBe('flexplm');
|
|
993
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, countryObject, type_conversion_utils_1.TypeConversionUtils.FLEX2VIBE_DIRECTION)).toBe('flexplm');
|
|
994
|
-
});
|
|
995
|
-
it('should return vibeiq when outbound is creatable and inbound is not, in both directions', async () => {
|
|
996
|
-
useMap({ isInboundCreatable: () => false, isOutboundCreatable: () => true });
|
|
997
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, countryEntity, type_conversion_utils_1.TypeConversionUtils.VIBE2FLEX_DIRECTION)).toBe('vibeiq');
|
|
998
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, countryObject, type_conversion_utils_1.TypeConversionUtils.FLEX2VIBE_DIRECTION)).toBe('vibeiq');
|
|
999
|
-
});
|
|
1000
|
-
it('should support async mapping functions', async () => {
|
|
1001
|
-
useMap({ isInboundCreatable: async () => true, isOutboundCreatable: async () => false });
|
|
1002
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, countryObject, type_conversion_utils_1.TypeConversionUtils.FLEX2VIBE_DIRECTION)).toBe('flexplm');
|
|
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('');
|
|
1003
969
|
});
|
|
1004
|
-
it('should
|
|
1005
|
-
|
|
1006
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.
|
|
1007
|
-
});
|
|
1008
|
-
it('should return undefined when both gates are false', async () => {
|
|
1009
|
-
useMap({ isInboundCreatable: () => false, isOutboundCreatable: () => false });
|
|
1010
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, countryEntity, type_conversion_utils_1.TypeConversionUtils.VIBE2FLEX_DIRECTION)).toBeUndefined();
|
|
1011
|
-
});
|
|
1012
|
-
it('should return undefined when either gate function is missing', async () => {
|
|
1013
|
-
useMap({ isInboundCreatable: () => true });
|
|
1014
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, countryObject, type_conversion_utils_1.TypeConversionUtils.FLEX2VIBE_DIRECTION)).toBeUndefined();
|
|
1015
|
-
useMap({ isOutboundCreatable: () => true });
|
|
1016
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, countryEntity, type_conversion_utils_1.TypeConversionUtils.VIBE2FLEX_DIRECTION)).toBeUndefined();
|
|
1017
|
-
});
|
|
1018
|
-
it('should return undefined when there is no LCSCountry section', async () => {
|
|
1019
|
-
useMap();
|
|
1020
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, countryEntity, type_conversion_utils_1.TypeConversionUtils.VIBE2FLEX_DIRECTION)).toBeUndefined();
|
|
1021
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, countryObject, type_conversion_utils_1.TypeConversionUtils.FLEX2VIBE_DIRECTION)).toBeUndefined();
|
|
1022
|
-
});
|
|
1023
|
-
it('should return undefined when there is no mapping file id or no entity', async () => {
|
|
1024
|
-
useMap({ isInboundCreatable: () => true, isOutboundCreatable: () => false });
|
|
1025
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem('', mapFileUtil, countryObject, type_conversion_utils_1.TypeConversionUtils.FLEX2VIBE_DIRECTION)).toBeUndefined();
|
|
1026
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, undefined, type_conversion_utils_1.TypeConversionUtils.FLEX2VIBE_DIRECTION)).toBeUndefined();
|
|
1027
|
-
});
|
|
1028
|
-
it('should return undefined and log when a mapping function throws', async () => {
|
|
1029
|
-
useMap({ isInboundCreatable: () => { throw new Error('boom'); }, isOutboundCreatable: () => false });
|
|
1030
|
-
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
1031
|
-
try {
|
|
1032
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, countryObject, type_conversion_utils_1.TypeConversionUtils.FLEX2VIBE_DIRECTION)).toBeUndefined();
|
|
1033
|
-
expect(errorSpy).toHaveBeenCalled();
|
|
1034
|
-
}
|
|
1035
|
-
finally {
|
|
1036
|
-
errorSpy.mockRestore();
|
|
1037
|
-
}
|
|
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);
|
|
1038
973
|
});
|
|
1039
|
-
it('should
|
|
1040
|
-
|
|
1041
|
-
const
|
|
1042
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.
|
|
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);
|
|
1043
978
|
});
|
|
1044
|
-
it('should
|
|
1045
|
-
|
|
1046
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.
|
|
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);
|
|
1047
982
|
});
|
|
1048
983
|
});
|
|
1049
984
|
});
|
|
@@ -20,7 +20,7 @@ export declare class TypeDefaults {
|
|
|
20
20
|
static applyConfig(config: any): void;
|
|
21
21
|
static isPropertyTrue(value: any): boolean;
|
|
22
22
|
/** True when the VibeIQ entity is a Country: a custom-entity whose typePath is
|
|
23
|
-
* 'custom-entity:country'
|
|
23
|
+
* exactly 'custom-entity:country'.
|
|
24
24
|
*
|
|
25
25
|
* @param entity VibeIQ entity
|
|
26
26
|
* @returns boolean
|
|
@@ -19,14 +19,14 @@ class TypeDefaults {
|
|
|
19
19
|
return value === true || (typeof value === 'string' && value.toLowerCase() === 'true');
|
|
20
20
|
}
|
|
21
21
|
/** True when the VibeIQ entity is a Country: a custom-entity whose typePath is
|
|
22
|
-
* 'custom-entity:country'
|
|
22
|
+
* exactly 'custom-entity:country'.
|
|
23
23
|
*
|
|
24
24
|
* @param entity VibeIQ entity
|
|
25
25
|
* @returns boolean
|
|
26
26
|
*/
|
|
27
27
|
static isCountryEntity(entity) {
|
|
28
28
|
const typePath = entity?.['typePath'];
|
|
29
|
-
return typeof typePath === 'string' && typePath
|
|
29
|
+
return typeof typePath === 'string' && typePath === TypeDefaults.COUNTRY_VIBE_TYPE_PATH;
|
|
30
30
|
}
|
|
31
31
|
/**Takes in full entity and returs the default FlexPLM
|
|
32
32
|
* object class.
|
|
@@ -50,12 +50,12 @@ describe('Type Defaults', () => {
|
|
|
50
50
|
const objectClass = type_defaults_1.TypeDefaults.getDefaultObjectClass(newData);
|
|
51
51
|
expect(objectClass).toEqual('LCSCountry');
|
|
52
52
|
});
|
|
53
|
-
it('should return
|
|
53
|
+
it('should return LCSRevisableEntity when the Country typePath uses different casing', () => {
|
|
54
54
|
const newData = {
|
|
55
55
|
entityType: 'custom-entity',
|
|
56
56
|
typePath: 'custom-entity:Country'
|
|
57
57
|
};
|
|
58
|
-
expect(type_defaults_1.TypeDefaults.getDefaultObjectClass(newData)).toEqual('
|
|
58
|
+
expect(type_defaults_1.TypeDefaults.getDefaultObjectClass(newData)).toEqual('LCSRevisableEntity');
|
|
59
59
|
});
|
|
60
60
|
it('should return LCSRevisableEntity for a custom entity that is not a Country', () => {
|
|
61
61
|
const newData = {
|
|
@@ -347,6 +347,9 @@ describe('Type Defaults', () => {
|
|
|
347
347
|
it('should return true for the custom-entity:country type path', () => {
|
|
348
348
|
expect(type_defaults_1.TypeDefaults.isCountryEntity({ typePath: 'custom-entity:country' })).toBe(true);
|
|
349
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
|
+
});
|
|
350
353
|
it('should return false for another subtype, a missing typePath, or an undefined entity', () => {
|
|
351
354
|
expect(type_defaults_1.TypeDefaults.isCountryEntity({ typePath: 'custom-entity:other' })).toBe(false);
|
|
352
355
|
expect(type_defaults_1.TypeDefaults.isCountryEntity({})).toBe(false);
|
package/lib/util/type-utils.js
CHANGED
|
@@ -15,6 +15,11 @@ class TypeUtils {
|
|
|
15
15
|
id,
|
|
16
16
|
relations: ['typeProperties', 'typeInterfaces']
|
|
17
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
|
+
}
|
|
18
23
|
return type;
|
|
19
24
|
}
|
|
20
25
|
async getByRootAndPath(options) {
|
|
@@ -53,9 +53,9 @@ describe('TypeUtils.getEventObjectClass() tests', () => {
|
|
|
53
53
|
const objectClass = tu.getEventObjectClass(entityType, newData);
|
|
54
54
|
expect(objectClass).toEqual('LCSCountry');
|
|
55
55
|
});
|
|
56
|
-
it('should return
|
|
56
|
+
it('should return LCSRevisableEntity when the Country typePath uses different casing', () => {
|
|
57
57
|
const objectClass = tu.getEventObjectClass('custom-entity', { typePath: 'custom-entity:Country' });
|
|
58
|
-
expect(objectClass).toEqual('
|
|
58
|
+
expect(objectClass).toEqual('LCSRevisableEntity');
|
|
59
59
|
});
|
|
60
60
|
it('should return LCSRevisableEntity for a custom entity that is not a Country', () => {
|
|
61
61
|
const objectClass = tu.getEventObjectClass('custom-entity', { typePath: 'custom-entity:exchangeRate' });
|
|
@@ -215,3 +215,16 @@ describe('filterProperties-color', () => {
|
|
|
215
215
|
expect(filteredProps.length).toEqual(len);
|
|
216
216
|
});
|
|
217
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
|
+
});
|