@contrail/flexplm 1.4.0 → 1.5.0-alpha.14a4f1b

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.
Files changed (112) hide show
  1. package/lib/cli/commands/compile.d.ts +4 -0
  2. package/lib/cli/commands/compile.js +73 -0
  3. package/lib/cli/commands/compile.spec.d.ts +1 -0
  4. package/lib/cli/commands/compile.spec.js +80 -0
  5. package/lib/cli/commands/create.d.ts +5 -0
  6. package/lib/cli/commands/create.js +77 -0
  7. package/lib/cli/commands/create.spec.d.ts +1 -0
  8. package/lib/cli/commands/create.spec.js +78 -0
  9. package/lib/cli/commands/upload.d.ts +17 -0
  10. package/lib/cli/commands/upload.js +228 -0
  11. package/lib/cli/commands/upload.spec.d.ts +1 -0
  12. package/lib/cli/commands/upload.spec.js +88 -0
  13. package/lib/cli/index.d.ts +5 -0
  14. package/lib/cli/index.js +70 -0
  15. package/lib/cli/index.spec.d.ts +1 -0
  16. package/lib/cli/index.spec.js +85 -0
  17. package/lib/cli/template/mapping-template.ts.template +62 -0
  18. package/lib/entity-processor/base-entity-processor.d.ts +65 -0
  19. package/lib/entity-processor/base-entity-processor.js +71 -0
  20. package/lib/entity-processor/base-entity-processor.spec.js +1 -0
  21. package/lib/index.d.ts +1 -0
  22. package/lib/index.js +1 -0
  23. package/lib/interfaces/mapping-file.d.ts +460 -0
  24. package/lib/interfaces/mapping-file.js +2 -0
  25. package/lib/publish/base-process-publish-assortment.d.ts +25 -0
  26. package/lib/publish/base-process-publish-assortment.js +60 -6
  27. package/lib/publish/base-process-publish-assortment.spec.js +22 -4
  28. package/lib/publish/mockData.js +5 -0
  29. package/lib/transform/identifier-conversion-spec-mockData.js +34 -6
  30. package/lib/transform/identifier-conversion.d.ts +36 -0
  31. package/lib/transform/identifier-conversion.js +37 -1
  32. package/lib/transform/identifier-conversion.spec.js +35 -0
  33. package/lib/util/config-defaults.d.ts +18 -0
  34. package/lib/util/config-defaults.js +25 -15
  35. package/lib/util/config-defaults.spec.js +56 -0
  36. package/lib/util/data-converter-spec-mockData.js +17 -3
  37. package/lib/util/data-converter.d.ts +102 -0
  38. package/lib/util/data-converter.js +195 -34
  39. package/lib/util/data-converter.spec.js +430 -0
  40. package/lib/util/error-response-object.d.ts +5 -0
  41. package/lib/util/error-response-object.js +7 -0
  42. package/lib/util/event-short-message-status.js +1 -0
  43. package/lib/util/federation.js +8 -0
  44. package/lib/util/flexplm-connect.d.ts +7 -0
  45. package/lib/util/flexplm-connect.js +14 -0
  46. package/lib/util/logger-config.js +1 -0
  47. package/lib/util/map-util-spec-mockData.js +17 -3
  48. package/lib/util/map-utils.d.ts +27 -0
  49. package/lib/util/map-utils.js +27 -0
  50. package/lib/util/thumbnail-util.d.ts +21 -0
  51. package/lib/util/thumbnail-util.js +28 -1
  52. package/lib/util/thumbnail-util.spec.js +6 -0
  53. package/lib/util/type-conversion-utils-spec-mockData.js +3 -3
  54. package/lib/util/type-conversion-utils.d.ts +151 -0
  55. package/lib/util/type-conversion-utils.js +154 -0
  56. package/lib/util/type-defaults.d.ts +69 -0
  57. package/lib/util/type-defaults.js +98 -4
  58. package/lib/util/type-defaults.spec.js +114 -4
  59. package/lib/util/type-utils.d.ts +21 -0
  60. package/lib/util/type-utils.js +23 -0
  61. package/lib/util/type-utils.spec.js +2 -0
  62. package/package.json +21 -6
  63. package/scripts/copy-template.js +10 -0
  64. package/.github/pull_request_template.md +0 -31
  65. package/.github/workflows/flexplm-lib.yml +0 -27
  66. package/.github/workflows/publish-to-npm.yml +0 -121
  67. package/CHANGELOG.md +0 -40
  68. package/publish.bat +0 -5
  69. package/publish.sh +0 -5
  70. package/src/entity-processor/base-entity-processor.spec.ts +0 -689
  71. package/src/entity-processor/base-entity-processor.ts +0 -583
  72. package/src/flexplm-request.ts +0 -28
  73. package/src/flexplm-utils.spec.ts +0 -27
  74. package/src/flexplm-utils.ts +0 -29
  75. package/src/index.ts +0 -22
  76. package/src/interfaces/interfaces.ts +0 -122
  77. package/src/interfaces/item-family-changes.ts +0 -67
  78. package/src/interfaces/publish-change-data.ts +0 -43
  79. package/src/publish/base-process-publish-assortment-callback.ts +0 -50
  80. package/src/publish/base-process-publish-assortment.spec.ts +0 -1992
  81. package/src/publish/base-process-publish-assortment.ts +0 -1134
  82. package/src/publish/mockData.ts +0 -4561
  83. package/src/transform/identifier-conversion-spec-mockData.ts +0 -496
  84. package/src/transform/identifier-conversion.spec.ts +0 -354
  85. package/src/transform/identifier-conversion.ts +0 -282
  86. package/src/util/config-defaults.spec.ts +0 -392
  87. package/src/util/config-defaults.ts +0 -97
  88. package/src/util/data-converter-spec-mockData.ts +0 -231
  89. package/src/util/data-converter.spec.ts +0 -1120
  90. package/src/util/data-converter.ts +0 -766
  91. package/src/util/error-response-object.spec.ts +0 -116
  92. package/src/util/error-response-object.ts +0 -50
  93. package/src/util/event-short-message-status.ts +0 -22
  94. package/src/util/federation.ts +0 -172
  95. package/src/util/flexplm-connect.spec.ts +0 -132
  96. package/src/util/flexplm-connect.ts +0 -208
  97. package/src/util/logger-config.ts +0 -20
  98. package/src/util/map-util-spec-mockData.ts +0 -231
  99. package/src/util/map-utils.spec.ts +0 -103
  100. package/src/util/map-utils.ts +0 -41
  101. package/src/util/mockData.ts +0 -101
  102. package/src/util/thumbnail-util.spec.ts +0 -508
  103. package/src/util/thumbnail-util.ts +0 -272
  104. package/src/util/type-conversion-utils-spec-mockData.ts +0 -272
  105. package/src/util/type-conversion-utils.spec.ts +0 -1031
  106. package/src/util/type-conversion-utils.ts +0 -490
  107. package/src/util/type-defaults.spec.ts +0 -669
  108. package/src/util/type-defaults.ts +0 -281
  109. package/src/util/type-utils.spec.ts +0 -227
  110. package/src/util/type-utils.ts +0 -144
  111. package/tsconfig.json +0 -24
  112. package/tslint.json +0 -57
@@ -0,0 +1,460 @@
1
+ import { FunctionTransformers, RekeyTransformers, TransformDefinition, TransformTask } from "@contrail/transform-data";
2
+ /**
3
+ * One direction of a {@link MappingSection} (either `vibe2flex` or `flex2vibe`).
4
+ *
5
+ * Holds the per-direction transform pipeline plus the named lookup tables
6
+ * (`rekey`, `removeKey`, `valueTransform`) that the tasks in
7
+ * {@link DirectionalSection.transformOrder} reference by key. A REKEY task
8
+ * configured with `rekeyTransformersKey: 'rekey'` resolves to
9
+ * {@link DirectionalSection.rekey} on the same directional section.
10
+ *
11
+ * The named members below cover the conventional shape, but the section
12
+ * is open: each task in a pipeline resolves its config by reading the
13
+ * sibling property whose name is supplied by the matching `*Key` pointer
14
+ * on the task. That means a directional section can hold:
15
+ * - additional `TransformTask[]` pipelines under names other than
16
+ * `transformOrder` (selected by passing a non-default `orderKey` to
17
+ * `MapFileUtil.getTransformTasks`)
18
+ * - additional REKEY / REMOVE / MORPH / VALUE_TRANSFORM / CONDITIONAL
19
+ * lookup tables under arbitrary names referenced by the task's
20
+ * `rekeyTransformersKey`, `removeKeysKey`, `functionTransformersKey`,
21
+ * or `conditionalTransformDefinitionsKey`.
22
+ *
23
+ * @example
24
+ * vibe2flex: {
25
+ * transformOrder: [
26
+ * { processor: 'VALUE_TRANSFORM', functionTransformersKey: 'valueTransform' },
27
+ * { processor: 'REKEY', rekeyDelete: true, rekeyKeepEmptyValues: true, rekeyTransformersKey: 'rekey' },
28
+ * { processor: 'REMOVE', removeKeysKey: 'removeKey' },
29
+ * ],
30
+ * rekey: { productName: 'name', custBrand: 'brand' },
31
+ * valueTransform: {
32
+ * itemNumber: (row) => '' + row['itemNumber'],
33
+ * },
34
+ * removeKey: ['patternReference', 'vibeOnlyProp'],
35
+ * }
36
+ */
37
+ export interface DirectionalSection {
38
+ /**
39
+ * Ordered pipeline of transforms applied to each row in this direction.
40
+ *
41
+ * Each task is dispatched by its `processor` discriminator
42
+ * (REKEY, REMOVE, VALUE_TRANSFORM, MORPH, CONDITIONAL) and reads its
43
+ * configuration from the corresponding sibling property on this section
44
+ * (e.g. a REKEY task with `rekeyTransformersKey: 'rekey'` reads
45
+ * {@link DirectionalSection.rekey}).
46
+ *
47
+ * @example
48
+ * transformOrder: [
49
+ * { processor: 'REKEY', rekeyDelete: true, rekeyKeepEmptyValues: true, rekeyTransformersKey: 'rekey' },
50
+ * { processor: 'REMOVE', removeKeysKey: 'removeKey' },
51
+ * ]
52
+ */
53
+ transformOrder?: TransformTask[];
54
+ /**
55
+ * Resolves the FlexPLM object class (vibe2flex) or VibeIQ entity class
56
+ * (flex2vibe) for a row when it cannot be derived from the row itself.
57
+ *
58
+ * Consulted by `TypeConversionUtils.getObjectClass` /
59
+ * `getEntityClassFromObject` after the explicit `flexPLMObjectClass` /
60
+ * `vibeIQEntityClass` property and before the `TypeDefaults` fallback.
61
+ *
62
+ * @example
63
+ * // vibe2flex
64
+ * getClass: () => 'LCSRevisableEntity'
65
+ * // flex2vibe
66
+ * getClass: () => 'custom-entity'
67
+ */
68
+ getClass?: (entity: Record<string, unknown>) => string;
69
+ /**
70
+ * Resolves the FlexPLM type path (vibe2flex) or VibeIQ type path
71
+ * (flex2vibe) for a row when it cannot be derived from the row itself.
72
+ *
73
+ * Consulted by `TypeConversionUtils.getObjectTypePath` /
74
+ * `getEntityTypePathFromObject` after the explicit `flexPLMTypePath` /
75
+ * `vibeIQTypePath` property and before the `TypeDefaults` fallback.
76
+ *
77
+ * @example
78
+ * // vibe2flex
79
+ * getSoftType: () => 'Revisable Entity\\Product Concept'
80
+ * // flex2vibe
81
+ * getSoftType: () => 'custom-entity:styleConcept'
82
+ */
83
+ getSoftType?: (entity: Record<string, unknown>) => string;
84
+ /**
85
+ * Lookup table consumed by REKEY tasks in {@link DirectionalSection.transformOrder}.
86
+ *
87
+ * **Read each entry as `destination: 'source'`** — the property name (left)
88
+ * is the key written onto the row, the string value (right) is the
89
+ * existing key whose value is read. Each entry copies (or moves, when the
90
+ * task sets `rekeyDelete: true`) the value at the source key onto the
91
+ * destination key. Referenced by `rekeyTransformersKey` on the REKEY task.
92
+ *
93
+ * Because REKEY runs on the row in its **input** shape, the source side
94
+ * matches the direction's input system and the destination side matches
95
+ * its output system:
96
+ * - Inside `vibe2flex.rekey`: `flexKey: 'vibeKey'` (vibe in → flex out).
97
+ * - Inside `flex2vibe.rekey`: `vibeKey: 'flexKey'` (flex in → vibe out).
98
+ *
99
+ * @example
100
+ * // vibe2flex — left side is the FlexPLM key, right side is the VibeIQ key
101
+ * rekey: {
102
+ * productName: 'name', // flex 'productName' ← vibe 'name'
103
+ * custBrand: 'brand', // flex 'custBrand' ← vibe 'brand'
104
+ * custStyleNumber: 'styleNumber', // flex 'custStyleNumber' ← vibe 'styleNumber'
105
+ * }
106
+ *
107
+ * @example
108
+ * // flex2vibe — left side is the VibeIQ key, right side is the FlexPLM key
109
+ * rekey: {
110
+ * name: 'productName', // vibe 'name' ← flex 'productName'
111
+ * brand: 'custBrand', // vibe 'brand' ← flex 'custBrand'
112
+ * styleNumber: 'custStyleNumber', // vibe 'styleNumber' ← flex 'custStyleNumber'
113
+ * }
114
+ */
115
+ rekey?: RekeyTransformers;
116
+ /**
117
+ * Lookup table consumed by REMOVE tasks in {@link DirectionalSection.transformOrder}.
118
+ *
119
+ * Names of keys to delete from each row. Referenced by `removeKeysKey`
120
+ * on the REMOVE task.
121
+ *
122
+ * @example
123
+ * removeKey: ['patternReference', 'vibeOnlyProp']
124
+ */
125
+ removeKey?: string[];
126
+ /**
127
+ * Lookup table consumed by VALUE_TRANSFORM tasks in
128
+ * {@link DirectionalSection.transformOrder}.
129
+ *
130
+ * Each entry's key is the property the function's return value is written
131
+ * to on the row; the function receives the full row plus optional
132
+ * dependencies. Referenced by `functionTransformersKey` on the
133
+ * VALUE_TRANSFORM task.
134
+ *
135
+ * @example
136
+ * valueTransform: {
137
+ * itemNumber: (row) => {
138
+ * const numValue = parseInt(row['itemNumber'], 10);
139
+ * return isNaN(numValue) ? row['itemNumber'] : numValue;
140
+ * },
141
+ * }
142
+ */
143
+ valueTransform?: Record<string, (row: Record<string, unknown>, dependencies?: unknown) => unknown>;
144
+ /**
145
+ * Lookup table consumed by MORPH tasks in {@link DirectionalSection.transformOrder}.
146
+ *
147
+ * Each entry's function receives the full row plus optional dependencies
148
+ * and may rewrite/augment it in place; unlike {@link DirectionalSection.valueTransform},
149
+ * the function key is just an identifier and is not the destination
150
+ * property. Referenced by `functionTransformersKey` on the MORPH task.
151
+ *
152
+ * @example
153
+ * morphTransform: {
154
+ * morph1: (row) => {
155
+ * const val = row['productStatus'];
156
+ * if (val && Object.keys(val).length === 0) {
157
+ * delete row['productStatus'];
158
+ * }
159
+ * },
160
+ * }
161
+ */
162
+ morphTransform?: Record<string, (row: Record<string, unknown>, dependencies?: unknown) => unknown>;
163
+ /**
164
+ * Catch-all for additional pipelines and per-processor lookup tables
165
+ * referenced by sibling `*Key` pointers on tasks. Each entry should be
166
+ * one of:
167
+ * - {@link TransformTask}[] — alternate pipeline (read it by passing
168
+ * its property name as `orderKey` to `MapFileUtil.getTransformTasks`)
169
+ * - {@link RekeyTransformers} — `newKey` → `existingKey` map for REKEY
170
+ * - `string[]` — keys to delete for REMOVE
171
+ * - {@link FunctionTransformers} — Record<string, Function> for MORPH or VALUE_TRANSFORM
172
+ * - {@link TransformDefinition}[] — conditional rules for CONDITIONAL
173
+ */
174
+ [key: string]: TransformTask[] | TransformDefinition[] | RekeyTransformers | FunctionTransformers | Record<string, unknown> | string[] | ((entity: Record<string, unknown>) => string) | ((entity: Record<string, unknown>) => string[]) | ((row: Record<string, unknown>, dependencies?: unknown) => unknown) | undefined;
175
+ }
176
+ /**
177
+ * Per-entity-type configuration block, keyed in the {@link MappingFile}
178
+ * by the section key returned from
179
+ * {@link TypeConversionEntry.getMapKey} (e.g. `LCSProduct`,
180
+ * `'Exchange Rate'`).
181
+ *
182
+ * Holds direction-agnostic settings (ownership, identifier/informational
183
+ * properties, creation and image-sync gates) plus the two
184
+ * {@link DirectionalSection}s that drive the actual data transforms.
185
+ *
186
+ * @example
187
+ * LCSProduct: {
188
+ * vibeOwningKeys: ['itemNumber', 'lifecycleStage'],
189
+ * getIdentifierProperties: () => ['styleNumber'],
190
+ * vibe2flex: {
191
+ * transformOrder: [
192
+ * { processor: 'REKEY', rekeyDelete: true, rekeyKeepEmptyValues: true, rekeyTransformersKey: 'rekey' },
193
+ * ],
194
+ * // destination: source -> flexKey: 'vibeKey'
195
+ * rekey: { productName: 'name' },
196
+ * },
197
+ * flex2vibe: {
198
+ * transformOrder: [
199
+ * { processor: 'REKEY', rekeyDelete: true, rekeyKeepEmptyValues: true, rekeyTransformersKey: 'rekey' },
200
+ * ],
201
+ * // destination: source -> vibeKey: 'flexKey'
202
+ * rekey: { name: 'productName' },
203
+ * },
204
+ * }
205
+ */
206
+ export interface MappingSection {
207
+ /**
208
+ * These are the slugs of the properties that the VibeIQ system owns. So
209
+ * even if data comes in from an external source, it is ignored and the
210
+ * values are not overwritten.
211
+ *
212
+ * @example
213
+ * vibeOwningKeys: ['itemNumber', 'lifecycleStage']
214
+ */
215
+ vibeOwningKeys?: string[];
216
+ /**
217
+ * Tag identifying which identity service uniqueness pool this entity participates in.
218
+ * There can only be one entity with a given value for the property(defined by `getIdentifierProperties`) within the same uniqueness pool.
219
+ *
220
+ * @example
221
+ * uniquenessPool: 'item'
222
+ */
223
+ uniquenessPool?: string;
224
+ /**
225
+ * Gate controlling whether an inbound FlexPLM object is allowed to
226
+ * create a new VibeIQ entity. When the function returns `false`, the
227
+ * inbound row may still update an existing entity but will not create
228
+ * one. Consulted by `TypeConversionUtils.isInboundCreatableFromObject`;
229
+ * defaults to `false` when the function is absent.
230
+ *
231
+ * @example
232
+ * isInboundCreatable: () => true
233
+ */
234
+ isInboundCreatable?: (entity: Record<string, unknown>, context?: unknown) => boolean;
235
+ /**
236
+ * Gate controlling whether an outbound VibeIQ entity is allowed to
237
+ * create a new FlexPLM object. When the function returns `false`, the
238
+ * outbound publish becomes an update-only operation. Consulted by
239
+ * `TypeConversionUtils.isOutboundCreatableFromEntity`; defaults to
240
+ * `true` when the function is absent.
241
+ *
242
+ * @example
243
+ * isOutboundCreatable: () => false
244
+ */
245
+ isOutboundCreatable?: (entity: Record<string, unknown>, context?: unknown) => boolean;
246
+ /**
247
+ * Gate controlling whether image/thumbnail content should be synced
248
+ * from FlexPLM into VibeIQ for this entity type. Consulted by
249
+ * `TypeConversionUtils.syncInboundImages`; defaults to `false` when
250
+ * the function is absent. Typically aligned with whichever system
251
+ * owns creation of the entity.
252
+ *
253
+ * @example
254
+ * syncInboundImages: () => true
255
+ */
256
+ syncInboundImages?: (entity: Record<string, unknown>, context?: unknown) => boolean;
257
+ /**
258
+ * Gate controlling whether image/thumbnail content should be synced
259
+ * from VibeIQ out to FlexPLM for this entity type. Consulted by
260
+ * `TypeConversionUtils.syncOutboundImages`; defaults to `true` when
261
+ * the function is absent. Typically aligned with whichever system
262
+ * owns creation of the entity.
263
+ *
264
+ * @example
265
+ * syncOutboundImages: () => false
266
+ */
267
+ syncOutboundImages?: (entity: Record<string, unknown>, context?: unknown) => boolean;
268
+ /**
269
+ * Returns the property names that uniquely identify this entity. Used
270
+ * by `TypeConversionUtils.getIdentifierProperties` to drive lookup and
271
+ * matching when an explicit `flexPLMIdentifierProperties` /
272
+ * `vibeIQIdentifierProperties` is not present on the row, falling
273
+ * back to `TypeDefaults` when this is absent.
274
+ *
275
+ * @example
276
+ * getIdentifierProperties: () => ['itemNumber']
277
+ */
278
+ getIdentifierProperties?: (entity: Record<string, unknown>) => string[];
279
+ /**
280
+ * Returns supplemental property names that should be carried alongside
281
+ * identifiers (e.g. display names) but do not participate in identity.
282
+ * Consulted by `TypeConversionUtils.getInformationalProperties` with
283
+ * the same lookup precedence as {@link MappingSection.getIdentifierProperties}.
284
+ *
285
+ * @example
286
+ * getInformationalProperties: () => ['longName']
287
+ */
288
+ getInformationalProperties?: (entity: Record<string, unknown>) => string[];
289
+ /**
290
+ * Transform configuration applied when sending data from VibeIQ to FlexPLM.
291
+ * Pipeline runs on rows in their VibeIQ shape and produces rows in their
292
+ * FlexPLM shape, so inside `rekey` the destination (left) is the FlexPLM
293
+ * key and the source (right) is the VibeIQ key — see
294
+ * {@link DirectionalSection.rekey}.
295
+ */
296
+ vibe2flex?: DirectionalSection;
297
+ /**
298
+ * Transform configuration applied when receiving data from FlexPLM into VibeIQ.
299
+ * Pipeline runs on rows in their FlexPLM shape and produces rows in their
300
+ * VibeIQ shape, so inside `rekey` the destination (left) is the VibeIQ
301
+ * key and the source (right) is the FlexPLM key — see
302
+ * {@link DirectionalSection.rekey}.
303
+ */
304
+ flex2vibe?: DirectionalSection;
305
+ }
306
+ /**
307
+ * Entry in the {@link TypeConversionSection} that maps a single source
308
+ * type onto the {@link MappingSection} key that should handle it.
309
+ *
310
+ * Useful for fanning a single VibeIQ `entityType` (e.g. `'custom-entity'`)
311
+ * out to multiple sections based on the row's `typePath`.
312
+ */
313
+ export interface TypeConversionEntry {
314
+ /**
315
+ * Returns the {@link MappingFile} key whose {@link MappingSection}
316
+ * should be used to transform `entity`. Returning an empty string
317
+ * signals that no section applies and the row should be skipped.
318
+ *
319
+ * @example
320
+ * getMapKey: (entity) => {
321
+ * switch (entity['typePath']) {
322
+ * case 'custom-entity:exchangeRate': return 'Exchange Rate';
323
+ * case 'custom-entity:styleConcept': return 'Style Concept Master';
324
+ * default: return '';
325
+ * }
326
+ * }
327
+ */
328
+ getMapKey: (entity: Record<string, unknown>) => string;
329
+ }
330
+ /**
331
+ * One direction of {@link TypeConversion}, keyed by the source-system
332
+ * type:
333
+ * - In `vibe2flex`, keys are VibeIQ entity types (e.g. `'custom-entity'`,
334
+ * `'size-range-template'`) — see `TypeConversionUtils.getEntityType`.
335
+ * - In `flex2vibe`, keys are FlexPLM object classes (e.g. `LCSProduct`) —
336
+ * see `TypeConversionUtils.getObjectType`.
337
+ */
338
+ export interface TypeConversionSection {
339
+ [type: string]: TypeConversionEntry;
340
+ }
341
+ /**
342
+ * Top-level routing table that resolves a row to its
343
+ * {@link MappingSection} key for each direction.
344
+ *
345
+ * `TypeConversionUtils.getMapKey` / `getMapKeyFromObject` look up the
346
+ * row's type in the appropriate side, invoke
347
+ * {@link TypeConversionEntry.getMapKey}, and use the returned string
348
+ * as the key into the rest of the {@link MappingFile}.
349
+ *
350
+ * @example
351
+ * typeConversion: {
352
+ * vibe2flex: {
353
+ * 'custom-entity': {
354
+ * getMapKey: (entity) => {
355
+ * switch (entity['typePath']) {
356
+ * case 'custom-entity:exchangeRate': return 'Exchange Rate';
357
+ * default: return '';
358
+ * }
359
+ * },
360
+ * },
361
+ * 'size-range-template': {
362
+ * getMapKey: () => 'size-range-template',
363
+ * },
364
+ * },
365
+ * flex2vibe: {
366
+ * LCSRevisableEntity: {
367
+ * getMapKey: (object) => {
368
+ * switch (object['flexPLMTypePath']) {
369
+ * case 'Revisable Entity\\Exchange Rate': return 'Exchange Rate';
370
+ * default: return '';
371
+ * }
372
+ * },
373
+ * },
374
+ * },
375
+ * }
376
+ */
377
+ export interface TypeConversion {
378
+ /** Routes outbound VibeIQ entities to their FlexPLM mapping sections. */
379
+ vibe2flex: TypeConversionSection;
380
+ /** Routes inbound FlexPLM objects to their VibeIQ mapping sections. */
381
+ flex2vibe: TypeConversionSection;
382
+ }
383
+ /** Identifies the application and tenant that owns a {@link MappingFile}. */
384
+ export interface OrgInfo {
385
+ /** This is the identifier for the application, and is used to set the owner of the mapping file */
386
+ appIdentifier: '@vibeiq/flexplm-connector';
387
+ /** The name of the organization using the mapping file. */
388
+ orgName: string;
389
+ }
390
+ interface MappingFileBase {
391
+ /** The information about the organization using the mapping file */
392
+ orgInfo: OrgInfo;
393
+ /** The type conversion information for the mapping file. */
394
+ typeConversion: TypeConversion;
395
+ }
396
+ /**
397
+ * Full mapping file driving bidirectional sync between VibeIQ and FlexPLM
398
+ * for a single tenant.
399
+ *
400
+ * Combines the fixed {@link MappingFileBase} fields (`orgInfo`,
401
+ * `typeConversion`) with an open set of {@link MappingSection} entries
402
+ * keyed by the strings returned from
403
+ * {@link TypeConversionEntry.getMapKey} (e.g. `LCSProduct`, `LCSSKU`,
404
+ * `'Exchange Rate'`).
405
+ *
406
+ * @example
407
+ * export const mapping: MappingFile = {
408
+ * orgInfo: {
409
+ * appIdentifier: '@vibeiq/flexplm-connector',
410
+ * orgName: 'acme',
411
+ * },
412
+ * typeConversion: {
413
+ * vibe2flex: {
414
+ * 'custom-entity': {
415
+ * getMapKey: (entity) => {
416
+ * switch (entity['typePath']) {
417
+ * case 'custom-entity:exchangeRate': return 'Exchange Rate';
418
+ * default: return '';
419
+ * }
420
+ * },
421
+ * },
422
+ * 'size-range-template': {
423
+ * getMapKey: () => 'size-range-template',
424
+ * },
425
+ * },
426
+ * flex2vibe: {
427
+ * LCSRevisableEntity: {
428
+ * getMapKey: (object) => {
429
+ * switch (object['flexPLMTypePath']) {
430
+ * case 'Revisable Entity\\Exchange Rate': return 'Exchange Rate';
431
+ * default: return '';
432
+ * }
433
+ * },
434
+ * },
435
+ * },
436
+ * },
437
+ * 'Exchange Rate': {
438
+ * vibe2flex: {
439
+ * transformOrder: [
440
+ * { processor: 'REKEY', rekeyDelete: true, rekeyKeepEmptyValues: true, rekeyTransformersKey: 'rekey' },
441
+ * ],
442
+ * rekey: { exchangeRateDescription: 'name' },
443
+ * getClass: () => 'LCSRevisableEntity',
444
+ * getSoftType: () => 'Revisable Entity\\Exchange Rate',
445
+ * },
446
+ * flex2vibe: {
447
+ * transformOrder: [
448
+ * { processor: 'REKEY', rekeyDelete: true, rekeyKeepEmptyValues: true, rekeyTransformersKey: 'rekey' },
449
+ * ],
450
+ * rekey: { name: 'exchangeRateDescription' },
451
+ * getClass: () => 'custom-entity',
452
+ * getSoftType: () => 'custom-entity:exchangeRate',
453
+ * },
454
+ * },
455
+ * };
456
+ */
457
+ export type MappingFile = MappingFileBase & {
458
+ [mapKey: string]: MappingSection | MappingFileBase[keyof MappingFileBase];
459
+ };
460
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -32,6 +32,15 @@ export declare class BaseProcessPublishAssortment {
32
32
  skip_await?: undefined;
33
33
  }>;
34
34
  getPublishInfo(assortmentId: string, assortmentPublishChangeId: string, apcHistory: any, publisher: any): Promise<any>;
35
+ /** Gets the version number of the snapshot that was created for the publish change.
36
+ * But if no snapshot was found for the publish change, and the last snapshot was
37
+ * created before the publish change, then it returns 'after: versionNumber' where
38
+ * versionNumber is the version number of the last snapshot.
39
+ * If no snapshot was found for the publish change, and the last snapshot was
40
+ * created after the publish change, then it returns 'unknown'.
41
+ *
42
+ * @returns versionNumber or 'unknown' or 'after: versionNumber'
43
+ */
35
44
  getSnapshotVersion(assortment: any, apc: any): Promise<number | string>;
36
45
  getSeasonFederation(assortmentId: any): Promise<SeasonFederation>;
37
46
  getAssortment(assortmentId: any): Promise<any>;
@@ -82,6 +91,22 @@ export declare class BaseProcessPublishAssortment {
82
91
  private getCurrentDateString;
83
92
  getItemFamilyChanges(pcd: PublishChangeData, changeDetail: any, assortmentItemFullChangeMap: Map<string, any>, assortmentItemDeleteMap: Map<string, any>): Map<string, ItemFamilyChanges>;
84
93
  getEventsForPublishChangeData(publishChangeData: PublishChangeData): Promise<SeasonalPayload[]>;
94
+ /**Returns the events for a given ItemFamilyChanges object
95
+ *
96
+ * Cases:
97
+ * Add just family:
98
+ * Add option to family (with existing option):
99
+ * Add option to family (no options on assortment):
100
+ * Remove family and option:
101
+ * Remove only option and leave family:
102
+ * Remove one option of multiple for family:
103
+ *
104
+ * @param itemFamilyChanges
105
+ * @param assortmentId
106
+ * @param assortmentFederationId
107
+ * @param itemToFederatedIdMapping
108
+ * @returns
109
+ */
85
110
  getEventsForItemFamilyChanges(itemFamilyChanges: ItemFamilyChanges, assortmentId: string, seasonFed: SeasonFederation, itemToFederatedIdMapping: Map<string, string>): Promise<SeasonalPayload[]>;
86
111
  getProjectItem(itemFamilyChanges: ItemFamilyChanges, id: string): any;
87
112
  getSeasonalData(projectItem: any): Promise<object>;
@@ -13,7 +13,7 @@ const app_framework_1 = require("@contrail/app-framework");
13
13
  const event_short_message_status_1 = require("../util/event-short-message-status");
14
14
  class BaseProcessPublishAssortment {
15
15
  constructor(_config, _dc, _mapFileUtil) {
16
- this.TTL = 30 * 24 * 60 * 60 * 1000;
16
+ this.TTL = 30 * 24 * 60 * 60 * 1000; // 30 days
17
17
  this.cache = {
18
18
  carriedFromSeason: {}
19
19
  };
@@ -52,13 +52,14 @@ class BaseProcessPublishAssortment {
52
52
  }
53
53
  apcHistory = await this.getApcHistory(assortmentId);
54
54
  const sinceDate = await this.getSinceDate(assortmentId, assortmentPublishChangeId, apcHistory);
55
+ //Get detail information
55
56
  const assortmentPublishChange = await this.downloadAssortmentPublishChange(assortmentId, assortmentPublishChangeId);
56
57
  publisher = this.getPublisher(assortmentPublishChange);
57
58
  const changeDetail = await this.downloadHydratedChangeDetail(assortmentPublishChange);
58
59
  const assortmentBaseline = await this.downloadAssortmentBaseline(assortmentPublishChange);
59
60
  const deleteChanges = await this.getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate);
60
61
  const releasedForDevelopmentItemIds = this.getReleasedForDevelopmentItemAndFamilyIds(assortmentBaseline, deleteChanges);
61
- const itemToFederatedIdMapping = await this.getItemFederatedIds();
62
+ const itemToFederatedIdMapping = await this.getItemFederatedIds( /*allItemIds*/);
62
63
  const pcd = new publish_change_data_1.PublishChangeData(assortmentId, seasonFed, assortmentPublishChangeId, sinceDate, publisher);
63
64
  pcd.itemToFederatedIdMapping = itemToFederatedIdMapping;
64
65
  pcd.releasedForDevelopmentItemIds = releasedForDevelopmentItemIds;
@@ -94,6 +95,15 @@ class BaseProcessPublishAssortment {
94
95
  };
95
96
  return publishInfo;
96
97
  }
98
+ /** Gets the version number of the snapshot that was created for the publish change.
99
+ * But if no snapshot was found for the publish change, and the last snapshot was
100
+ * created before the publish change, then it returns 'after: versionNumber' where
101
+ * versionNumber is the version number of the last snapshot.
102
+ * If no snapshot was found for the publish change, and the last snapshot was
103
+ * created after the publish change, then it returns 'unknown'.
104
+ *
105
+ * @returns versionNumber or 'unknown' or 'after: versionNumber'
106
+ */
97
107
  async getSnapshotVersion(assortment, apc) {
98
108
  const entityReference = assortment?.createdForReference;
99
109
  const createdOnString = apc?.createdOn;
@@ -101,7 +111,7 @@ class BaseProcessPublishAssortment {
101
111
  return 'unknown';
102
112
  }
103
113
  const createdOnDate = new Date(createdOnString);
104
- createdOnDate.setMonth(createdOnDate.getMonth() - 1);
114
+ createdOnDate.setMonth(createdOnDate.getMonth() - 1); //subtract 1 month
105
115
  const createdOnStringMinus1 = createdOnDate.toISOString();
106
116
  const createdOn = 'ISGREATERTHAN ' + createdOnStringMinus1;
107
117
  const snapshots = await new sdk_1.Entities().get({
@@ -348,6 +358,7 @@ class BaseProcessPublishAssortment {
348
358
  console.info('sinceDateMilliseconds: ' + sinceDateMilliseconds);
349
359
  console.info('apcDateMilliseconds: ' + previousApcDate);
350
360
  }
361
+ //if only 1 apc, no processing needed
351
362
  if (sinceDateMilliseconds !== previousApcDate) {
352
363
  console.info('sinceDateMilliseconds !== apcDateMilliseconds');
353
364
  const currentAssortmentItemIds = this.getBaselineItemIds(assortmentBaseline);
@@ -433,12 +444,27 @@ class BaseProcessPublishAssortment {
433
444
  previousBaseline = await this.downloadAssortmentBaseline(previousApc);
434
445
  }
435
446
  const deleteIds = apc?.detail?.deletes.map(dItem => dItem?.id);
447
+ //building deletes based on previous baseline; because some APCs don't have delete data
436
448
  const deleteArray = previousBaseline?.assortmentItems.filter(aItem => deleteIds.includes(aItem?.itemId));
437
449
  console.info('deleteArray.length: ' + deleteArray.length);
438
450
  return deleteArray;
439
451
  }
440
- async getItemFederatedIds() {
452
+ async getItemFederatedIds( /*itemIds*/) {
441
453
  const itemFederatedIds = new Map();
454
+ // const expandedItemIds = itemIds.map(id => 'item:' + id);
455
+ // const fedRecords = await new Federation(this.logger).getFederationRecordsFromIdsBulk(expandedItemIds);
456
+ // for (let i = 0; i < fedRecords.length; i++) {
457
+ // const federationRecord = fedRecords[i];
458
+ // // console.log('federationRecord: ' + JSON.stringify(federationRecord));
459
+ // // console.log('federationRecord.reference: ' + federationRecord.reference);
460
+ // // console.log('federationRecord.mappedReference: ' + federationRecord.mappedReference);
461
+ // let vibeId = federationRecord.reference;
462
+ // if (vibeId && vibeId.startsWith('item:')) {
463
+ // vibeId = vibeId.substring(5);
464
+ // }
465
+ // itemFederatedIds.set(vibeId, federationRecord.mappedReference);
466
+ // }
467
+ // console.log('itemFederatedIds: ' + JSON.stringify(Object.fromEntries(itemFederatedIds)));
442
468
  return itemFederatedIds;
443
469
  }
444
470
  getFullChangeAssortmentMap(fullChange) {
@@ -565,7 +591,7 @@ class BaseProcessPublishAssortment {
565
591
  case 'vibeiqfile-dontsendtoflexplm':
566
592
  return this.handleVibeIQFile(events, eventType, sendMode);
567
593
  default:
568
- return {};
594
+ return {}; // Or handle other cases as required
569
595
  }
570
596
  }
571
597
  async sendToFlexPLM(events, eventType) {
@@ -735,7 +761,7 @@ class BaseProcessPublishAssortment {
735
761
  else {
736
762
  ifc.colorDeletes.push(itemId);
737
763
  }
738
- }
764
+ } //End deletes for loop
739
765
  if (app_framework_1.Logger.isDebugOn()) {
740
766
  console.debug('returning size: ' + itemFamilyChanges.size);
741
767
  for (const [key, value] of itemFamilyChanges) {
@@ -755,6 +781,22 @@ class BaseProcessPublishAssortment {
755
781
  }
756
782
  return seasonalPayloads;
757
783
  }
784
+ /**Returns the events for a given ItemFamilyChanges object
785
+ *
786
+ * Cases:
787
+ * Add just family:
788
+ * Add option to family (with existing option):
789
+ * Add option to family (no options on assortment):
790
+ * Remove family and option:
791
+ * Remove only option and leave family:
792
+ * Remove one option of multiple for family:
793
+ *
794
+ * @param itemFamilyChanges
795
+ * @param assortmentId
796
+ * @param assortmentFederationId
797
+ * @param itemToFederatedIdMapping
798
+ * @returns
799
+ */
758
800
  async getEventsForItemFamilyChanges(itemFamilyChanges, assortmentId, seasonFed, itemToFederatedIdMapping) {
759
801
  console.info('getEventsForItemFamilyChanges()');
760
802
  const events = [];
@@ -764,7 +806,10 @@ class BaseProcessPublishAssortment {
764
806
  const familyAssortmentItem = itemFamilyChanges.familyAdd || itemFamilyChanges.familyUpdate || itemFamilyChanges.familyDelete
765
807
  || itemFamilyChanges.colorAdds.length > 0 || itemFamilyChanges.colorUpdates.length > 0 || itemFamilyChanges.colorDeletes.length > 0
766
808
  || (projectItem && this.updatedSinceDate(projectItem, itemFamilyChanges.sinceDate));
809
+ //familyItemRemoved is used when adding the first option to an assortment
810
+ //and will have updates for the family item.
767
811
  if (familyAssortmentItem) {
812
+ //Product-season add
768
813
  const entityReference = itemFamilyChanges.itemFamilyId;
769
814
  const prodEntityData = (itemFamilyChanges.assortmentItemFullChangeMap.has(entityReference))
770
815
  ? itemFamilyChanges.assortmentItemFullChangeMap.get(entityReference)?.item
@@ -790,6 +835,9 @@ class BaseProcessPublishAssortment {
790
835
  }
791
836
  events.push(psUpsert);
792
837
  }
838
+ //colorway-season adds
839
+ //colorAdds
840
+ //colorUpdates
793
841
  const colorwayChanges = [];
794
842
  colorwayChanges.push(...itemFamilyChanges.colorAdds);
795
843
  colorwayChanges.push(...itemFamilyChanges.colorUpdates);
@@ -830,6 +878,9 @@ class BaseProcessPublishAssortment {
830
878
  const aItem = itemFamilyChanges?.assortmentItemFullChangeMap.get(id);
831
879
  projectItem = aItem?.projectItem;
832
880
  }
881
+ /////////////////////////////////////////////////////////////////////////////
882
+ //Get from option assortmentItem because family not in assortmentItemFullChangeMap:start
883
+ /////////////////////////////////////////////////////////////////////////////
833
884
  if (id === itemFamilyChanges.itemFamilyId && Object.keys(projectItem).length == 0) {
834
885
  for (const asstItem of itemFamilyChanges.assortmentItemFullChangeMap.values()) {
835
886
  if (asstItem?.familyProjectItem) {
@@ -838,6 +889,9 @@ class BaseProcessPublishAssortment {
838
889
  }
839
890
  }
840
891
  }
892
+ /////////////////////////////////////////////////////////////////////////////
893
+ //Get from option assortmentItem because family not in assortmentItemFullChangeMap:end
894
+ /////////////////////////////////////////////////////////////////////////////
841
895
  return projectItem;
842
896
  }
843
897
  async getSeasonalData(projectItem) {