@contrail/flexplm 1.5.1-alpha.c9b11be → 1.6.0-alpha.8e73fa3
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/lib/cli/commands/compile.d.ts +4 -0
- package/lib/cli/commands/compile.js +73 -0
- package/lib/cli/commands/compile.spec.d.ts +1 -0
- package/lib/cli/commands/compile.spec.js +80 -0
- package/lib/cli/commands/create.d.ts +5 -0
- package/lib/cli/commands/create.js +77 -0
- package/lib/cli/commands/create.spec.d.ts +1 -0
- package/lib/cli/commands/create.spec.js +78 -0
- package/lib/cli/commands/upload.d.ts +17 -0
- package/lib/cli/commands/upload.js +228 -0
- package/lib/cli/commands/upload.spec.d.ts +1 -0
- package/lib/cli/commands/upload.spec.js +88 -0
- package/lib/cli/index.d.ts +5 -0
- package/lib/cli/index.js +70 -0
- package/lib/cli/index.spec.d.ts +1 -0
- package/lib/cli/index.spec.js +85 -0
- package/lib/cli/template/mapping-template.ts.template +62 -0
- package/lib/entity-processor/base-entity-processor.d.ts +65 -0
- package/lib/entity-processor/base-entity-processor.js +71 -0
- package/lib/entity-processor/base-entity-processor.spec.js +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/interfaces/mapping-file.d.ts +783 -0
- package/lib/interfaces/mapping-file.js +2 -0
- package/lib/publish/base-process-publish-assortment.d.ts +26 -0
- package/lib/publish/base-process-publish-assortment.js +87 -13
- package/lib/publish/base-process-publish-assortment.spec.js +100 -9
- package/lib/publish/mockData.js +5 -0
- package/lib/transform/identifier-conversion-spec-mockData.js +34 -6
- package/lib/transform/identifier-conversion.d.ts +36 -0
- package/lib/transform/identifier-conversion.js +36 -0
- package/lib/transform/identifier-conversion.spec.js +4 -0
- package/lib/util/config-defaults.js +3 -0
- package/lib/util/config-defaults.spec.js +9 -0
- package/lib/util/data-converter-spec-mockData.js +17 -3
- package/lib/util/data-converter.d.ts +97 -0
- package/lib/util/data-converter.js +127 -1
- package/lib/util/data-converter.spec.js +2 -0
- package/lib/util/error-response-object.d.ts +5 -0
- package/lib/util/error-response-object.js +7 -0
- package/lib/util/event-short-message-status.js +1 -0
- package/lib/util/federation.js +8 -0
- package/lib/util/flexplm-connect.d.ts +7 -0
- package/lib/util/flexplm-connect.js +14 -0
- package/lib/util/logger-config.js +1 -0
- package/lib/util/map-util-spec-mockData.js +17 -3
- package/lib/util/map-utils.d.ts +27 -0
- package/lib/util/map-utils.js +27 -0
- package/lib/util/thumbnail-util.d.ts +21 -0
- package/lib/util/thumbnail-util.js +28 -1
- package/lib/util/thumbnail-util.spec.js +6 -0
- package/lib/util/type-conversion-utils-spec-mockData.js +3 -3
- package/lib/util/type-conversion-utils.d.ts +151 -0
- package/lib/util/type-conversion-utils.js +154 -0
- package/lib/util/type-defaults.d.ts +66 -0
- package/lib/util/type-defaults.js +66 -0
- package/lib/util/type-defaults.spec.js +5 -5
- package/lib/util/type-utils.d.ts +21 -0
- package/lib/util/type-utils.js +23 -0
- package/lib/util/type-utils.spec.js +2 -0
- package/package.json +22 -6
- package/scripts/copy-template.js +10 -0
- package/.github/pull_request_template.md +0 -31
- package/.github/workflows/flexplm-lib.yml +0 -27
- package/.github/workflows/publish-to-npm.yml +0 -121
- package/CHANGELOG.md +0 -49
- package/publish.bat +0 -5
- package/publish.sh +0 -5
- package/src/entity-processor/base-entity-processor.spec.ts +0 -689
- package/src/entity-processor/base-entity-processor.ts +0 -583
- package/src/flexplm-request.ts +0 -28
- package/src/flexplm-utils.spec.ts +0 -27
- package/src/flexplm-utils.ts +0 -29
- package/src/index.ts +0 -22
- package/src/interfaces/interfaces.ts +0 -122
- package/src/interfaces/item-family-changes.ts +0 -67
- package/src/interfaces/publish-change-data.ts +0 -43
- package/src/publish/base-process-publish-assortment-callback.ts +0 -50
- package/src/publish/base-process-publish-assortment.spec.ts +0 -2154
- package/src/publish/base-process-publish-assortment.ts +0 -1173
- package/src/publish/mockData.ts +0 -4561
- package/src/transform/identifier-conversion-spec-mockData.ts +0 -496
- package/src/transform/identifier-conversion.spec.ts +0 -386
- package/src/transform/identifier-conversion.ts +0 -282
- package/src/util/config-defaults.spec.ts +0 -445
- package/src/util/config-defaults.ts +0 -106
- package/src/util/data-converter-spec-mockData.ts +0 -231
- package/src/util/data-converter.spec.ts +0 -1622
- package/src/util/data-converter.ts +0 -819
- package/src/util/error-response-object.spec.ts +0 -116
- package/src/util/error-response-object.ts +0 -50
- package/src/util/event-short-message-status.ts +0 -22
- package/src/util/federation.ts +0 -172
- package/src/util/flexplm-connect.spec.ts +0 -132
- package/src/util/flexplm-connect.ts +0 -208
- package/src/util/logger-config.ts +0 -20
- package/src/util/map-util-spec-mockData.ts +0 -231
- package/src/util/map-utils.spec.ts +0 -103
- package/src/util/map-utils.ts +0 -41
- package/src/util/mockData.ts +0 -101
- package/src/util/thumbnail-util.spec.ts +0 -508
- package/src/util/thumbnail-util.ts +0 -272
- package/src/util/type-conversion-utils-spec-mockData.ts +0 -272
- package/src/util/type-conversion-utils.spec.ts +0 -1031
- package/src/util/type-conversion-utils.ts +0 -490
- package/src/util/type-defaults.spec.ts +0 -797
- package/src/util/type-defaults.ts +0 -320
- package/src/util/type-utils.spec.ts +0 -227
- package/src/util/type-utils.ts +0 -144
- package/tsconfig.json +0 -24
- package/tslint.json +0 -57
|
@@ -0,0 +1,783 @@
|
|
|
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
|
+
* Ordering matters — tasks run top-to-bottom and operate on whatever
|
|
48
|
+
* the previous task left behind. Also there can be multiple tasks of
|
|
49
|
+
* the same processor type, as long as they have different config keys.
|
|
50
|
+
* This allows you to split up different logical steps (e.g. remove most
|
|
51
|
+
* unwanted fields before a value transform. Where the value transform
|
|
52
|
+
* needs a field to be present, but it must be removed before sending.
|
|
53
|
+
* Conventional order:
|
|
54
|
+
* 1. **REMOVE** — drop keys you don't want carried forward, while the
|
|
55
|
+
* row is still in its input shape (so REMOVE keys match the input
|
|
56
|
+
* system's slugs).
|
|
57
|
+
* 2. **VALUE_TRANSFORM** / **MORPH** — coerce / normalize values
|
|
58
|
+
* while the keys are still recognizable.
|
|
59
|
+
* 3. **REKEY** — last, so the remaining keys are renamed to the
|
|
60
|
+
* output system. After REKEY runs with `rekeyDelete: true`, the
|
|
61
|
+
* input-side slugs are gone and any later REMOVE / VALUE_TRANSFORM
|
|
62
|
+
* would need to be written against the output-side slugs instead.
|
|
63
|
+
*
|
|
64
|
+
* Swapping REKEY before REMOVE is a common cause of "my removeKey isn't
|
|
65
|
+
* doing anything" — by the time REMOVE runs, the keys have already
|
|
66
|
+
* been renamed.
|
|
67
|
+
*
|
|
68
|
+
* **Only processors listed in `transformOrder` actually run.** A
|
|
69
|
+
* `rekey` / `removeKey` / `valueTransform` table sitting on the
|
|
70
|
+
* directional section is inert unless a matching task references it
|
|
71
|
+
* here — defining `rekey: {...}` without a REKEY entry in
|
|
72
|
+
* `transformOrder` is silently a no-op. If a step seems to be doing
|
|
73
|
+
* nothing, check that its processor is in this list before assuming
|
|
74
|
+
* the lookup table is wrong.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Conventional: REMOVE → VALUE_TRANSFORM → REKEY.
|
|
78
|
+
* transformOrder: [
|
|
79
|
+
* { processor: 'REMOVE', removeKeysKey: 'removeKey' },
|
|
80
|
+
* { processor: 'VALUE_TRANSFORM', functionTransformersKey: 'valueTransform' },
|
|
81
|
+
* { processor: 'REKEY', rekeyDelete: true, rekeyKeepEmptyValues: true, rekeyTransformersKey: 'rekey' },
|
|
82
|
+
* ]
|
|
83
|
+
*/
|
|
84
|
+
transformOrder?: TransformTask[];
|
|
85
|
+
/**
|
|
86
|
+
* Resolves the FlexPLM object class (vibe2flex) or VibeIQ entity class
|
|
87
|
+
* (flex2vibe) for a row when it cannot be derived from the row itself.
|
|
88
|
+
*
|
|
89
|
+
* Consulted by `TypeConversionUtils.getObjectClass` /
|
|
90
|
+
* `getEntityClassFromObject` after the explicit `flexPLMObjectClass` /
|
|
91
|
+
* `vibeIQEntityClass` property and before the `TypeDefaults` fallback.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* // vibe2flex
|
|
95
|
+
* getClass: () => 'LCSRevisableEntity'
|
|
96
|
+
* // flex2vibe
|
|
97
|
+
* getClass: () => 'custom-entity'
|
|
98
|
+
*/
|
|
99
|
+
getClass?: (entity: Record<string, unknown>) => string;
|
|
100
|
+
/**
|
|
101
|
+
* Resolves the FlexPLM type path (vibe2flex) or VibeIQ type path
|
|
102
|
+
* (flex2vibe) for a row when it cannot be derived from the row itself.
|
|
103
|
+
*
|
|
104
|
+
* Consulted by `TypeConversionUtils.getObjectTypePath` /
|
|
105
|
+
* `getEntityTypePathFromObject` after the explicit `flexPLMTypePath` /
|
|
106
|
+
* `vibeIQTypePath` property and before the `TypeDefaults` fallback.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* // vibe2flex
|
|
110
|
+
* getSoftType: () => 'Revisable Entity\\Product Concept'
|
|
111
|
+
* // flex2vibe
|
|
112
|
+
* getSoftType: () => 'custom-entity:styleConcept'
|
|
113
|
+
*/
|
|
114
|
+
getSoftType?: (entity: Record<string, unknown>) => string;
|
|
115
|
+
/**
|
|
116
|
+
* Lookup table consumed by REKEY tasks in {@link DirectionalSection.transformOrder}.
|
|
117
|
+
*
|
|
118
|
+
* **Read each entry as `destination: 'source'`** — the property name (left)
|
|
119
|
+
* is the key written onto the row, the string value (right) is the
|
|
120
|
+
* existing key whose value is read. Each entry copies (or moves, when the
|
|
121
|
+
* task sets `rekeyDelete: true`) the value at the source key onto the
|
|
122
|
+
* destination key. Referenced by `rekeyTransformersKey` on the REKEY task.
|
|
123
|
+
*
|
|
124
|
+
* Because REKEY runs on the row in its **input** shape, the source side
|
|
125
|
+
* matches the direction's input system and the destination side matches
|
|
126
|
+
* its output system:
|
|
127
|
+
* - Inside `vibe2flex.rekey`: `flexKey: 'vibeKey'` (vibe in → flex out).
|
|
128
|
+
* - Inside `flex2vibe.rekey`: `vibeKey: 'flexKey'` (flex in → vibe out).
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* // vibe2flex — left side is the FlexPLM key, right side is the VibeIQ key
|
|
132
|
+
* rekey: {
|
|
133
|
+
* productName: 'name', // flex 'productName' ← vibe 'name'
|
|
134
|
+
* custBrand: 'brand', // flex 'custBrand' ← vibe 'brand'
|
|
135
|
+
* custStyleNumber: 'styleNumber', // flex 'custStyleNumber' ← vibe 'styleNumber'
|
|
136
|
+
* }
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* // flex2vibe — left side is the VibeIQ key, right side is the FlexPLM key
|
|
140
|
+
* rekey: {
|
|
141
|
+
* name: 'productName', // vibe 'name' ← flex 'productName'
|
|
142
|
+
* brand: 'custBrand', // vibe 'brand' ← flex 'custBrand'
|
|
143
|
+
* styleNumber: 'custStyleNumber', // vibe 'styleNumber' ← flex 'custStyleNumber'
|
|
144
|
+
* }
|
|
145
|
+
*/
|
|
146
|
+
rekey?: RekeyTransformers;
|
|
147
|
+
/**
|
|
148
|
+
* Lookup table consumed by REMOVE tasks in {@link DirectionalSection.transformOrder}.
|
|
149
|
+
*
|
|
150
|
+
* Names of keys to delete from each row. Referenced by `removeKeysKey`
|
|
151
|
+
* on the REMOVE task.
|
|
152
|
+
*
|
|
153
|
+
* On the `vibe2flex` side, this is the **outbound filter** —
|
|
154
|
+
* {@link MappingSection.vibeOwningKeys} only governs inbound
|
|
155
|
+
* protection, so anything you want kept out of the FlexPLM payload
|
|
156
|
+
* has to be listed here. Typical entries:
|
|
157
|
+
* - Vibe-internal fields with no FlexPLM equivalent
|
|
158
|
+
* (`federatedId`, computed totals, planning-only properties).
|
|
159
|
+
* - FlexPLM-owned inbound properties under their Vibe-side slug,
|
|
160
|
+
* so they aren't echoed back to FlexPLM after a prior inbound sync.
|
|
161
|
+
*
|
|
162
|
+
* Keys are matched against the row in its **input** shape, so on
|
|
163
|
+
* `vibe2flex` list Vibe-side slugs (REMOVE runs before REKEY in the
|
|
164
|
+
* conventional ordering) and on `flex2vibe` list FlexPLM field names.
|
|
165
|
+
*
|
|
166
|
+
* `removeKey` vs {@link MappingSection.vibeOwningKeys} — different
|
|
167
|
+
* jobs, not redundant:
|
|
168
|
+
* - `vibeOwningKeys` = **refuse to accept on inbound**.
|
|
169
|
+
* - `vibe2flex.removeKey` = **refuse to send on outbound**.
|
|
170
|
+
*
|
|
171
|
+
* A property can belong in one, both, or neither — decide per
|
|
172
|
+
* property based on which system owns it:
|
|
173
|
+
* - `targetIntroDate` (Vibe-owned, no Flex counterpart)
|
|
174
|
+
* → in **both** (protect inbound, don't bother sending).
|
|
175
|
+
* - `materialDescription` (Flex-owned, Vibe stores it after a
|
|
176
|
+
* prior inbound sync) → in **`removeKey` only**, so the value
|
|
177
|
+
* isn't echoed back to Flex; Flex still owns updates inbound.
|
|
178
|
+
* - `event` (Vibe-owned, published to Flex) → in
|
|
179
|
+
* **`vibeOwningKeys` only**; we want to keep sending it.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* // vibe2flex.removeKey — strip Vibe-internal + Flex-owned-inbound
|
|
183
|
+
* // properties before publish.
|
|
184
|
+
* removeKey: [
|
|
185
|
+
* 'federatedId',
|
|
186
|
+
* 'targetIntroDate', // Vibe-owned, no Flex counterpart
|
|
187
|
+
* 'materialDescription', // Vibe-side slug of a Flex-owned inbound field
|
|
188
|
+
* ]
|
|
189
|
+
*/
|
|
190
|
+
removeKey?: string[];
|
|
191
|
+
/**
|
|
192
|
+
* Lookup table consumed by VALUE_TRANSFORM tasks in
|
|
193
|
+
* {@link DirectionalSection.transformOrder}.
|
|
194
|
+
*
|
|
195
|
+
* Each entry's key is the property the function's return value is written
|
|
196
|
+
* to on the row; the function receives the full row plus optional
|
|
197
|
+
* dependencies. Referenced by `functionTransformersKey` on the
|
|
198
|
+
* VALUE_TRANSFORM task.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* valueTransform: {
|
|
202
|
+
* itemNumber: (row) => {
|
|
203
|
+
* const numValue = parseInt(row['itemNumber'], 10);
|
|
204
|
+
* return isNaN(numValue) ? row['itemNumber'] : numValue;
|
|
205
|
+
* },
|
|
206
|
+
* }
|
|
207
|
+
*/
|
|
208
|
+
valueTransform?: Record<string, (row: Record<string, unknown>, dependencies?: unknown) => unknown>;
|
|
209
|
+
/**
|
|
210
|
+
* Lookup table consumed by MORPH tasks in {@link DirectionalSection.transformOrder}.
|
|
211
|
+
*
|
|
212
|
+
* Each entry's function receives the full row plus optional dependencies
|
|
213
|
+
* and may rewrite/augment it in place; unlike {@link DirectionalSection.valueTransform},
|
|
214
|
+
* the function key is just an identifier and is not the destination
|
|
215
|
+
* property. Referenced by `functionTransformersKey` on the MORPH task.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* morphTransform: {
|
|
219
|
+
* morph1: (row) => {
|
|
220
|
+
* const val = row['productStatus'];
|
|
221
|
+
* if (val && Object.keys(val).length === 0) {
|
|
222
|
+
* delete row['productStatus'];
|
|
223
|
+
* }
|
|
224
|
+
* },
|
|
225
|
+
* }
|
|
226
|
+
*/
|
|
227
|
+
morphTransform?: Record<string, (row: Record<string, unknown>, dependencies?: unknown) => unknown>;
|
|
228
|
+
/**
|
|
229
|
+
* Catch-all for additional pipelines and per-processor lookup tables
|
|
230
|
+
* referenced by sibling `*Key` pointers on tasks. Each entry should be
|
|
231
|
+
* one of:
|
|
232
|
+
* - {@link TransformTask}[] — alternate pipeline (read it by passing
|
|
233
|
+
* its property name as `orderKey` to `MapFileUtil.getTransformTasks`)
|
|
234
|
+
* - {@link RekeyTransformers} — `newKey` → `existingKey` map for REKEY
|
|
235
|
+
* - `string[]` — keys to delete for REMOVE
|
|
236
|
+
* - {@link FunctionTransformers} — Record<string, Function> for MORPH or VALUE_TRANSFORM
|
|
237
|
+
* - {@link TransformDefinition}[] — conditional rules for CONDITIONAL
|
|
238
|
+
*/
|
|
239
|
+
[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;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Per-entity-type configuration block, keyed in the {@link MappingFile}
|
|
243
|
+
* by the section key returned from
|
|
244
|
+
* {@link TypeConversionEntry.getMapKey} (e.g. `LCSProduct`,
|
|
245
|
+
* `'Exchange Rate'`).
|
|
246
|
+
*
|
|
247
|
+
* Holds direction-agnostic settings (ownership, identifier/informational
|
|
248
|
+
* properties, creation and image-sync gates) plus the two
|
|
249
|
+
* {@link DirectionalSection}s that drive the actual data transforms.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* LCSProduct: {
|
|
253
|
+
* // Identifier — used for lookup / create-vs-update routing.
|
|
254
|
+
* getIdentifierProperties: () => ['styleNumber'],
|
|
255
|
+
* // Vibe-owned attributes.
|
|
256
|
+
* vibeOwningKeys: ['lifecycleStage', 'targetIntroDate'],
|
|
257
|
+
* vibe2flex: {
|
|
258
|
+
* transformOrder: [
|
|
259
|
+
* { processor: 'REMOVE', removeKeysKey: 'removeKey' },
|
|
260
|
+
* { processor: 'REKEY', rekeyDelete: true, rekeyKeepEmptyValues: true, rekeyTransformersKey: 'rekey' },
|
|
261
|
+
* ],
|
|
262
|
+
* // Strip Vibe-only / Flex-owned-inbound props before publish.
|
|
263
|
+
* removeKey: ['federatedId'],
|
|
264
|
+
* // destination: source -> flexKey: 'vibeKey'
|
|
265
|
+
* rekey: { productName: 'name' },
|
|
266
|
+
* },
|
|
267
|
+
* flex2vibe: {
|
|
268
|
+
* transformOrder: [
|
|
269
|
+
* { processor: 'REKEY', rekeyDelete: true, rekeyKeepEmptyValues: true, rekeyTransformersKey: 'rekey' },
|
|
270
|
+
* ],
|
|
271
|
+
* // destination: source -> vibeKey: 'flexKey'
|
|
272
|
+
* rekey: { name: 'productName' },
|
|
273
|
+
* },
|
|
274
|
+
* }
|
|
275
|
+
*/
|
|
276
|
+
export interface MappingSection {
|
|
277
|
+
/**
|
|
278
|
+
* Slugs of the VibeIQ properties that VibeIQ owns. During inbound
|
|
279
|
+
* (flex2vibe) processing, any value FlexPLM tries to send for one
|
|
280
|
+
* of these slugs is ignored — the existing VibeIQ value is preserved.
|
|
281
|
+
*
|
|
282
|
+
* Scope: **inbound only**. This list does *not* filter outbound
|
|
283
|
+
* payloads — Vibe-only properties still flow to FlexPLM unless you
|
|
284
|
+
* strip them in {@link DirectionalSection.removeKey} on the
|
|
285
|
+
* `vibe2flex` side.
|
|
286
|
+
*
|
|
287
|
+
* What `vibeOwningKeys` does **not** do — reach for these fields
|
|
288
|
+
* instead:
|
|
289
|
+
* - Strip fields from the outbound payload → use
|
|
290
|
+
* {@link DirectionalSection.removeKey} on `vibe2flex`.
|
|
291
|
+
* - Block inbound entity creation → use
|
|
292
|
+
* {@link MappingSection.isInboundCreatable}.
|
|
293
|
+
* - Drive lookup / matching / dedupe → use
|
|
294
|
+
* {@link MappingSection.getIdentifierProperties}.
|
|
295
|
+
* - Skip value transformations or rekeying → those run regardless;
|
|
296
|
+
* omit the property from `rekey` / `valueTransform` if you want
|
|
297
|
+
* it left alone.
|
|
298
|
+
*
|
|
299
|
+
* `vibeOwningKeys` vs {@link DirectionalSection.removeKey} —
|
|
300
|
+
* different jobs, not redundant:
|
|
301
|
+
* - `vibeOwningKeys` = **refuse to accept on inbound**.
|
|
302
|
+
* - `vibe2flex.removeKey` = **refuse to send on outbound**.
|
|
303
|
+
*
|
|
304
|
+
* A property can belong in one, both, or neither — decide per
|
|
305
|
+
* property based on which system owns it:
|
|
306
|
+
* - `targetIntroDate` (Vibe-owned, no Flex counterpart)
|
|
307
|
+
* → in **both** (protect inbound, don't bother sending).
|
|
308
|
+
* - `materialDescription` (Flex-owned, Vibe stores it after a
|
|
309
|
+
* prior inbound sync) → in **`removeKey` only**, so the value
|
|
310
|
+
* isn't echoed back to Flex; Flex still owns updates inbound.
|
|
311
|
+
* - `event` (Vibe-owned, published to Flex) → in
|
|
312
|
+
* **`vibeOwningKeys` only**; we want to keep sending it.
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* // Right: Vibe-owned attributes;
|
|
316
|
+
* // If Vibe owns the identifier, include it here as well.
|
|
317
|
+
* vibeOwningKeys: ['itemNumber', 'lifecycleStage', 'targetIntroDate']
|
|
318
|
+
*/
|
|
319
|
+
vibeOwningKeys?: string[];
|
|
320
|
+
/**
|
|
321
|
+
* Tag identifying which identity service uniqueness pool this entity participates in.
|
|
322
|
+
* There can only be one entity with a given value for the property(defined by `getIdentifierProperties`) within the same uniqueness pool.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* uniquenessPool: 'item'
|
|
326
|
+
*/
|
|
327
|
+
uniquenessPool?: string;
|
|
328
|
+
/**
|
|
329
|
+
* Gate controlling whether an inbound FlexPLM object is allowed to
|
|
330
|
+
* create a new VibeIQ entity. When the function returns `false`, the
|
|
331
|
+
* inbound row may still update an existing entity but will not create
|
|
332
|
+
* one. Consulted by `TypeConversionUtils.isInboundCreatableFromObject`;
|
|
333
|
+
* defaults to `false` when the function is absent.
|
|
334
|
+
*
|
|
335
|
+
* The function receives the inbound row, so the decision can be
|
|
336
|
+
* per-record (e.g. only allow inbound creation when a status or
|
|
337
|
+
* type-discriminator flag is set). Common patterns:
|
|
338
|
+
* - `() => true` — FlexPLM owns creation for this entity type.
|
|
339
|
+
* - `() => false` — VibeIQ owns creation; inbound rows can only
|
|
340
|
+
* update existing entities.
|
|
341
|
+
* - Conditional — inspect the row and allow / disallow per record.
|
|
342
|
+
*
|
|
343
|
+
* On the **season-link** sections (`LCSProductSeasonLink` /
|
|
344
|
+
* `LCSSKUSeasonLink`), `() => true` enables the add-to-project
|
|
345
|
+
* flow: FlexPLM-driven inbound events can add an item to a Vibe
|
|
346
|
+
* project (not just update an existing project-item).
|
|
347
|
+
* Pair with {@link MappingSection.isOutboundCreatable} returning
|
|
348
|
+
* `false` so the outbound side becomes update-only. This pattern
|
|
349
|
+
* also requires matching FlexPLM-side configuration; see the
|
|
350
|
+
* project documentation for the full setup.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* // Flex owns creation.
|
|
354
|
+
* isInboundCreatable: () => true
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* // Conditional: allow inbound creation only when the row is active
|
|
358
|
+
* // and the type-path matches a class we accept from FlexPLM.
|
|
359
|
+
* isInboundCreatable: (object) => {
|
|
360
|
+
* if (object?.['status']?.value?.toLowerCase?.() !== 'active') return false;
|
|
361
|
+
* return object?.['flexPLMTypePath'] === 'Color\\Solid';
|
|
362
|
+
* }
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* // add-to-project flow — Flex drives project-item creation.
|
|
366
|
+
* // Paired with isOutboundCreatable: () => false to make
|
|
367
|
+
* // outbound update-only.
|
|
368
|
+
* LCSProductSeasonLink: {
|
|
369
|
+
* isInboundCreatable: () => true,
|
|
370
|
+
* isOutboundCreatable: () => false,
|
|
371
|
+
* }
|
|
372
|
+
*/
|
|
373
|
+
isInboundCreatable?: (entity: Record<string, unknown>, context?: unknown) => boolean;
|
|
374
|
+
/**
|
|
375
|
+
* Gate controlling whether an outbound VibeIQ entity is allowed to
|
|
376
|
+
* create a new FlexPLM object. When the function returns `false`, the
|
|
377
|
+
* outbound publish becomes an update-only operation. Consulted by
|
|
378
|
+
* `TypeConversionUtils.isOutboundCreatableFromEntity`; defaults to
|
|
379
|
+
* `true` when the function is absent.
|
|
380
|
+
*
|
|
381
|
+
* On the **season-link** sections (`LCSProductSeasonLink` /
|
|
382
|
+
* `LCSSKUSeasonLink`), `() => false` converts outbound publish events
|
|
383
|
+
* to `UPDATE_ON_SEASON`: they only update an existing season-link,
|
|
384
|
+
* never create one. If the Product / Colorway isn't already on the
|
|
385
|
+
* season, **that specific event fails** while other events continue
|
|
386
|
+
* to process — the failure is per-record, not fatal to the batch.
|
|
387
|
+
*
|
|
388
|
+
* Pair with {@link MappingSection.isInboundCreatable} returning
|
|
389
|
+
* `true` to delegate add-to-project creation to FlexPLM. This pattern
|
|
390
|
+
* also requires matching FlexPLM-side configuration; see the project
|
|
391
|
+
* documentation for the full setup.
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* // add-to-project flow — Vibe never adds items to seasons; Flex does.
|
|
395
|
+
* LCSProductSeasonLink: {
|
|
396
|
+
* isInboundCreatable: () => true,
|
|
397
|
+
* isOutboundCreatable: () => false, // becomes UPDATE_ON_SEASON
|
|
398
|
+
* }
|
|
399
|
+
*/
|
|
400
|
+
isOutboundCreatable?: (entity: Record<string, unknown>, context?: unknown) => boolean;
|
|
401
|
+
/**
|
|
402
|
+
* Gate controlling whether image/thumbnail content should be synced
|
|
403
|
+
* from FlexPLM into VibeIQ for this entity type. Consulted by
|
|
404
|
+
* `TypeConversionUtils.syncInboundImages`; defaults to `false` when
|
|
405
|
+
* the function is absent. Typically aligned with whichever system
|
|
406
|
+
* owns creation of the entity.
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* syncInboundImages: () => true
|
|
410
|
+
*/
|
|
411
|
+
syncInboundImages?: (entity: Record<string, unknown>, context?: unknown) => boolean;
|
|
412
|
+
/**
|
|
413
|
+
* Gate controlling whether image/thumbnail content should be synced
|
|
414
|
+
* from VibeIQ out to FlexPLM for this entity type. Consulted by
|
|
415
|
+
* `TypeConversionUtils.syncOutboundImages`; defaults to `true` when
|
|
416
|
+
* the function is absent. Typically aligned with whichever system
|
|
417
|
+
* owns creation of the entity.
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* syncOutboundImages: () => false
|
|
421
|
+
*/
|
|
422
|
+
syncOutboundImages?: (entity: Record<string, unknown>, context?: unknown) => boolean;
|
|
423
|
+
/**
|
|
424
|
+
* Returns the property names that uniquely identify this entity. Used
|
|
425
|
+
* by `TypeConversionUtils.getIdentifierProperties` to drive lookup and
|
|
426
|
+
* matching when an explicit `flexPLMIdentifierProperties` /
|
|
427
|
+
* `vibeIQIdentifierProperties` is not present on the row, falling
|
|
428
|
+
* back to `TypeDefaults` when this is absent.
|
|
429
|
+
*
|
|
430
|
+
* Identifier properties are the *business key* used to answer
|
|
431
|
+
* "does this inbound payload refer to an existing entity, or do we
|
|
432
|
+
* need to create one?" The connector uses them for entity lookup,
|
|
433
|
+
* create-vs-update routing, and the deterministic targeting of
|
|
434
|
+
* retry / resend events.
|
|
435
|
+
*
|
|
436
|
+
* Inbound lookup outcomes drive routing:
|
|
437
|
+
* - **0 matches** → row is treated as a new entity; creation
|
|
438
|
+
* proceeds only if {@link MappingSection.isInboundCreatable}
|
|
439
|
+
* allows it, otherwise the row is dropped.
|
|
440
|
+
* - **1 match** → existing entity is updated.
|
|
441
|
+
* - **>1 match** → processing fails for that row to avoid
|
|
442
|
+
* ambiguity. Multiple matches means the identifier isn't
|
|
443
|
+
* actually unique; fix the data or pick a tighter key rather
|
|
444
|
+
* than letting the merge happen silently.
|
|
445
|
+
*
|
|
446
|
+
* Multiple slugs form a **composite key** — all of them must match
|
|
447
|
+
* the same existing entity for it to count as the same record. If
|
|
448
|
+
* any value differs, the row is treated as a different entity.
|
|
449
|
+
*
|
|
450
|
+
* Best practices:
|
|
451
|
+
* - Pick the **minimum set** of slugs that uniquely identifies the
|
|
452
|
+
* entity. Multiple slugs are treated as a composite key — all of
|
|
453
|
+
* them must match for the lookup to succeed.
|
|
454
|
+
* - Prefer **stable** business identifiers (item / style / season
|
|
455
|
+
* numbers) over frequently-changing fields. Renames of an
|
|
456
|
+
* identifier are a data-migration event, not a routine update.
|
|
457
|
+
* - Ensure the identifier exists in **both** systems and on
|
|
458
|
+
* historical records. If it's missing on legacy data the lookup
|
|
459
|
+
* will silently create duplicates.
|
|
460
|
+
*
|
|
461
|
+
* When the connector app config sets
|
|
462
|
+
* `search.<entityType>.useIdentityServiceForInboundData: true` for
|
|
463
|
+
* the entity type (e.g. `item`, `color`, `custom-entity`), inbound
|
|
464
|
+
* identifier matching switches from a direct-property query to the
|
|
465
|
+
* platform identity service. This is forward-looking: enable it only
|
|
466
|
+
* after the identifier property has been configured as unique in
|
|
467
|
+
* Vibe and the historical-data backfill is complete. {@link
|
|
468
|
+
* MappingSection.uniquenessPool} declares which identity-service
|
|
469
|
+
* uniqueness pool the entity participates in.
|
|
470
|
+
*
|
|
471
|
+
* @example
|
|
472
|
+
* // Single business key
|
|
473
|
+
* getIdentifierProperties: () => ['itemNumber']
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* // Composite key — both slugs must match for a row to count as the
|
|
477
|
+
* // same entity.
|
|
478
|
+
* getIdentifierProperties: () => ['seasonName', 'year']
|
|
479
|
+
*/
|
|
480
|
+
getIdentifierProperties?: (entity: Record<string, unknown>) => string[];
|
|
481
|
+
/**
|
|
482
|
+
* Returns supplemental property names that should be carried alongside
|
|
483
|
+
* identifiers (e.g. display names) but do not participate in identity.
|
|
484
|
+
* Consulted by `TypeConversionUtils.getInformationalProperties` with
|
|
485
|
+
* the same lookup precedence as {@link MappingSection.getIdentifierProperties}.
|
|
486
|
+
*
|
|
487
|
+
* Purpose is **observability**, not behavior. Informational properties:
|
|
488
|
+
* - ride along in retry / resend event payloads so the retry target
|
|
489
|
+
* is human-readable, not just a numeric ID;
|
|
490
|
+
* - surface in logs, error messages, and monitoring dashboards so
|
|
491
|
+
* failures can be triaged without opening a debugger;
|
|
492
|
+
* - give engineers and ops a way to recognize "which entity is this"
|
|
493
|
+
* at a glance.
|
|
494
|
+
*
|
|
495
|
+
* They are **not** used for entity matching, create-vs-update routing,
|
|
496
|
+
* uniqueness enforcement, or duplicate prevention. If a property
|
|
497
|
+
* needs to influence any of those, put it in
|
|
498
|
+
* {@link MappingSection.getIdentifierProperties} instead.
|
|
499
|
+
*
|
|
500
|
+
* Prefer stable, readable, scalar fields (names, numbers). Avoid
|
|
501
|
+
* large objects, references, or frequently-changing values — they'll
|
|
502
|
+
* bloat retry payloads and clutter logs without adding clarity.
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* // SKU: matched by colorwaySeqID, but log lines show the itemNumber.
|
|
506
|
+
* // "Failed to upsert SKU — colorwaySeqID=12345 itemNumber=NB-574-BLU"
|
|
507
|
+
* LCSSKU: {
|
|
508
|
+
* getIdentifierProperties: () => ['colorwaySeqID'],
|
|
509
|
+
* getInformationalProperties: () => ['itemNumber'],
|
|
510
|
+
* }
|
|
511
|
+
*
|
|
512
|
+
* @example
|
|
513
|
+
* // Multiple informational properties — all carried together; none
|
|
514
|
+
* // affect matching.
|
|
515
|
+
* getInformationalProperties: () => ['itemNumber', 'skuName']
|
|
516
|
+
*/
|
|
517
|
+
getInformationalProperties?: (entity: Record<string, unknown>) => string[];
|
|
518
|
+
/**
|
|
519
|
+
* Transform configuration applied when sending data from VibeIQ to FlexPLM.
|
|
520
|
+
* Pipeline runs on rows in their VibeIQ shape and produces rows in their
|
|
521
|
+
* FlexPLM shape, so inside `rekey` the destination (left) is the FlexPLM
|
|
522
|
+
* key and the source (right) is the VibeIQ key — see
|
|
523
|
+
* {@link DirectionalSection.rekey}.
|
|
524
|
+
*
|
|
525
|
+
* Responsibilities:
|
|
526
|
+
* - Rename Vibe attribute slugs to Flex field names (`rekey`).
|
|
527
|
+
* - Remove Vibe-only attributes from the payload
|
|
528
|
+
* ({@link DirectionalSection.removeKey}).
|
|
529
|
+
* - Convert / coerce values to formats FlexPLM accepts
|
|
530
|
+
* ({@link DirectionalSection.valueTransform},
|
|
531
|
+
* {@link DirectionalSection.morphTransform}).
|
|
532
|
+
* - Produce a Flex-valid payload ready for publish.
|
|
533
|
+
*
|
|
534
|
+
* Note the asymmetry with `flex2vibe`: the inbound ownership and
|
|
535
|
+
* creation gates ({@link MappingSection.vibeOwningKeys},
|
|
536
|
+
* {@link MappingSection.isInboundCreatable}) do **not** apply here.
|
|
537
|
+
* `vibe2flex` is the side that *sends* Vibe data out — protecting
|
|
538
|
+
* Vibe data from being overwritten only makes sense on the inbound
|
|
539
|
+
* side. To keep Vibe-only fields from leaking outbound, list them in
|
|
540
|
+
* `removeKey`.
|
|
541
|
+
*/
|
|
542
|
+
vibe2flex?: DirectionalSection;
|
|
543
|
+
/**
|
|
544
|
+
* Transform configuration applied when receiving data from FlexPLM into VibeIQ.
|
|
545
|
+
* Pipeline runs on rows in their FlexPLM shape and produces rows in their
|
|
546
|
+
* VibeIQ shape, so inside `rekey` the destination (left) is the VibeIQ
|
|
547
|
+
* key and the source (right) is the FlexPLM key — see
|
|
548
|
+
* {@link DirectionalSection.rekey}.
|
|
549
|
+
*
|
|
550
|
+
* Responsibilities:
|
|
551
|
+
* - Rename Flex field names to Vibe attribute slugs (`rekey`).
|
|
552
|
+
* - Normalize / coerce inbound values into Vibe-compatible formats
|
|
553
|
+
* ({@link DirectionalSection.valueTransform},
|
|
554
|
+
* {@link DirectionalSection.morphTransform}).
|
|
555
|
+
* - Produce a row ready to hand to `setEntityValues()` /
|
|
556
|
+
* inbound persistence.
|
|
557
|
+
*
|
|
558
|
+
* This is the direction where the protective gates fire:
|
|
559
|
+
* - {@link MappingSection.vibeOwningKeys} prevents inbound rows
|
|
560
|
+
* from overwriting Vibe-owned attributes.
|
|
561
|
+
* - {@link MappingSection.isInboundCreatable} decides whether an
|
|
562
|
+
* inbound row whose identifier matches nothing should create a
|
|
563
|
+
* new Vibe entity or be dropped.
|
|
564
|
+
*/
|
|
565
|
+
flex2vibe?: DirectionalSection;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Entry in the {@link TypeConversionSection} that maps a single source
|
|
569
|
+
* type onto the {@link MappingSection} key that should handle it.
|
|
570
|
+
*
|
|
571
|
+
* Useful for fanning a single VibeIQ `entityType` (e.g. `'custom-entity'`)
|
|
572
|
+
* out to multiple sections based on the row's `typePath`.
|
|
573
|
+
*/
|
|
574
|
+
export interface TypeConversionEntry {
|
|
575
|
+
/**
|
|
576
|
+
* Returns the {@link MappingFile} key whose {@link MappingSection}
|
|
577
|
+
* should be used to transform `entity`. Returning an empty string
|
|
578
|
+
* signals that no section applies and the row should be skipped.
|
|
579
|
+
*
|
|
580
|
+
* @example
|
|
581
|
+
* getMapKey: (entity) => {
|
|
582
|
+
* switch (entity['typePath']) {
|
|
583
|
+
* case 'custom-entity:exchangeRate': return 'Exchange Rate';
|
|
584
|
+
* case 'custom-entity:styleConcept': return 'Style Concept Master';
|
|
585
|
+
* default: return '';
|
|
586
|
+
* }
|
|
587
|
+
* }
|
|
588
|
+
*/
|
|
589
|
+
getMapKey: (entity: Record<string, unknown>) => string;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* One direction of {@link TypeConversion}, keyed by the source-system
|
|
593
|
+
* type:
|
|
594
|
+
* - In `vibe2flex`, keys are VibeIQ entity types (e.g. `'custom-entity'`,
|
|
595
|
+
* `'size-range-template'`) — see `TypeConversionUtils.getEntityType`.
|
|
596
|
+
* - In `flex2vibe`, keys are FlexPLM object classes (e.g. `LCSProduct`) —
|
|
597
|
+
* see `TypeConversionUtils.getObjectType`.
|
|
598
|
+
*/
|
|
599
|
+
export interface TypeConversionSection {
|
|
600
|
+
[type: string]: TypeConversionEntry;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Top-level routing table that resolves a row to its
|
|
604
|
+
* {@link MappingSection} key for each direction.
|
|
605
|
+
*
|
|
606
|
+
* `TypeConversionUtils.getMapKey` / `getMapKeyFromObject` look up the
|
|
607
|
+
* row's type in the appropriate side, invoke
|
|
608
|
+
* {@link TypeConversionEntry.getMapKey}, and use the returned string
|
|
609
|
+
* as the key into the rest of the {@link MappingFile}.
|
|
610
|
+
*
|
|
611
|
+
* @example
|
|
612
|
+
* typeConversion: {
|
|
613
|
+
* vibe2flex: {
|
|
614
|
+
* 'custom-entity': {
|
|
615
|
+
* getMapKey: (entity) => {
|
|
616
|
+
* switch (entity['typePath']) {
|
|
617
|
+
* case 'custom-entity:exchangeRate': return 'Exchange Rate';
|
|
618
|
+
* default: return '';
|
|
619
|
+
* }
|
|
620
|
+
* },
|
|
621
|
+
* },
|
|
622
|
+
* 'size-range-template': {
|
|
623
|
+
* getMapKey: () => 'size-range-template',
|
|
624
|
+
* },
|
|
625
|
+
* },
|
|
626
|
+
* flex2vibe: {
|
|
627
|
+
* LCSRevisableEntity: {
|
|
628
|
+
* getMapKey: (object) => {
|
|
629
|
+
* switch (object['flexPLMTypePath']) {
|
|
630
|
+
* case 'Revisable Entity\\Exchange Rate': return 'Exchange Rate';
|
|
631
|
+
* default: return '';
|
|
632
|
+
* }
|
|
633
|
+
* },
|
|
634
|
+
* },
|
|
635
|
+
* },
|
|
636
|
+
* }
|
|
637
|
+
*/
|
|
638
|
+
export interface TypeConversion {
|
|
639
|
+
/**
|
|
640
|
+
* Routes outbound VibeIQ entities to their FlexPLM mapping sections.
|
|
641
|
+
*
|
|
642
|
+
* When no entry matches the row's VibeIQ entity type, the connector
|
|
643
|
+
* falls back to looking up a section by the row's `TypeDefaults`
|
|
644
|
+
* class. The explicit `vibe2flex` entries here override that fallback
|
|
645
|
+
* and are only needed when a single VibeIQ type fans out to multiple
|
|
646
|
+
* mapping sections (e.g. `'custom-entity'` dispatched by `typePath`).
|
|
647
|
+
*/
|
|
648
|
+
vibe2flex: TypeConversionSection;
|
|
649
|
+
/**
|
|
650
|
+
* Routes inbound FlexPLM objects to their VibeIQ mapping sections.
|
|
651
|
+
*
|
|
652
|
+
* When no entry matches the row's FlexPLM object class, the connector
|
|
653
|
+
* falls back to using the row's `flexPLMObjectClass` directly as the
|
|
654
|
+
* section key. Explicit `flex2vibe` entries are only needed when one
|
|
655
|
+
* FlexPLM class fans out to multiple mapping sections (e.g.
|
|
656
|
+
* `LCSLifecycleManaged` dispatched by `flexPLMTypePath`).
|
|
657
|
+
*
|
|
658
|
+
* `LCSMaterial` routing — special case: by default `LCSMaterial`
|
|
659
|
+
* maps to `custom-entity` on the VibeIQ side. To route it to
|
|
660
|
+
* `item:material` (treating materials as a subtype of `item`)
|
|
661
|
+
* instead, **both** of the following must be in place:
|
|
662
|
+
* 1. Connector app config sets
|
|
663
|
+
* `{ "LCSMaterial": { "processAsItem": true } }`. The mapping
|
|
664
|
+
* file alone can't change which entity type LCSMaterial maps to.
|
|
665
|
+
* 2. `flex2vibe.LCSMaterial.getMapKey` returns the name of a custom
|
|
666
|
+
* mapping section that declares `getClass: () => 'item'` +
|
|
667
|
+
* `getSoftType: () => 'item:material'` on its `flex2vibe` side.
|
|
668
|
+
* The matching `vibe2flex.item.getMapKey` should route
|
|
669
|
+
* `item:material` typePath back to the same section.
|
|
670
|
+
*/
|
|
671
|
+
flex2vibe: TypeConversionSection;
|
|
672
|
+
}
|
|
673
|
+
/** Identifies the application and tenant that owns a {@link MappingFile}. */
|
|
674
|
+
export interface OrgInfo {
|
|
675
|
+
/** This is the identifier for the application, and is used to set the owner of the mapping file */
|
|
676
|
+
appIdentifier: '@vibeiq/flexplm-connector';
|
|
677
|
+
/** The name of the organization using the mapping file. */
|
|
678
|
+
orgName: string;
|
|
679
|
+
}
|
|
680
|
+
interface MappingFileBase {
|
|
681
|
+
/** The information about the organization using the mapping file */
|
|
682
|
+
orgInfo: OrgInfo;
|
|
683
|
+
/** The type conversion information for the mapping file. */
|
|
684
|
+
typeConversion: TypeConversion;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Full mapping file driving bidirectional sync between VibeIQ and FlexPLM
|
|
688
|
+
* for a single tenant.
|
|
689
|
+
*
|
|
690
|
+
* Combines the fixed {@link MappingFileBase} fields (`orgInfo`,
|
|
691
|
+
* `typeConversion`) with an open set of {@link MappingSection} entries
|
|
692
|
+
* keyed by the strings returned from
|
|
693
|
+
* {@link TypeConversionEntry.getMapKey} (e.g. `LCSProduct`, `LCSSKU`,
|
|
694
|
+
* `'Exchange Rate'`).
|
|
695
|
+
*
|
|
696
|
+
* # Supported entity mappings
|
|
697
|
+
*
|
|
698
|
+
* The tables below describe which VibeIQ entity types and FlexPLM object
|
|
699
|
+
* classes can be synced today and what each direction does behind the
|
|
700
|
+
* scenes. "Not yet supported" entries are listed explicitly so authors
|
|
701
|
+
* know not to attempt them.
|
|
702
|
+
*
|
|
703
|
+
* ## VibeIQ → FlexPLM
|
|
704
|
+
*
|
|
705
|
+
* | VibeIQ entity type | FlexPLM object class | Behavior |
|
|
706
|
+
* |----------------------|----------------------------------|------------------------------------------------------------------------------------------------------------|
|
|
707
|
+
* | `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
|
+
* | `color` | `LCSColor` | Syncs on create / update. Sends Primary Viewable as the thumbnail. Creates `LCSColor` if missing. |
|
|
709
|
+
* | `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. |
|
|
710
|
+
* | `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
|
+
* | `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
|
+
* | `custom-entity` | `LCSLast` / `LCSLifecycleManaged` | **Not yet supported.** |
|
|
713
|
+
*
|
|
714
|
+
* ## FlexPLM → VibeIQ
|
|
715
|
+
*
|
|
716
|
+
* | FlexPLM object class | VibeIQ entity type | Behavior |
|
|
717
|
+
* |-------------------------|----------------------------|------------------------------------------------------------------------------------------------------------|
|
|
718
|
+
* | `LCSProduct` | `item` (role: `family`) | Configurable via mapping file to create new items; updates existing items if found. Does **not** sync the thumbnail to VibeIQ. |
|
|
719
|
+
* | `LCSSKU` | `item` (role: `option`) | Configurable to create new items; updates existing items. Does **not** sync the thumbnail. |
|
|
720
|
+
* | `LCSProductSeasonLink` | `project-item` (`family`) | Updates existing items if found. Can be configured to add an item to a project, including setting carried-over data — see the add-to-project flow under {@link MappingSection.isInboundCreatable}. |
|
|
721
|
+
* | `LCSSKUSeasonLink` | `project-item` (`option`) | Updates existing items. Add-to-project supported under the same flow. |
|
|
722
|
+
* | `LCSColor` | `color` | Configurable to create new colors; updates existing colors. Does **not** sync the thumbnail. |
|
|
723
|
+
* | `LCSLast` | `custom-entity` | Configurable to create new custom-entities; updates existing. |
|
|
724
|
+
* | `LCSLifecycleManaged` | `custom-entity` | Configurable to create new custom-entities; updates existing. |
|
|
725
|
+
* | `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
|
+
* | `LCSRevisableEntity` | `custom-entity` | Configurable to create new custom-entities; updates existing. |
|
|
727
|
+
* | (any) | `item` (direct, no link) | **Not yet supported.** |
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
* export const mapping: MappingFile = {
|
|
731
|
+
* orgInfo: {
|
|
732
|
+
* appIdentifier: '@vibeiq/flexplm-connector',
|
|
733
|
+
* orgName: 'acme',
|
|
734
|
+
* },
|
|
735
|
+
* typeConversion: {
|
|
736
|
+
* vibe2flex: {
|
|
737
|
+
* 'custom-entity': {
|
|
738
|
+
* getMapKey: (entity) => {
|
|
739
|
+
* switch (entity['typePath']) {
|
|
740
|
+
* case 'custom-entity:exchangeRate': return 'Exchange Rate';
|
|
741
|
+
* default: return '';
|
|
742
|
+
* }
|
|
743
|
+
* },
|
|
744
|
+
* },
|
|
745
|
+
* 'size-range-template': {
|
|
746
|
+
* getMapKey: () => 'size-range-template',
|
|
747
|
+
* },
|
|
748
|
+
* },
|
|
749
|
+
* flex2vibe: {
|
|
750
|
+
* LCSRevisableEntity: {
|
|
751
|
+
* getMapKey: (object) => {
|
|
752
|
+
* switch (object['flexPLMTypePath']) {
|
|
753
|
+
* case 'Revisable Entity\\Exchange Rate': return 'Exchange Rate';
|
|
754
|
+
* default: return '';
|
|
755
|
+
* }
|
|
756
|
+
* },
|
|
757
|
+
* },
|
|
758
|
+
* },
|
|
759
|
+
* },
|
|
760
|
+
* 'Exchange Rate': {
|
|
761
|
+
* vibe2flex: {
|
|
762
|
+
* transformOrder: [
|
|
763
|
+
* { processor: 'REKEY', rekeyDelete: true, rekeyKeepEmptyValues: true, rekeyTransformersKey: 'rekey' },
|
|
764
|
+
* ],
|
|
765
|
+
* rekey: { exchangeRateDescription: 'name' },
|
|
766
|
+
* getClass: () => 'LCSRevisableEntity',
|
|
767
|
+
* getSoftType: () => 'Revisable Entity\\Exchange Rate',
|
|
768
|
+
* },
|
|
769
|
+
* flex2vibe: {
|
|
770
|
+
* transformOrder: [
|
|
771
|
+
* { processor: 'REKEY', rekeyDelete: true, rekeyKeepEmptyValues: true, rekeyTransformersKey: 'rekey' },
|
|
772
|
+
* ],
|
|
773
|
+
* rekey: { name: 'exchangeRateDescription' },
|
|
774
|
+
* getClass: () => 'custom-entity',
|
|
775
|
+
* getSoftType: () => 'custom-entity:exchangeRate',
|
|
776
|
+
* },
|
|
777
|
+
* },
|
|
778
|
+
* };
|
|
779
|
+
*/
|
|
780
|
+
export type MappingFile = MappingFileBase & {
|
|
781
|
+
[mapKey: string]: MappingSection | MappingFileBase[keyof MappingFileBase];
|
|
782
|
+
};
|
|
783
|
+
export {};
|