@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.
- 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 +460 -0
- package/lib/interfaces/mapping-file.js +2 -0
- package/lib/publish/base-process-publish-assortment.d.ts +25 -0
- package/lib/publish/base-process-publish-assortment.js +60 -6
- package/lib/publish/base-process-publish-assortment.spec.js +22 -4
- 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 +37 -1
- package/lib/transform/identifier-conversion.spec.js +35 -0
- package/lib/util/config-defaults.d.ts +18 -0
- package/lib/util/config-defaults.js +25 -15
- package/lib/util/config-defaults.spec.js +56 -0
- package/lib/util/data-converter-spec-mockData.js +17 -3
- package/lib/util/data-converter.d.ts +102 -0
- package/lib/util/data-converter.js +195 -34
- package/lib/util/data-converter.spec.js +430 -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 +69 -0
- package/lib/util/type-defaults.js +98 -4
- package/lib/util/type-defaults.spec.js +114 -4
- 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 +21 -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 -40
- 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 -1992
- package/src/publish/base-process-publish-assortment.ts +0 -1134
- 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 -354
- package/src/transform/identifier-conversion.ts +0 -282
- package/src/util/config-defaults.spec.ts +0 -392
- package/src/util/config-defaults.ts +0 -97
- package/src/util/data-converter-spec-mockData.ts +0 -231
- package/src/util/data-converter.spec.ts +0 -1120
- package/src/util/data-converter.ts +0 -766
- 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 -669
- package/src/util/type-defaults.ts +0 -281
- 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,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 {};
|
|
@@ -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) {
|