@contrail/flexplm 1.4.0-alpha.6954f61 → 1.5.0-alpha.6fc44c4

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
@@ -6,6 +6,8 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
  Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## [Unreleased]
9
+ ### Added
10
+ - Added support for Inbound `LCSMaterial` to sync to the entity class `item` with type path `item:material` and `itemNumber` as identifier. This is controlled by an `LCSMaterial.processAsItem` (default `false`) config default.
9
11
 
10
12
  ## [1.4.0] - 2026-05-06
11
13
  ### Added
@@ -201,7 +201,7 @@ class IdentifierConversion {
201
201
  }
202
202
  static async getItemCriteriaFromObject(transformMapFile, mapFileUtil, dc, object) {
203
203
  const criteria = await IdentifierConversion.getEntityCriteriaFromObject(transformMapFile, mapFileUtil, dc, object);
204
- const roles = (object.flexPLMObjectClass === 'LCSProduct') ? 'family' : 'color';
204
+ const roles = (['LCSProduct', 'LCSMaterial'].includes(object.flexPLMObjectClass)) ? 'family' : 'color';
205
205
  criteria['roles'] = roles;
206
206
  return criteria;
207
207
  }
@@ -296,6 +296,34 @@ describe('getItemCriteriaFromObject', () => {
296
296
  }
297
297
  }
298
298
  });
299
+ it('should return the item family criteria from the object -LCSMaterial', async () => {
300
+ const object = {
301
+ "flexPLMObjectClass": "LCSMaterial",
302
+ "flexPLMTypePath": "Material\\form",
303
+ "itemNumber": "MAT-100"
304
+ };
305
+ const criteriaObject = {
306
+ flexPLMObjectClass: 'LCSMaterial',
307
+ itemNumber: 'MAT-100',
308
+ flexPLMTypePath: 'Material\\form',
309
+ };
310
+ const resultsObject = {
311
+ roles: 'family',
312
+ itemNumber: 'MAT-100'
313
+ };
314
+ let getEntityValuesSpyOn = undefined;
315
+ try {
316
+ getEntityValuesSpyOn = jest.spyOn(dc, 'getEntityValues');
317
+ const result = await identifier_conversion_1.IdentifierConversion.getItemCriteriaFromObject(transformMapFile1, mapFileUtil, dc, object);
318
+ expect(getEntityValuesSpyOn).toHaveBeenCalledWith('LCSMaterial', criteriaObject, []);
319
+ expect(result).toEqual(resultsObject);
320
+ }
321
+ finally {
322
+ if (getEntityValuesSpyOn) {
323
+ getEntityValuesSpyOn.mockRestore();
324
+ }
325
+ }
326
+ });
299
327
  it('should return the item option criteria from the object -uniqueIdentifierA, uniqueIdentifierB', async () => {
300
328
  const object = {
301
329
  "flexBoolean": false,
@@ -3,6 +3,24 @@ export declare class ConfigDefaults {
3
3
  static NEED_CONFIG_VALUES: string;
4
4
  static STATIC_CONFIG_CACHE: {};
5
5
  static setConfigDefaults(config: any): Promise<FCConfig>;
6
+ static getDefaultConfig(): {
7
+ urlContext: string;
8
+ sendMode: {
9
+ ASYNC_PUBLISH_SEASON: string;
10
+ };
11
+ itemPreDevelopmentLifecycleStages: string[];
12
+ identifierAtts: {
13
+ LCSProduct: string[];
14
+ LCSSeason: string[];
15
+ LCSSKU: string[];
16
+ };
17
+ LCSMaterial: {
18
+ processAsItem: boolean;
19
+ };
20
+ csrfEndpoint: string;
21
+ vibeEventEndpoint: string;
22
+ payloadDefaultAsArray: boolean;
23
+ };
6
24
  static getConfigFile(fileId: string): Promise<any>;
7
25
  static isPropertyTrue(value: any): boolean;
8
26
  static clearConfigCache(): void;
@@ -14,7 +14,23 @@ class ConfigDefaults {
14
14
  else if (Object.keys(config).includes('itemPreDevelopmentLifecycleStages')) {
15
15
  delete config['itemPreDevelopmentLifecycleStages'];
16
16
  }
17
- const defaultConfig = {
17
+ const configArr = [ConfigDefaults.getDefaultConfig()];
18
+ if (config.configFile) {
19
+ const fileConfig = await ConfigDefaults.getConfigFile(config.configFile);
20
+ configArr.push(fileConfig);
21
+ }
22
+ configArr.push(config);
23
+ const outputConfig = util_1.ObjectUtil.mergeDeep({}, ...configArr);
24
+ const uName = outputConfig.userName;
25
+ const pass = outputConfig.password;
26
+ outputConfig.userName = () => uName;
27
+ outputConfig.password = () => pass;
28
+ outputConfig['OOBvibeEventEndpoint'] = '/rfa/vibeiq/vibeEvents';
29
+ console.log('outputConfig: ' + JSON.stringify(outputConfig));
30
+ return outputConfig;
31
+ }
32
+ static getDefaultConfig() {
33
+ return {
18
34
  urlContext: '/Windchill',
19
35
  sendMode: {
20
36
  ASYNC_PUBLISH_SEASON: 'vibeiqfile'
@@ -25,24 +41,13 @@ class ConfigDefaults {
25
41
  LCSSeason: ['flexPLMSeasonName'],
26
42
  LCSSKU: ['itemNumber']
27
43
  },
44
+ LCSMaterial: {
45
+ processAsItem: false
46
+ },
28
47
  csrfEndpoint: '/servlet/rest/security/csrf',
29
48
  vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
30
49
  payloadDefaultAsArray: true
31
50
  };
32
- const configArr = [defaultConfig];
33
- if (config.configFile) {
34
- const fileConfig = await ConfigDefaults.getConfigFile(config.configFile);
35
- configArr.push(fileConfig);
36
- }
37
- configArr.push(config);
38
- const outputConfig = util_1.ObjectUtil.mergeDeep({}, ...configArr);
39
- const uName = outputConfig.userName;
40
- const pass = outputConfig.password;
41
- outputConfig.userName = () => uName;
42
- outputConfig.password = () => pass;
43
- outputConfig['OOBvibeEventEndpoint'] = '/rfa/vibeiq/vibeEvents';
44
- console.log('outputConfig: ' + JSON.stringify(outputConfig));
45
- return outputConfig;
46
51
  }
47
52
  static async getConfigFile(fileId) {
48
53
  try {
@@ -296,6 +296,39 @@ describe('all tests', () => {
296
296
  expect(config_defaults_1.ConfigDefaults.isPropertyTrue(1)).toBe(false);
297
297
  });
298
298
  });
299
+ describe('getDefaultConfig', () => {
300
+ it('returns LCSMaterial.processAsItem=false by default', () => {
301
+ const dc = config_defaults_1.ConfigDefaults.getDefaultConfig();
302
+ expect(dc.LCSMaterial).toBeDefined();
303
+ expect(dc.LCSMaterial.processAsItem).toBe(false);
304
+ });
305
+ it('returns a fresh object each call (no shared reference)', () => {
306
+ const a = config_defaults_1.ConfigDefaults.getDefaultConfig();
307
+ const b = config_defaults_1.ConfigDefaults.getDefaultConfig();
308
+ expect(a).not.toBe(b);
309
+ expect(a.LCSMaterial).not.toBe(b.LCSMaterial);
310
+ a.LCSMaterial.processAsItem = true;
311
+ expect(b.LCSMaterial.processAsItem).toBe(false);
312
+ });
313
+ });
314
+ describe('setConfigDefaults - LCSMaterial', () => {
315
+ const config = {
316
+ apiHost: 'http://test.com',
317
+ userName: 'vibeiq',
318
+ password: 'vibeiq'
319
+ };
320
+ it('LCSMaterial.processAsItem-get default', async () => {
321
+ const startConfig = Object.assign({}, config);
322
+ const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
323
+ expect(fcConfig.LCSMaterial.processAsItem).toBe(false);
324
+ });
325
+ it('LCSMaterial.processAsItem-override', async () => {
326
+ const startConfig = Object.assign({}, config);
327
+ startConfig.LCSMaterial = { processAsItem: true };
328
+ const fcConfig = await config_defaults_1.ConfigDefaults.setConfigDefaults(startConfig);
329
+ expect(fcConfig.LCSMaterial.processAsItem).toBe(true);
330
+ });
331
+ });
299
332
  describe('getConfigFile', () => {
300
333
  beforeEach(() => {
301
334
  config_defaults_1.ConfigDefaults.clearConfigCache();
@@ -235,7 +235,7 @@ class DataConverter {
235
235
  const type = await this.typeUtils.getByRootAndPath(tco);
236
236
  const typePath = type['typePath'];
237
237
  if (typePath && (typePath.startsWith('item') || typePath.startsWith('project-item'))) {
238
- if (['LCSProduct', 'LCSProductSeasonLink'].includes(objectClass)) {
238
+ if (['LCSProduct', 'LCSProductSeasonLink', 'LCSMaterial'].includes(objectClass)) {
239
239
  entityValues['roles'] = ['family'];
240
240
  }
241
241
  else {
@@ -525,6 +525,47 @@ describe('getObjectReferenceValue - use mapping', () => {
525
525
  }
526
526
  });
527
527
  });
528
+ describe('getEntityValues', () => {
529
+ const config = {
530
+ apiHost: 'host',
531
+ userName: () => 'user',
532
+ password: () => 'pass',
533
+ urlContext: 'xxx',
534
+ vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
535
+ csrfEndpoint: '/servlet/rest/security/csrf',
536
+ itemPreDevelopmentLifecycleStages: ['concept']
537
+ };
538
+ const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
539
+ const dc = new data_converter_1.DataConverter(config, mapFileUtil);
540
+ const runWithStubs = async (typePath, objectClass, data) => {
541
+ const tcoSpy = jest.spyOn(dc['typeUtils'], 'getEntityTypeClientOptionsUsingMapping')
542
+ .mockImplementation(async () => ({ root: 'item' }));
543
+ const typeSpy = jest.spyOn(dc['typeUtils'], 'getByRootAndPath')
544
+ .mockImplementation(async () => ({ typePath, typeProperties: [] }));
545
+ const filterSpy = jest.spyOn(dc['typeUtils'], 'filterTypeProperties')
546
+ .mockImplementation(() => []);
547
+ try {
548
+ return await dc.getEntityValues(objectClass, data);
549
+ }
550
+ finally {
551
+ tcoSpy.mockRestore();
552
+ typeSpy.mockRestore();
553
+ filterSpy.mockRestore();
554
+ }
555
+ };
556
+ it('LCSProduct -> roles family', async () => {
557
+ const result = await runWithStubs('item', 'LCSProduct', { itemNumber: 'X1' });
558
+ expect(result['roles']).toEqual(['family']);
559
+ });
560
+ it('LCSSKU -> roles color, option', async () => {
561
+ const result = await runWithStubs('item', 'LCSSKU', { itemNumber: 'X1' });
562
+ expect(result['roles']).toEqual(['color', 'option']);
563
+ });
564
+ it('LCSMaterial -> roles family', async () => {
565
+ const result = await runWithStubs('item:material', 'LCSMaterial', { itemNumber: 'MAT-100' });
566
+ expect(result['roles']).toEqual(['family']);
567
+ });
568
+ });
528
569
  describe('setEnumerationKeys', () => {
529
570
  const config = {
530
571
  apiHost: 'host',
@@ -130,7 +130,7 @@ class TypeDefaults {
130
130
  static getDefaultEntityClass(object) {
131
131
  let entityClass = '';
132
132
  let objectClass = TypeDefaults.getObjectClass(object);
133
- if (['LCSProduct', 'LCSSKU'].includes(objectClass)) {
133
+ if (['LCSProduct', 'LCSSKU', 'LCSMaterial'].includes(objectClass)) {
134
134
  entityClass = 'item';
135
135
  }
136
136
  else if (['LCSProductSeasonLink', 'LCSSKUSeasonLink'].includes(objectClass)) {
@@ -142,7 +142,7 @@ class TypeDefaults {
142
142
  else if (['LCSSeason', 'SeasonGroup'].includes(objectClass)) {
143
143
  entityClass = 'assortment';
144
144
  }
145
- else if (['LCSRevisableEntity', 'LCSLifecycleManaged', 'LCSLast', 'LCSMaterial'].includes(objectClass)) {
145
+ else if (['LCSRevisableEntity', 'LCSLifecycleManaged', 'LCSLast'].includes(objectClass)) {
146
146
  entityClass = 'custom-entity';
147
147
  }
148
148
  if (entityClass === '')
@@ -157,6 +157,9 @@ class TypeDefaults {
157
157
  case 'LCSSKU':
158
158
  typePath = 'item';
159
159
  break;
160
+ case 'LCSMaterial':
161
+ typePath = 'item:material';
162
+ break;
160
163
  case 'LCSProductSeasonLink':
161
164
  case 'LCSSKUSeasonLink':
162
165
  typePath = 'project-item';
@@ -179,6 +182,7 @@ class TypeDefaults {
179
182
  switch (objectClass) {
180
183
  case 'LCSProduct':
181
184
  case 'LCSSKU':
185
+ case 'LCSMaterial':
182
186
  identifierProps.push('itemNumber');
183
187
  break;
184
188
  case 'LCSSeason':
@@ -191,7 +195,6 @@ class TypeDefaults {
191
195
  case 'LCSRevisableEntity':
192
196
  case 'LCSLifecycleManaged':
193
197
  case 'LCSLast':
194
- case 'LCSMaterial':
195
198
  identifierProps.push('name');
196
199
  break;
197
200
  }
@@ -200,7 +203,7 @@ class TypeDefaults {
200
203
  static getDefaultInformationalPropertiesFromObject(object) {
201
204
  const objectClass = TypeDefaults.getObjectClass(object);
202
205
  let properties = [];
203
- if ('LCSProduct' === objectClass) {
206
+ if (['LCSProduct', 'LCSMaterial'].includes(objectClass)) {
204
207
  properties.push('name');
205
208
  }
206
209
  else if ('LCSSKU' === objectClass) {
@@ -369,6 +369,13 @@ describe('Type Defaults', () => {
369
369
  const entityClass = type_defaults_1.TypeDefaults.getDefaultEntityClass(object);
370
370
  expect(entityClass).toBe('custom-entity');
371
371
  });
372
+ it('item - LCSMaterial', () => {
373
+ const object = {
374
+ flexPLMObjectClass: 'LCSMaterial'
375
+ };
376
+ const entityClass = type_defaults_1.TypeDefaults.getDefaultEntityClass(object);
377
+ expect(entityClass).toBe('item');
378
+ });
372
379
  });
373
380
  describe('getDefaultEntityTypePath', () => {
374
381
  it('LCSProduct', () => {
@@ -420,6 +427,13 @@ describe('Type Defaults', () => {
420
427
  const typePath = type_defaults_1.TypeDefaults.getDefaultEntityTypePath(object);
421
428
  expect(typePath).toBe('assortment');
422
429
  });
430
+ it('LCSMaterial', () => {
431
+ const object = {
432
+ flexPLMObjectClass: 'LCSMaterial'
433
+ };
434
+ const typePath = type_defaults_1.TypeDefaults.getDefaultEntityTypePath(object);
435
+ expect(typePath).toBe('item:material');
436
+ });
423
437
  });
424
438
  describe('getDefaultIdentifierPropertiesFromObject', () => {
425
439
  it('LCSProduct', () => {
@@ -478,6 +492,14 @@ describe('Type Defaults', () => {
478
492
  expect(defaultIdentifiers).toContain('name');
479
493
  expect(defaultIdentifiers).toHaveLength(1);
480
494
  });
495
+ it('LCSMaterial', () => {
496
+ const object = {
497
+ flexPLMObjectClass: 'LCSMaterial'
498
+ };
499
+ const defaultIdentifiers = type_defaults_1.TypeDefaults.getDefaultIdentifierPropertiesFromObject(object);
500
+ expect(defaultIdentifiers).toContain('itemNumber');
501
+ expect(defaultIdentifiers).toHaveLength(1);
502
+ });
481
503
  });
482
504
  describe('getDefaultInformationalPropertiesFromObject', () => {
483
505
  it('LCSProduct', () => {
@@ -512,5 +534,13 @@ describe('Type Defaults', () => {
512
534
  expect(defaultIdentifiers).toContain('name');
513
535
  expect(defaultIdentifiers).toHaveLength(1);
514
536
  });
537
+ it('LCSMaterial', () => {
538
+ const object = {
539
+ flexPLMObjectClass: 'LCSMaterial'
540
+ };
541
+ const defaultIdentifiers = type_defaults_1.TypeDefaults.getDefaultInformationalPropertiesFromObject(object);
542
+ expect(defaultIdentifiers).toContain('name');
543
+ expect(defaultIdentifiers).toHaveLength(1);
544
+ });
515
545
  });
516
546
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/flexplm",
3
- "version": "1.4.0-alpha.6954f61",
3
+ "version": "1.5.0-alpha.6fc44c4",
4
4
  "description": "Library used for integration with flexplm.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -309,6 +309,35 @@ describe('getItemCriteriaFromObject', () => {
309
309
  }
310
310
  });
311
311
 
312
+ it('should return the item family criteria from the object -LCSMaterial', async () => {
313
+ const object = {
314
+ "flexPLMObjectClass": "LCSMaterial",
315
+ "flexPLMTypePath": "Material\\form",
316
+ "itemNumber": "MAT-100"
317
+ };
318
+ const criteriaObject = {
319
+ flexPLMObjectClass: 'LCSMaterial',
320
+ itemNumber: 'MAT-100',
321
+ flexPLMTypePath: 'Material\\form',
322
+ };
323
+ const resultsObject = {
324
+ roles: 'family',
325
+ itemNumber: 'MAT-100'
326
+ };
327
+ let getEntityValuesSpyOn = undefined
328
+ try {
329
+
330
+ getEntityValuesSpyOn = jest.spyOn(dc, 'getEntityValues');
331
+ const result = await IdentifierConversion.getItemCriteriaFromObject(transformMapFile1, mapFileUtil, dc, object);
332
+ expect(getEntityValuesSpyOn).toHaveBeenCalledWith('LCSMaterial', criteriaObject, []);
333
+ expect(result).toEqual(resultsObject);
334
+ } finally {
335
+ if (getEntityValuesSpyOn) {
336
+ getEntityValuesSpyOn.mockRestore();
337
+ }
338
+ }
339
+ });
340
+
312
341
  it('should return the item option criteria from the object -uniqueIdentifierA, uniqueIdentifierB', async () => {
313
342
  const object = {
314
343
  "flexBoolean": false,
@@ -274,7 +274,7 @@ export class IdentifierConversion {
274
274
 
275
275
  static async getItemCriteriaFromObject(transformMapFile: string, mapFileUtil: MapFileUtil, dc: DataConverter, object: any): Promise<any> {
276
276
  const criteria = await IdentifierConversion.getEntityCriteriaFromObject(transformMapFile, mapFileUtil, dc, object);
277
- const roles = (object.flexPLMObjectClass === 'LCSProduct') ? 'family' : 'color';
277
+ const roles = (['LCSProduct', 'LCSMaterial'].includes(object.flexPLMObjectClass)) ? 'family' : 'color';
278
278
  criteria['roles'] = roles;
279
279
 
280
280
  return criteria;
@@ -356,6 +356,44 @@ describe('all tests', () => {
356
356
  });
357
357
  });
358
358
 
359
+ describe('getDefaultConfig', () => {
360
+ it('returns LCSMaterial.processAsItem=false by default', () => {
361
+ const dc: any = ConfigDefaults.getDefaultConfig();
362
+ expect(dc.LCSMaterial).toBeDefined();
363
+ expect(dc.LCSMaterial.processAsItem).toBe(false);
364
+ });
365
+
366
+ it('returns a fresh object each call (no shared reference)', () => {
367
+ const a: any = ConfigDefaults.getDefaultConfig();
368
+ const b: any = ConfigDefaults.getDefaultConfig();
369
+ expect(a).not.toBe(b);
370
+ expect(a.LCSMaterial).not.toBe(b.LCSMaterial);
371
+ a.LCSMaterial.processAsItem = true;
372
+ expect(b.LCSMaterial.processAsItem).toBe(false);
373
+ });
374
+ });
375
+
376
+ describe('setConfigDefaults - LCSMaterial', () => {
377
+ const config = {
378
+ apiHost: 'http://test.com',
379
+ userName: 'vibeiq',
380
+ password: 'vibeiq'
381
+ };
382
+
383
+ it('LCSMaterial.processAsItem-get default', async () => {
384
+ const startConfig = Object.assign({}, config);
385
+ const fcConfig: any = await ConfigDefaults.setConfigDefaults(startConfig);
386
+ expect(fcConfig.LCSMaterial.processAsItem).toBe(false);
387
+ });
388
+
389
+ it('LCSMaterial.processAsItem-override', async () => {
390
+ const startConfig: any = Object.assign({}, config);
391
+ startConfig.LCSMaterial = { processAsItem: true };
392
+ const fcConfig: any = await ConfigDefaults.setConfigDefaults(startConfig);
393
+ expect(fcConfig.LCSMaterial.processAsItem).toBe(true);
394
+ });
395
+ });
396
+
359
397
  describe('getConfigFile', () => {
360
398
  beforeEach(() => {
361
399
  ConfigDefaults.clearConfigCache();
@@ -18,22 +18,7 @@ export class ConfigDefaults {
18
18
  delete config['itemPreDevelopmentLifecycleStages'];
19
19
  }
20
20
 
21
- const defaultConfig = {
22
- urlContext: '/Windchill',
23
- sendMode: {
24
- ASYNC_PUBLISH_SEASON: 'vibeiqfile'
25
- },
26
- itemPreDevelopmentLifecycleStages: ['concept'],
27
- identifierAtts: {
28
- LCSProduct: ['itemNumber'],
29
- LCSSeason: ['flexPLMSeasonName'],
30
- LCSSKU: ['itemNumber']
31
- },
32
- csrfEndpoint: '/servlet/rest/security/csrf',
33
- vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
34
- payloadDefaultAsArray: true
35
- };
36
- const configArr = [defaultConfig];
21
+ const configArr = [ConfigDefaults.getDefaultConfig()];
37
22
 
38
23
  if(config.configFile){
39
24
  const fileConfig = await ConfigDefaults.getConfigFile(config.configFile);
@@ -53,6 +38,27 @@ export class ConfigDefaults {
53
38
  return outputConfig as FCConfig;
54
39
  }
55
40
 
41
+ static getDefaultConfig() {
42
+ return {
43
+ urlContext: '/Windchill',
44
+ sendMode: {
45
+ ASYNC_PUBLISH_SEASON: 'vibeiqfile'
46
+ },
47
+ itemPreDevelopmentLifecycleStages: ['concept'],
48
+ identifierAtts: {
49
+ LCSProduct: ['itemNumber'],
50
+ LCSSeason: ['flexPLMSeasonName'],
51
+ LCSSKU: ['itemNumber']
52
+ },
53
+ LCSMaterial: {
54
+ processAsItem: false
55
+ },
56
+ csrfEndpoint: '/servlet/rest/security/csrf',
57
+ vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
58
+ payloadDefaultAsArray: true
59
+ };
60
+ }
61
+
56
62
  static async getConfigFile(fileId: string) {
57
63
  try {
58
64
 
@@ -594,6 +594,51 @@ describe('getObjectReferenceValue - use mapping', () => {
594
594
  });
595
595
  });
596
596
 
597
+ describe('getEntityValues', () => {
598
+ const config: FCConfig = {
599
+ apiHost: 'host',
600
+ userName: () => 'user',
601
+ password: () => 'pass',
602
+ urlContext: 'xxx',
603
+ vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
604
+ csrfEndpoint: '/servlet/rest/security/csrf',
605
+ itemPreDevelopmentLifecycleStages: ['concept']
606
+ };
607
+ const mapFileUtil = new MapFileUtil(new Entities());
608
+ const dc = new DataConverter(config, mapFileUtil);
609
+
610
+ const runWithStubs = async (typePath: string, objectClass: string, data: any) => {
611
+ const tcoSpy = jest.spyOn(dc['typeUtils'], 'getEntityTypeClientOptionsUsingMapping')
612
+ .mockImplementation(async () => ({ root: 'item' }));
613
+ const typeSpy = jest.spyOn(dc['typeUtils'], 'getByRootAndPath')
614
+ .mockImplementation(async () => ({ typePath, typeProperties: [] }));
615
+ const filterSpy = jest.spyOn(dc['typeUtils'], 'filterTypeProperties')
616
+ .mockImplementation(() => []);
617
+ try {
618
+ return await dc.getEntityValues(objectClass, data);
619
+ } finally {
620
+ tcoSpy.mockRestore();
621
+ typeSpy.mockRestore();
622
+ filterSpy.mockRestore();
623
+ }
624
+ };
625
+
626
+ it('LCSProduct -> roles family', async () => {
627
+ const result = await runWithStubs('item', 'LCSProduct', { itemNumber: 'X1' });
628
+ expect(result['roles']).toEqual(['family']);
629
+ });
630
+
631
+ it('LCSSKU -> roles color, option', async () => {
632
+ const result = await runWithStubs('item', 'LCSSKU', { itemNumber: 'X1' });
633
+ expect(result['roles']).toEqual(['color', 'option']);
634
+ });
635
+
636
+ it('LCSMaterial -> roles family', async () => {
637
+ const result = await runWithStubs('item:material', 'LCSMaterial', { itemNumber: 'MAT-100' });
638
+ expect(result['roles']).toEqual(['family']);
639
+ });
640
+ });
641
+
597
642
  describe('setEnumerationKeys', () =>{
598
643
  const config: FCConfig = {
599
644
  apiHost: 'host',
@@ -287,7 +287,7 @@ export class DataConverter {
287
287
  const type = await this.typeUtils.getByRootAndPath(tco);
288
288
  const typePath = type['typePath'];
289
289
  if(typePath && (typePath.startsWith('item') || typePath.startsWith('project-item'))){
290
- if(['LCSProduct', 'LCSProductSeasonLink'].includes(objectClass)){
290
+ if(['LCSProduct', 'LCSProductSeasonLink', 'LCSMaterial'].includes(objectClass)){
291
291
  entityValues['roles'] = ['family'];
292
292
  }else{
293
293
  entityValues['roles'] = ['color', 'option'];
@@ -485,6 +485,16 @@ describe('Type Defaults', () =>{
485
485
  expect(entityClass).toBe('custom-entity');
486
486
  });
487
487
 
488
+ it('item - LCSMaterial', () =>{
489
+ const object = {
490
+ flexPLMObjectClass: 'LCSMaterial'
491
+ };
492
+
493
+ const entityClass = TypeDefaults.getDefaultEntityClass(object);
494
+
495
+ expect(entityClass).toBe('item');
496
+ });
497
+
488
498
  });//getDefaultEntityClass
489
499
 
490
500
  describe('getDefaultEntityTypePath', () =>{
@@ -549,6 +559,15 @@ describe('Type Defaults', () =>{
549
559
  const typePath = TypeDefaults.getDefaultEntityTypePath(object);
550
560
  expect(typePath).toBe('assortment');
551
561
  });
562
+
563
+ it('LCSMaterial', () =>{
564
+ const object = {
565
+ flexPLMObjectClass: 'LCSMaterial'
566
+ };
567
+
568
+ const typePath = TypeDefaults.getDefaultEntityTypePath(object);
569
+ expect(typePath).toBe('item:material');
570
+ });
552
571
  });//getDefaultEntityTypePath
553
572
 
554
573
  describe('getDefaultIdentifierPropertiesFromObject', () =>{
@@ -622,6 +641,16 @@ describe('Type Defaults', () =>{
622
641
  expect(defaultIdentifiers).toHaveLength(1);
623
642
  });
624
643
 
644
+ it('LCSMaterial', () =>{
645
+ const object = {
646
+ flexPLMObjectClass: 'LCSMaterial'
647
+ };
648
+ const defaultIdentifiers = TypeDefaults.getDefaultIdentifierPropertiesFromObject(object);
649
+
650
+ expect(defaultIdentifiers).toContain('itemNumber');
651
+ expect(defaultIdentifiers).toHaveLength(1);
652
+ });
653
+
625
654
  });//getDefaultIdentifierPropertiesFromObject
626
655
 
627
656
  describe('getDefaultInformationalPropertiesFromObject', () =>{
@@ -665,5 +694,15 @@ describe('Type Defaults', () =>{
665
694
  expect(defaultIdentifiers).toHaveLength(1);
666
695
  });
667
696
 
697
+ it('LCSMaterial', () =>{
698
+ const object = {
699
+ flexPLMObjectClass: 'LCSMaterial'
700
+ };
701
+
702
+ const defaultIdentifiers = TypeDefaults.getDefaultInformationalPropertiesFromObject(object);
703
+ expect(defaultIdentifiers).toContain('name');
704
+ expect(defaultIdentifiers).toHaveLength(1);
705
+ });
706
+
668
707
  });//getDefaultInformationalPropertiesFromObject
669
708
  });
@@ -168,7 +168,7 @@ export class TypeDefaults {
168
168
  static getDefaultEntityClass(object): string {
169
169
  let entityClass = '';
170
170
  let objectClass = TypeDefaults.getObjectClass(object);
171
- if(['LCSProduct', 'LCSSKU'].includes(objectClass)){
171
+ if(['LCSProduct', 'LCSSKU', 'LCSMaterial'].includes(objectClass)){
172
172
  entityClass = 'item';
173
173
  }else if(['LCSProductSeasonLink', 'LCSSKUSeasonLink'].includes(objectClass)){
174
174
  entityClass = 'project-item'
@@ -176,7 +176,7 @@ export class TypeDefaults {
176
176
  entityClass = 'color';
177
177
  } else if(['LCSSeason', 'SeasonGroup'].includes(objectClass)) {
178
178
  entityClass = 'assortment';
179
- } else if(['LCSRevisableEntity', 'LCSLifecycleManaged', 'LCSLast', 'LCSMaterial'].includes(objectClass)) {
179
+ } else if(['LCSRevisableEntity', 'LCSLifecycleManaged', 'LCSLast'].includes(objectClass)) {
180
180
  entityClass = 'custom-entity';
181
181
  }
182
182
 
@@ -201,6 +201,9 @@ export class TypeDefaults {
201
201
  case 'LCSSKU':
202
202
  typePath = 'item';
203
203
  break;
204
+ case 'LCSMaterial':
205
+ typePath = 'item:material';
206
+ break;
204
207
  case 'LCSProductSeasonLink':
205
208
  case 'LCSSKUSeasonLink':
206
209
  typePath = 'project-item';
@@ -229,12 +232,13 @@ export class TypeDefaults {
229
232
  */
230
233
 
231
234
  static getDefaultIdentifierPropertiesFromObject(object): string[] {
232
- const identifierProps = [];
235
+ const identifierProps: string[] = [];
233
236
  const objectClass = TypeDefaults.getObjectClass(object);
234
237
 
235
238
  switch (objectClass) {
236
239
  case 'LCSProduct':
237
240
  case 'LCSSKU':
241
+ case 'LCSMaterial':
238
242
  identifierProps.push('itemNumber');
239
243
  break;
240
244
  case 'LCSSeason':
@@ -247,7 +251,6 @@ export class TypeDefaults {
247
251
  case 'LCSRevisableEntity':
248
252
  case 'LCSLifecycleManaged':
249
253
  case 'LCSLast':
250
- case 'LCSMaterial':
251
254
  identifierProps.push('name');
252
255
  break;
253
256
  }
@@ -265,7 +268,7 @@ export class TypeDefaults {
265
268
  static getDefaultInformationalPropertiesFromObject(object): string[] {
266
269
  const objectClass = TypeDefaults.getObjectClass(object);
267
270
  let properties:string[] = [];
268
- if ('LCSProduct' === objectClass) {
271
+ if (['LCSProduct', 'LCSMaterial'].includes(objectClass)) {
269
272
  properties.push('name');
270
273
  } else if ('LCSSKU' === objectClass) {
271
274
  properties.push('optionName');