@contrail/flexplm 1.1.13 → 1.1.15
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/.github/pull_request_template.md +30 -0
- package/.github/workflows/flexplm-lib.yml +27 -0
- package/lib/flexplm-utils.spec.d.ts +1 -0
- package/lib/flexplm-utils.spec.js +26 -0
- package/lib/publish/base-process-publish-assortment.spec.d.ts +1 -0
- package/lib/publish/base-process-publish-assortment.spec.js +1053 -0
- package/lib/util/config-defaults.spec.d.ts +1 -0
- package/lib/util/config-defaults.spec.js +264 -0
- package/lib/util/data-converter.d.ts +4 -0
- package/lib/util/data-converter.js +88 -1
- package/lib/util/data-converter.spec.d.ts +1 -0
- package/lib/util/data-converter.spec.js +591 -0
- package/lib/util/map-utils.d.ts +2 -2
- package/lib/util/map-utils.js +5 -28
- package/lib/util/map-utils.spec.d.ts +1 -0
- package/lib/util/map-utils.spec.js +89 -0
- package/lib/util/thumbnail-util.spec.d.ts +1 -0
- package/lib/util/thumbnail-util.spec.js +132 -0
- package/lib/util/type-conversion-utils-spec-mockData.js +21 -0
- package/lib/util/type-conversion-utils.d.ts +7 -0
- package/lib/util/type-conversion-utils.js +88 -4
- package/lib/util/type-conversion-utils.spec.d.ts +1 -0
- package/lib/util/type-conversion-utils.spec.js +547 -0
- package/lib/util/type-defaults.d.ts +5 -0
- package/lib/util/type-defaults.js +66 -0
- package/lib/util/type-defaults.spec.d.ts +1 -0
- package/lib/util/type-defaults.spec.js +460 -0
- package/lib/util/type-utils.spec.d.ts +1 -0
- package/lib/util/type-utils.spec.js +190 -0
- package/package.json +2 -2
- package/publish.bat +5 -0
- package/publish.sh +5 -0
- package/src/entity-processor/base-entity-processor.ts +183 -0
- package/src/flexplm-request.ts +28 -0
- package/src/flexplm-utils.spec.ts +27 -0
- package/src/flexplm-utils.ts +29 -0
- package/src/index.ts +20 -0
- package/src/interfaces/interfaces.ts +120 -0
- package/src/interfaces/item-family-changes.ts +67 -0
- package/src/interfaces/publish-change-data.ts +43 -0
- package/src/publish/base-process-publish-assortment-callback.ts +23 -0
- package/src/publish/base-process-publish-assortment.spec.ts +1239 -0
- package/src/publish/base-process-publish-assortment.ts +1024 -0
- package/src/publish/mockData.ts +4561 -0
- package/src/transform/identifier-conversion.ts +226 -0
- package/src/util/config-defaults.spec.ts +315 -0
- package/src/util/config-defaults.ts +79 -0
- package/src/util/data-converter-spec-mockData.ts +231 -0
- package/src/util/data-converter.spec.ts +872 -0
- package/src/util/data-converter.ts +507 -0
- package/src/util/federation.ts +172 -0
- package/src/util/flexplm-connect.ts +169 -0
- package/src/util/logger-config.ts +20 -0
- package/src/util/map-util-spec-mockData.ts +231 -0
- package/src/util/map-utils.spec.ts +103 -0
- package/src/util/map-utils.ts +40 -0
- package/src/util/mockData.ts +98 -0
- package/src/util/thumbnail-util.spec.ts +152 -0
- package/src/util/thumbnail-util.ts +128 -0
- package/src/util/type-conversion-utils-spec-mockData.ts +238 -0
- package/src/util/type-conversion-utils.spec.ts +601 -0
- package/src/util/type-conversion-utils.ts +345 -0
- package/src/util/type-defaults.spec.ts +592 -0
- package/src/util/type-defaults.ts +261 -0
- package/src/util/type-utils.spec.ts +227 -0
- package/src/util/type-utils.ts +124 -0
- package/tsconfig.json +27 -0
- package/tslint.json +57 -0
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
import { Entities, Files, Request } from '@contrail/sdk';
|
|
2
|
+
import { AsyncEventsPayloadType, FCConfig, ProductFederation, SeasonalPayload, SeasonFederation, SkuFederation } from '../interfaces/interfaces';
|
|
3
|
+
import { DataConverter } from '../util/data-converter';
|
|
4
|
+
import { FlexPLMConnect } from '../util/flexplm-connect';
|
|
5
|
+
import { ItemFamilyChanges } from '../interfaces/item-family-changes';
|
|
6
|
+
import { PublishChangeData } from '../interfaces/publish-change-data';
|
|
7
|
+
import { MapUtil } from '../util/map-utils';
|
|
8
|
+
|
|
9
|
+
import fsPromise = require('fs/promises');
|
|
10
|
+
import path = require('path');
|
|
11
|
+
import { MapFileUtil } from '@contrail/transform-data';
|
|
12
|
+
import { Logger } from '@contrail/app-framework';
|
|
13
|
+
import { TypeConversionUtils } from '../util/type-conversion-utils';
|
|
14
|
+
|
|
15
|
+
export class BaseProcessPublishAssortment {
|
|
16
|
+
|
|
17
|
+
private TTL = 7 * 24 * 60 * 60 * 1_000; // 1 week
|
|
18
|
+
static ASSORTMENT_NOT_PUBLISHABLE = 'Assortment isn\'t marked for publishing to FlexPLM';
|
|
19
|
+
static ASSORTMENT_NO_FED_INFO = 'Assortment doesn\'t have all "identifier" properties or a federated id, so there is no processing. Identifier properties: ';
|
|
20
|
+
static NOT_ABLE_TO_PROCESS_DELETE_CHANGES = 'Error: Not able to process delete changes';
|
|
21
|
+
private dc: DataConverter;
|
|
22
|
+
private config: FCConfig;
|
|
23
|
+
private mapFileUtil: MapFileUtil;
|
|
24
|
+
private transformMapFile: string;
|
|
25
|
+
private cache = {
|
|
26
|
+
carriedFromSeason: {}
|
|
27
|
+
};
|
|
28
|
+
constructor(_config: FCConfig, _dc: DataConverter, _mapFileUtil: MapFileUtil) {
|
|
29
|
+
this.config = _config;
|
|
30
|
+
this.dc = _dc;
|
|
31
|
+
this.mapFileUtil = _mapFileUtil;
|
|
32
|
+
this.transformMapFile = this.config['transformMapFile'];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public async process(event) {
|
|
36
|
+
try {
|
|
37
|
+
console.info('process-start!');
|
|
38
|
+
const assortmentId = event.assortmentId;
|
|
39
|
+
let seasonFed: SeasonFederation;
|
|
40
|
+
try {
|
|
41
|
+
seasonFed = await this.getSeasonFederation(assortmentId);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
const message = e.message;
|
|
44
|
+
console.log(message);
|
|
45
|
+
const output = {
|
|
46
|
+
results: { message },
|
|
47
|
+
skip_await: true
|
|
48
|
+
};
|
|
49
|
+
return output;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const assortmentPublishChangeId = event.assortmentPublishChangeId;
|
|
53
|
+
const apcHistory = await this.getApcHistory(assortmentId);
|
|
54
|
+
const sinceDate = await this.getSinceDate(assortmentId, assortmentPublishChangeId, apcHistory);
|
|
55
|
+
|
|
56
|
+
//Get detail information
|
|
57
|
+
const assortmentPublishChange = await this.downloadAssortmentPublishChange(assortmentId, assortmentPublishChangeId);
|
|
58
|
+
|
|
59
|
+
const changeDetail = await this.downloadHydratedChangeDetail(assortmentPublishChange);
|
|
60
|
+
|
|
61
|
+
const assortmentBaseline = await this.downloadAssortmentBaseline(assortmentPublishChange);
|
|
62
|
+
|
|
63
|
+
const deleteChanges = await this.getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate);
|
|
64
|
+
|
|
65
|
+
const releasedForDevelopmentItemIds = this.getReleasedForDevelopmentItemAndFamilyIds(assortmentBaseline, deleteChanges);
|
|
66
|
+
const itemToFederatedIdMapping = await this.getItemFederatedIds(/*allItemIds*/);
|
|
67
|
+
|
|
68
|
+
const publisher = this.getPublisher(assortmentPublishChange);
|
|
69
|
+
const pcd = new PublishChangeData(assortmentId, seasonFed, assortmentPublishChangeId, sinceDate, publisher);
|
|
70
|
+
pcd.itemToFederatedIdMapping = itemToFederatedIdMapping;
|
|
71
|
+
|
|
72
|
+
pcd.releasedForDevelopmentItemIds = releasedForDevelopmentItemIds;
|
|
73
|
+
|
|
74
|
+
const output = await this.processPublish(pcd, changeDetail, assortmentBaseline, deleteChanges);
|
|
75
|
+
console.info('process-end');
|
|
76
|
+
|
|
77
|
+
return output;
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.log('catch e: ' + e.message);
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getSeasonFederation(assortmentId): Promise<SeasonFederation> {
|
|
85
|
+
let assortment;
|
|
86
|
+
try {
|
|
87
|
+
assortment = await new Entities().get({
|
|
88
|
+
entityName: 'assortment',
|
|
89
|
+
id: assortmentId
|
|
90
|
+
});
|
|
91
|
+
console.info('assortment-name: ' + assortment['name']);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.warn(`No Assortment found for id: ${assortmentId}`);
|
|
94
|
+
}
|
|
95
|
+
const publishToFlexPLM = assortment?.publishToFlexPLM;
|
|
96
|
+
if (!publishToFlexPLM) {
|
|
97
|
+
throw new Error(BaseProcessPublishAssortment.ASSORTMENT_NOT_PUBLISHABLE);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const assortmentObj = await this.dc.getFlexPLMObjectData(assortment, [], true);
|
|
101
|
+
let seasonObj: any = {
|
|
102
|
+
entityReference: 'assortment:' + assortmentId,
|
|
103
|
+
objectClass: 'LCSSeason' as const,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const identifierKeys: string[] = this.config?.identifierAtts?.LCSSeason || [];
|
|
107
|
+
for (const key of identifierKeys) {
|
|
108
|
+
if (assortmentObj[key]) {
|
|
109
|
+
seasonObj[key] = assortmentObj[key];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const objectKeys = Object.keys(seasonObj);
|
|
113
|
+
const hasAllIdentifiers = identifierKeys.every(key => objectKeys.includes(key));
|
|
114
|
+
|
|
115
|
+
if (!hasAllIdentifiers && !seasonObj?.federationId) {
|
|
116
|
+
throw new Error(BaseProcessPublishAssortment.ASSORTMENT_NO_FED_INFO + identifierKeys);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
seasonObj = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonObj, 'LCSSeason', 'vibe2flex');
|
|
120
|
+
return seasonObj as SeasonFederation;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public async getSinceDate(assortmentId: any, assortmentPublishChangeId: any, apcHistory: object[]): Promise<Date> {
|
|
124
|
+
|
|
125
|
+
let sinceDate = null;
|
|
126
|
+
|
|
127
|
+
let pastPublishCount = 1;
|
|
128
|
+
if (this.config['sinceDate']) {
|
|
129
|
+
const sinceDateString = '' + this.config['sinceDate'];
|
|
130
|
+
if(sinceDateString && sinceDateString.indexOf('-') > 0){
|
|
131
|
+
const sinceDateParts = sinceDateString.split('-');
|
|
132
|
+
if(sinceDateParts.length == 3){
|
|
133
|
+
const year = parseInt(sinceDateParts[0]);
|
|
134
|
+
const month = parseInt(sinceDateParts[1]) - 1;
|
|
135
|
+
const day = parseInt(sinceDateParts[2]);
|
|
136
|
+
const minutes = 0 - new Date().getTimezoneOffset();
|
|
137
|
+
sinceDate = this.getSinceDateFromAPCSpecificDate(apcHistory, assortmentPublishChangeId, new Date(year, month, day, 0, minutes, 0));
|
|
138
|
+
}
|
|
139
|
+
} else if(sinceDateString && sinceDateString.indexOf(':') > 0){
|
|
140
|
+
const sinceDateParts = sinceDateString.split(':');
|
|
141
|
+
const type = sinceDateParts[0];
|
|
142
|
+
const count = parseInt(sinceDateParts[1]);
|
|
143
|
+
if('numberofdays' == type.toLocaleLowerCase()){
|
|
144
|
+
sinceDate = this.getSinceDateDaysPrevious(apcHistory, assortmentPublishChangeId, count);
|
|
145
|
+
} else if('numberofpublishes' == type.toLocaleLowerCase()){
|
|
146
|
+
pastPublishCount = count;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if(sinceDate == null) {
|
|
153
|
+
sinceDate = this.getSinceDateFromAPCs(apcHistory, assortmentPublishChangeId, pastPublishCount);
|
|
154
|
+
}
|
|
155
|
+
if(sinceDate == null){
|
|
156
|
+
throw new Error('Couldnt set sinceDate');
|
|
157
|
+
}
|
|
158
|
+
console.info('getSinceDate: ' + sinceDate);
|
|
159
|
+
return sinceDate;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async getApcHistory(assortmentId: any) {
|
|
163
|
+
const resource = `/assortments/${assortmentId}/history/`;
|
|
164
|
+
return Request.request(resource, {
|
|
165
|
+
method: 'GET',
|
|
166
|
+
headers: {
|
|
167
|
+
Accept: 'application/json',
|
|
168
|
+
'Content-Type': 'application/json'
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected updatedSinceDate(entity, sinceDate: Date): boolean {
|
|
174
|
+
const updatedOn = entity?.updatedOn;
|
|
175
|
+
return (updatedOn && new Date(updatedOn) > sinceDate);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
getSinceDateFromAPCSpecificDate(apcHistory: object[], assortmentPublishChangeId: string, specificDate: Date):Date {
|
|
179
|
+
const apc = apcHistory.find(apc => apc['id'] === assortmentPublishChangeId);
|
|
180
|
+
const apcDate = new Date(apc['createdOn']);
|
|
181
|
+
const dateArray = [];
|
|
182
|
+
for (const tempapc of apcHistory) {
|
|
183
|
+
if (assortmentPublishChangeId !== tempapc['id']) {
|
|
184
|
+
const tempDate = new Date(tempapc['createdOn']);
|
|
185
|
+
if (tempDate < apcDate) {
|
|
186
|
+
dateArray.push(tempDate);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
dateArray.sort((a,b) => { return Date.parse(a) - Date.parse(b);});
|
|
192
|
+
|
|
193
|
+
let sinceDate = specificDate;
|
|
194
|
+
|
|
195
|
+
const sinceDateMilliseconds = sinceDate.getTime();
|
|
196
|
+
for(const d of dateArray){
|
|
197
|
+
const milli = Date.parse(d);
|
|
198
|
+
if(milli >= sinceDateMilliseconds){
|
|
199
|
+
sinceDate = d;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return sinceDate;
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
getSinceDateDaysPrevious(apcHistory: object[], assortmentPublishChangeId: string, daysBack):Date {
|
|
207
|
+
const apc = apcHistory.find(apc => apc['id'] === assortmentPublishChangeId);
|
|
208
|
+
const apcDate = new Date(apc['createdOn']);
|
|
209
|
+
const dateArray = [];
|
|
210
|
+
for (const tempapc of apcHistory) {
|
|
211
|
+
if (assortmentPublishChangeId !== tempapc['id']) {
|
|
212
|
+
const tempDate = new Date(tempapc['createdOn']);
|
|
213
|
+
if (tempDate < apcDate) {
|
|
214
|
+
dateArray.push(tempDate);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
dateArray.sort((a,b) => { return Date.parse(a) - Date.parse(b);});
|
|
220
|
+
|
|
221
|
+
let sinceDate = new Date(
|
|
222
|
+
apcDate.getUTCFullYear(),
|
|
223
|
+
apcDate.getUTCMonth(),
|
|
224
|
+
apcDate.getUTCDate()-daysBack,
|
|
225
|
+
0,
|
|
226
|
+
(0 - apcDate.getTimezoneOffset()),
|
|
227
|
+
0);
|
|
228
|
+
|
|
229
|
+
const sinceDateMilliseconds = sinceDate.getTime();
|
|
230
|
+
for(const d of dateArray){
|
|
231
|
+
const milli = Date.parse(d);
|
|
232
|
+
if(milli >= sinceDateMilliseconds){
|
|
233
|
+
sinceDate = d;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return sinceDate;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
getSinceDateFromAPCs(apcHistory: any[], assortmentPublishChangeId: string, pastPublishCount = 1): Date {
|
|
241
|
+
if(apcHistory.length === 1){
|
|
242
|
+
return new Date(0);
|
|
243
|
+
}
|
|
244
|
+
const apcIndex = apcHistory.findIndex(apc => apc.id === assortmentPublishChangeId);
|
|
245
|
+
if(apcIndex === 0){
|
|
246
|
+
return new Date(0);
|
|
247
|
+
}
|
|
248
|
+
const apc = apcHistory[apcIndex];
|
|
249
|
+
const apcDate = new Date(apc['createdOn']);
|
|
250
|
+
let sinceDate = new Date(0);
|
|
251
|
+
const dateArray = [];
|
|
252
|
+
for (const tempapc of apcHistory) {
|
|
253
|
+
if (assortmentPublishChangeId !== tempapc['id']) {
|
|
254
|
+
const tempDate = new Date(tempapc['createdOn']);
|
|
255
|
+
if (tempDate < apcDate) {
|
|
256
|
+
dateArray.push(tempDate);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
dateArray.sort((a,b) => { return Date.parse(a) - Date.parse(b);});
|
|
261
|
+
const arrIndex = (dateArray.length - pastPublishCount > 0)? dateArray.length - pastPublishCount: 0;
|
|
262
|
+
sinceDate = dateArray[arrIndex];
|
|
263
|
+
return sinceDate;
|
|
264
|
+
}
|
|
265
|
+
protected getPublisher(assortmentPublishChange) {
|
|
266
|
+
const createdBy = assortmentPublishChange?.createdBy;
|
|
267
|
+
const publisher = {};
|
|
268
|
+
const includeKeys = ['email', 'firstName', 'isSsoUser', 'lastName'];
|
|
269
|
+
for(const [key, value] of Object.entries(createdBy)){
|
|
270
|
+
if(includeKeys.includes(key)){
|
|
271
|
+
publisher[key] = value;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return publisher;
|
|
276
|
+
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async downloadAssortmentPublishChange(assortmentId: string, assortmentPublishChangeId: string) {
|
|
280
|
+
const resourceString = '/assortments/' + assortmentId + '/history/' + assortmentPublishChangeId;
|
|
281
|
+
return await Request.request(
|
|
282
|
+
resourceString,
|
|
283
|
+
{
|
|
284
|
+
method: 'GET',
|
|
285
|
+
Headers: {
|
|
286
|
+
Accept: 'application/json',
|
|
287
|
+
'Content-Type': 'application/json'
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async downloadHydratedChangeDetail(assortmentPublishChange) {
|
|
294
|
+
try {
|
|
295
|
+
console.info('downloadHydratedChangeDetail-start');
|
|
296
|
+
const link = assortmentPublishChange?.hydratedDetailDownloadLink;
|
|
297
|
+
const response = await fetch(link);
|
|
298
|
+
const data = await response.json();
|
|
299
|
+
return data;
|
|
300
|
+
|
|
301
|
+
} catch (e) {
|
|
302
|
+
console.log('Error hydratedDetailDownloadLink: ' + e.message);
|
|
303
|
+
}
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async downloadAssortmentBaseline(assortmentPublishChange) {
|
|
308
|
+
console.info('downloadAssortmentBaseline-start');
|
|
309
|
+
try {
|
|
310
|
+
const link = assortmentPublishChange?.assortmentBaselineDownloadLink;
|
|
311
|
+
const response = await fetch(link);
|
|
312
|
+
const data = await response.json();
|
|
313
|
+
return data;
|
|
314
|
+
|
|
315
|
+
} catch (e) {
|
|
316
|
+
console.log('Error assortmentBaselineDownloadLink: ' + e.message);
|
|
317
|
+
}
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async getDeleteChanges(assortmentPublishChange, apcHistory: object[], assortmentBaseline, sinceDate: Date): Promise<[]>{
|
|
322
|
+
console.info('getDeleteChanges(): ' + assortmentPublishChange['id']);
|
|
323
|
+
console.info(sinceDate);
|
|
324
|
+
const currentAPCIndex = apcHistory.findIndex(pc => pc['id'] === assortmentPublishChange['id']);
|
|
325
|
+
console.info(' currentAPCIndex: ' + currentAPCIndex);
|
|
326
|
+
if(currentAPCIndex == 0){
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
const previousApcIndex = (currentAPCIndex > 0)? currentAPCIndex-1: 0;
|
|
330
|
+
|
|
331
|
+
const apcCreatedOn = apcHistory[previousApcIndex]['createdOn'];
|
|
332
|
+
console.info('apcCreatedOn: ' + apcCreatedOn);
|
|
333
|
+
const apcDeletes = await this.downloadDeleteChanges(assortmentPublishChange);
|
|
334
|
+
|
|
335
|
+
let previousApcDate = Date.parse(apcCreatedOn);
|
|
336
|
+
const sinceDateMilliseconds = sinceDate.getTime();
|
|
337
|
+
if(Logger.isInfoOn()){
|
|
338
|
+
console.info('apcCreatedOn: ' + apcCreatedOn);
|
|
339
|
+
console.info('sinceDate: ' + sinceDate);
|
|
340
|
+
console.info('sinceDateMilliseconds: ' + sinceDateMilliseconds);
|
|
341
|
+
console.info('apcDateMilliseconds: ' + previousApcDate);
|
|
342
|
+
}
|
|
343
|
+
//if only 1 apc, no processing needed
|
|
344
|
+
if(sinceDateMilliseconds !== previousApcDate){
|
|
345
|
+
console.info('sinceDateMilliseconds !== apcDateMilliseconds');
|
|
346
|
+
const currentAssortmentItemIds = this.getBaselineItemIds(assortmentBaseline);
|
|
347
|
+
const deleteIds = (apcDeletes.length === 0)
|
|
348
|
+
?[]
|
|
349
|
+
:apcDeletes.map(item => item['itemId']);
|
|
350
|
+
// console.log('currentAssortmentItemIds: ' + currentAssortmentItemIds);
|
|
351
|
+
// console.log('deleteIds: ' + deleteIds);
|
|
352
|
+
|
|
353
|
+
for(let i = currentAPCIndex -1; i > 0; i--) {
|
|
354
|
+
const workingAPC = apcHistory[i];
|
|
355
|
+
previousApcDate = Date.parse(workingAPC['createdOn']);
|
|
356
|
+
if(sinceDateMilliseconds === previousApcDate){
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
if(workingAPC['deletes'] > 0){
|
|
360
|
+
const previousApc = (i > 0)
|
|
361
|
+
?apcHistory[i-1]
|
|
362
|
+
:undefined;
|
|
363
|
+
const deleteChanges = await this.buildDeleteChanges(workingAPC, previousApc);
|
|
364
|
+
console.info('checking deleteChanges');
|
|
365
|
+
for(const dItem of deleteChanges){
|
|
366
|
+
const dItemId = dItem['itemId'];
|
|
367
|
+
console.info(dItemId);
|
|
368
|
+
if(!currentAssortmentItemIds.includes(dItemId) && !deleteIds.includes(dItemId)){
|
|
369
|
+
console.info('adding');
|
|
370
|
+
deleteIds.push(dItemId);
|
|
371
|
+
apcDeletes.push(dItem);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
console.info('getDeleteChanges()-currentAssortmentItemIds: ' + currentAssortmentItemIds);
|
|
377
|
+
console.info('getDeleteChanges()-deleteIds: ' + deleteIds);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return apcDeletes;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
getBaselineItemIds(assortmentBaseline): string[]{
|
|
384
|
+
const itemIds = [];
|
|
385
|
+
const assortmentItemsArray = assortmentBaseline['assortmentItems'];
|
|
386
|
+
|
|
387
|
+
for (const aItem of assortmentItemsArray) {
|
|
388
|
+
itemIds.push(aItem['itemId']);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return itemIds;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async downloadDeleteChanges(apc) {
|
|
395
|
+
console.info('deleteDataDownloadLink-start');
|
|
396
|
+
try {
|
|
397
|
+
const link = apc['deleteDataDownloadLink'];
|
|
398
|
+
// console.info('link: ' + link);
|
|
399
|
+
const response = await fetch(link);
|
|
400
|
+
// console.debug('Got deleteDataDownloadLink: ');
|
|
401
|
+
const data = await response.json();
|
|
402
|
+
// console.debug(JSON.stringify(data));
|
|
403
|
+
return data;
|
|
404
|
+
|
|
405
|
+
} catch (e) {
|
|
406
|
+
console.log('Error deleteDataDownloadLink: ' + e.message);
|
|
407
|
+
}
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async buildDeleteChanges(apc, previousApc) {
|
|
412
|
+
console.info('buildDeleteChanges()');
|
|
413
|
+
|
|
414
|
+
if(apc['deleteDataDownloadLink']){
|
|
415
|
+
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
416
|
+
if(deleteChanges){
|
|
417
|
+
return deleteChanges;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
console.info('pulling down full APC');
|
|
421
|
+
apc = await this.downloadAssortmentPublishChange(apc['assortmentId'], apc['id']);
|
|
422
|
+
if(apc['deleteDataDownloadLink']){
|
|
423
|
+
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
424
|
+
if(deleteChanges){
|
|
425
|
+
return deleteChanges;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if(!previousApc){
|
|
429
|
+
throw new Error(BaseProcessPublishAssortment.NOT_ABLE_TO_PROCESS_DELETE_CHANGES);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
let previousBaseline;
|
|
433
|
+
console.info('check previousApc');
|
|
434
|
+
if(previousApc['assortmentBaselineDownloadLink']){
|
|
435
|
+
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
436
|
+
}else {
|
|
437
|
+
console.info('previousApc pulling down full APC');
|
|
438
|
+
previousApc = await this.downloadAssortmentPublishChange(previousApc['assortmentId'], previousApc['id']);
|
|
439
|
+
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const deleteIds = apc['detail']['deletes'].map(dItem => dItem['id']);
|
|
443
|
+
|
|
444
|
+
//building deletes based on previous baseline; because some APCs don't have delete data
|
|
445
|
+
const deleteArray = previousBaseline['assortmentItems'].filter(aItem => deleteIds.includes(aItem['itemId']));
|
|
446
|
+
console.info('deleteArray.length: ' + deleteArray.length);
|
|
447
|
+
return deleteArray;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async getItemFederatedIds(/*itemIds*/): Promise<Map<string, string>> {
|
|
451
|
+
|
|
452
|
+
const itemFederatedIds = new Map<string, string>();
|
|
453
|
+
// const expandedItemIds = itemIds.map(id => 'item:' + id);
|
|
454
|
+
// const fedRecords = await new Federation(this.logger).getFederationRecordsFromIdsBulk(expandedItemIds);
|
|
455
|
+
// for (let i = 0; i < fedRecords.length; i++) {
|
|
456
|
+
// const federationRecord = fedRecords[i];
|
|
457
|
+
// // console.log('federationRecord: ' + JSON.stringify(federationRecord));
|
|
458
|
+
// // console.log('federationRecord.reference: ' + federationRecord.reference);
|
|
459
|
+
// // console.log('federationRecord.mappedReference: ' + federationRecord.mappedReference);
|
|
460
|
+
// let vibeId = federationRecord.reference;
|
|
461
|
+
// if (vibeId && vibeId.startsWith('item:')) {
|
|
462
|
+
// vibeId = vibeId.substring(5);
|
|
463
|
+
// }
|
|
464
|
+
// itemFederatedIds.set(vibeId, federationRecord.mappedReference);
|
|
465
|
+
// }
|
|
466
|
+
// console.log('itemFederatedIds: ' + JSON.stringify(Object.fromEntries(itemFederatedIds)));
|
|
467
|
+
return itemFederatedIds;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
getFullChangeAssortmentMap(fullChange: any): Map<string, object> {
|
|
471
|
+
const assortmentItemsArray = fullChange?.assortmentItems || [];
|
|
472
|
+
const assortmentItemsMap = new Map<string, object>();
|
|
473
|
+
|
|
474
|
+
for (const aItem of assortmentItemsArray) {
|
|
475
|
+
const itemId = aItem?.itemId;
|
|
476
|
+
assortmentItemsMap.set(itemId, aItem);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return assortmentItemsMap;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
getDeleteChangesAssortmentMap(deleteChanges: any[]): Map<string, object> {
|
|
483
|
+
const deleteItems = new Map<string, object>();
|
|
484
|
+
|
|
485
|
+
for (const dItem of deleteChanges) {
|
|
486
|
+
deleteItems.set(dItem['itemId'], dItem);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return deleteItems;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
getReleasedForDevelopmentItemAndFamilyIds(fullChange, deleteChanges): string[] {
|
|
493
|
+
console.info('getReleasedForDevelopmentItemAndFamilyIds');
|
|
494
|
+
const releasedForDevelopmentItemIds:string[] = [];
|
|
495
|
+
const itemFamilySet = new Set<string>();
|
|
496
|
+
const assortmentItemsArray = fullChange['assortmentItems'];
|
|
497
|
+
|
|
498
|
+
for (const aItem of assortmentItemsArray) {
|
|
499
|
+
const meetsCriteria= this.meetsCriteria(aItem);
|
|
500
|
+
const item = aItem['item'];
|
|
501
|
+
const itemId = item['id'];
|
|
502
|
+
const itemFamilyId = item['itemFamilyId'];
|
|
503
|
+
console.debug('itemId: ' + item['id']);
|
|
504
|
+
if(meetsCriteria){
|
|
505
|
+
console.debug('adding item to releasedForDevelopmentItemIds');
|
|
506
|
+
console.debug('itemFamilyId: ' + item['itemFamilyId']);
|
|
507
|
+
releasedForDevelopmentItemIds.push(itemId);
|
|
508
|
+
if(!itemFamilySet.has(itemFamilyId) && itemId !== itemFamilyId){
|
|
509
|
+
const familyItem = item['itemFamily'];
|
|
510
|
+
const familyItemMeetsCriteria = this.meetsCriteria({item: familyItem});
|
|
511
|
+
if(familyItemMeetsCriteria){
|
|
512
|
+
releasedForDevelopmentItemIds.push(itemFamilyId);
|
|
513
|
+
}
|
|
514
|
+
itemFamilySet.add(itemFamilyId);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
for(const dItem of deleteChanges){
|
|
520
|
+
const meetsCriteria = this.meetsCriteria(dItem);
|
|
521
|
+
const item = dItem['item'];
|
|
522
|
+
const itemId = item['id'];
|
|
523
|
+
const itemFamilyId = item['itemFamilyId'];
|
|
524
|
+
console.debug('itemId: ' + item['id']);
|
|
525
|
+
if(meetsCriteria) {
|
|
526
|
+
releasedForDevelopmentItemIds.push(itemId);
|
|
527
|
+
if(!itemFamilySet.has(itemFamilyId) && itemId !== itemFamilyId){
|
|
528
|
+
const familyItem = item['itemFamily'];
|
|
529
|
+
if(familyItem){
|
|
530
|
+
const familyItemMeetsCriteria = this.meetsCriteria({item: familyItem});
|
|
531
|
+
if(familyItemMeetsCriteria){
|
|
532
|
+
releasedForDevelopmentItemIds.push(itemFamilyId);
|
|
533
|
+
}
|
|
534
|
+
itemFamilySet.add(itemFamilyId);
|
|
535
|
+
}else{
|
|
536
|
+
itemFamilySet.add(itemFamilyId);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return releasedForDevelopmentItemIds;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
meetsCriteria(aItem): boolean {
|
|
545
|
+
const item = aItem['item'];
|
|
546
|
+
const lifecycleStage = item['lifecycleStage'];
|
|
547
|
+
|
|
548
|
+
return lifecycleStage && !this.config.itemPreDevelopmentLifecycleStages.includes(lifecycleStage);
|
|
549
|
+
}
|
|
550
|
+
async processPublish(pcd: PublishChangeData, changeDetail, fullChange, deleteChanges) {
|
|
551
|
+
console.info('processPublish-start');
|
|
552
|
+
|
|
553
|
+
const assortmentItemFullChangeMap = this.getFullChangeAssortmentMap(fullChange);
|
|
554
|
+
const assortmentItemDeleteMap = this.getDeleteChangesAssortmentMap(deleteChanges);
|
|
555
|
+
pcd.itemFamilyChanges = this.getItemFamilyChanges(pcd, changeDetail, assortmentItemFullChangeMap, assortmentItemDeleteMap);
|
|
556
|
+
|
|
557
|
+
const events = await this.getEventsForPublishChangeData(pcd);
|
|
558
|
+
if (events.length === 0) {
|
|
559
|
+
const message = 'No Events to Send; returning';
|
|
560
|
+
console.log(message);
|
|
561
|
+
return {
|
|
562
|
+
results: { message },
|
|
563
|
+
skip_await: true
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const results = await this.sendEvents(events);
|
|
568
|
+
return { results };
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private async sendEvents(events: SeasonalPayload[]): Promise<any> {
|
|
572
|
+
console.info('sendEvents()');
|
|
573
|
+
const eventType = 'ASYNC_PUBLISH_SEASON';
|
|
574
|
+
const sendMode = (this.config['sendMode'][eventType] || '').toLowerCase();
|
|
575
|
+
|
|
576
|
+
switch (sendMode) {
|
|
577
|
+
case 'sendtoflexplm':
|
|
578
|
+
return this.sendToFlexPLM(events, eventType);
|
|
579
|
+
case 'localfile':
|
|
580
|
+
return this.saveToLocalFile(events, eventType);
|
|
581
|
+
case 'vibeiqfile':
|
|
582
|
+
case 'vibeiqfile-dontsendtoflexplm':
|
|
583
|
+
return this.handleVibeIQFile(events, eventType, sendMode);
|
|
584
|
+
default:
|
|
585
|
+
return {}; // Or handle other cases as required
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
private async sendToFlexPLM(events: SeasonalPayload[], eventType: string) {
|
|
590
|
+
const asyncEvent: AsyncEventsPayloadType = {
|
|
591
|
+
taskId: this.config.taskId,
|
|
592
|
+
eventType,
|
|
593
|
+
objectClass: 'LCSSeason',
|
|
594
|
+
events
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
const flexPLMConnect = new FlexPLMConnect(this.config);
|
|
598
|
+
return await flexPLMConnect.sendToFlexPLM(asyncEvent);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
private async saveToLocalFile(events: SeasonalPayload[], eventType: string) {
|
|
602
|
+
let results = {};
|
|
603
|
+
let fileHandle;
|
|
604
|
+
|
|
605
|
+
try {
|
|
606
|
+
const dateString = this.getCurrentDateString();
|
|
607
|
+
const dirName = await path.resolve();
|
|
608
|
+
await fsPromise.mkdir(dirName, { recursive: true });
|
|
609
|
+
const eventDirName = 'events-output';
|
|
610
|
+
const fileName = `${path.sep}${eventDirName}${path.sep}events-${dateString}.json`;
|
|
611
|
+
fileHandle = await fsPromise.open('.' + fileName, 'a');
|
|
612
|
+
|
|
613
|
+
const asyncEvent: AsyncEventsPayloadType = {
|
|
614
|
+
taskId: this.config.taskId,
|
|
615
|
+
eventType,
|
|
616
|
+
objectClass: 'LCSSeason',
|
|
617
|
+
events
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
await fileHandle.writeFile(JSON.stringify(asyncEvent));
|
|
621
|
+
|
|
622
|
+
results = {
|
|
623
|
+
message: 'Successfully Saved File',
|
|
624
|
+
fileLocation: dirName + fileName
|
|
625
|
+
};
|
|
626
|
+
} catch (e) {
|
|
627
|
+
results['error'] = e.message;
|
|
628
|
+
} finally {
|
|
629
|
+
await fileHandle?.close();
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return results;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
private async handleVibeIQFile(events: SeasonalPayload[], eventType: string, sendMode: string) {
|
|
636
|
+
const eventBuffer = Buffer.from(JSON.stringify(events), 'utf-8');
|
|
637
|
+
const fileName = 'ASYNC_PUBLISH_SEASON-events.json';
|
|
638
|
+
const uploadFile = await new Files().createAndUploadFileFromBuffer(eventBuffer, 'application/json', fileName, null, this.TTL);
|
|
639
|
+
|
|
640
|
+
const asyncEvent: AsyncEventsPayloadType = {
|
|
641
|
+
taskId: this.config.taskId,
|
|
642
|
+
eventType,
|
|
643
|
+
objectClass: 'LCSSeason',
|
|
644
|
+
eventsFileId: uploadFile.id,
|
|
645
|
+
eventsDownloadLink: uploadFile.downloadUrl
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
if (sendMode === 'vibeiqfile') {
|
|
649
|
+
const flexPLMConnect = new FlexPLMConnect(this.config);
|
|
650
|
+
return await flexPLMConnect.sendToFlexPLM(asyncEvent);
|
|
651
|
+
} else {
|
|
652
|
+
return {
|
|
653
|
+
message: 'Successfully Uploaded File.',
|
|
654
|
+
asyncEvent
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
private getCurrentDateString() {
|
|
660
|
+
const d = new Date();
|
|
661
|
+
return '' + d.getUTCFullYear()
|
|
662
|
+
+ '-' + d.getUTCMonth()
|
|
663
|
+
+ '-' + (d.getUTCDate() + 1)
|
|
664
|
+
+ '-' + d.getUTCHours()
|
|
665
|
+
+ '-' + d.getUTCMinutes()
|
|
666
|
+
+ '-' + d.getUTCSeconds()
|
|
667
|
+
+ '-' + d.getUTCMilliseconds();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
getItemFamilyChanges(pcd: PublishChangeData, changeDetail, assortmentItemFullChangeMap: Map<string, any>, assortmentItemDeleteMap: Map<string, any>): Map<string, ItemFamilyChanges> {
|
|
671
|
+
console.info('getItemFamilyChanges-start');
|
|
672
|
+
|
|
673
|
+
const itemFamilyChanges = new Map<string, ItemFamilyChanges>();
|
|
674
|
+
const { adds, deletes, updates, familyItemsRemoved } = changeDetail;
|
|
675
|
+
const addIds = adds.map(item => item.id);
|
|
676
|
+
const deleteIds = deletes.map(item => item.id);
|
|
677
|
+
const updateIds = updates.map(item => item.id);
|
|
678
|
+
const familyItemsRemovedIds = familyItemsRemoved.map(item => item.itemId);
|
|
679
|
+
|
|
680
|
+
for (const [itemId, aItem] of assortmentItemFullChangeMap) {
|
|
681
|
+
const { item: { itemFamilyId }, projectItem } = aItem;
|
|
682
|
+
|
|
683
|
+
if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId) || !pcd.releasedForDevelopmentItemIds.includes(itemId)) {
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const ifc = itemFamilyChanges.get(itemFamilyId) || new ItemFamilyChanges(itemFamilyId, pcd.sinceDate);
|
|
688
|
+
if (!itemFamilyChanges.has(itemFamilyId)) {
|
|
689
|
+
ifc.itemFamilyObject = itemId === itemFamilyId ? aItem?.item : aItem?.item?.itemFamily;
|
|
690
|
+
itemFamilyChanges.set(itemFamilyId, ifc);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
ifc.assortmentItemFullChangeMap.set(itemId, aItem);
|
|
694
|
+
if (pcd.itemToFederatedIdMapping.has(itemId)) {
|
|
695
|
+
ifc.itemToFederatedIdMapping.set(itemId, pcd.itemToFederatedIdMapping.get(itemId));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (itemId === itemFamilyId) {
|
|
699
|
+
if (addIds.includes(itemId)) ifc.familyAdd = true;
|
|
700
|
+
else if (deleteIds.includes(itemId)) ifc.familyDelete = true;
|
|
701
|
+
else if (updateIds.includes(itemId)) ifc.familyUpdate = true;
|
|
702
|
+
else if (familyItemsRemovedIds.includes(itemId)) ifc.familyItemRemoved = true;
|
|
703
|
+
|
|
704
|
+
if (projectItem) {
|
|
705
|
+
const updatedOnDate = new Date(projectItem.updatedOn);
|
|
706
|
+
if (updatedOnDate > pcd.sinceDate) {
|
|
707
|
+
ifc.familyUpdate = true;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (projectItem && !projectItem?.roles && aItem?.item?.roles) {
|
|
712
|
+
projectItem.roles = aItem?.item?.roles;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
} else {
|
|
716
|
+
if (addIds.includes(itemId)) ifc.colorAdds.push(itemId);
|
|
717
|
+
else if (deleteIds.includes(itemId)) ifc.colorDeletes.push(itemId);
|
|
718
|
+
else if (updateIds.includes(itemId)) ifc.colorUpdates.push(itemId);
|
|
719
|
+
else if (projectItem && new Date(projectItem.updatedOn) > pcd.sinceDate) ifc.colorUpdates.push(itemId);
|
|
720
|
+
else ifc.colorUnchanged.push(itemId);
|
|
721
|
+
|
|
722
|
+
if (projectItem && !projectItem?.roles && aItem?.item?.roles) {
|
|
723
|
+
projectItem.roles = aItem?.item?.roles;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
for(const [itemId, delEntity] of assortmentItemDeleteMap){
|
|
729
|
+
const itemFamilyId = delEntity['item']['itemFamilyId'];
|
|
730
|
+
|
|
731
|
+
const item = delEntity['item'];
|
|
732
|
+
if(!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId) || !pcd.releasedForDevelopmentItemIds.includes(itemId)){
|
|
733
|
+
continue;
|
|
734
|
+
} else {
|
|
735
|
+
if(!item){
|
|
736
|
+
console.error('Failed to find deleted item entity');
|
|
737
|
+
console.error(' itemFamilyId: ' + itemFamilyId + ' -itemId: ' + itemId);
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
let ifc = itemFamilyChanges.get(itemFamilyId);
|
|
744
|
+
if(!ifc){
|
|
745
|
+
ifc = new ItemFamilyChanges(itemFamilyId, pcd.sinceDate);
|
|
746
|
+
if(itemId === itemFamilyId){
|
|
747
|
+
ifc.familyDelete = true;
|
|
748
|
+
ifc.itemFamilyObject = item;
|
|
749
|
+
} else{
|
|
750
|
+
ifc.itemFamilyObject = item?.itemFamily;
|
|
751
|
+
}
|
|
752
|
+
itemFamilyChanges.set(itemFamilyId, ifc);
|
|
753
|
+
}
|
|
754
|
+
if(pcd.itemToFederatedIdMapping.has(itemId)){
|
|
755
|
+
ifc.itemToFederatedIdMapping.set(itemId, pcd.itemToFederatedIdMapping.get(itemId));
|
|
756
|
+
}
|
|
757
|
+
ifc.assortmentItemDeleteMap.set(itemId, delEntity);
|
|
758
|
+
ifc.assortmentItemFullChangeMap.set(itemId, delEntity);
|
|
759
|
+
|
|
760
|
+
if(itemId === itemFamilyId){
|
|
761
|
+
ifc.familyDelete = true;
|
|
762
|
+
}else {
|
|
763
|
+
ifc.colorDeletes.push(itemId);
|
|
764
|
+
}
|
|
765
|
+
}//End deletes for loop
|
|
766
|
+
if(Logger.isDebugOn()){
|
|
767
|
+
console.debug('returning size: ' + itemFamilyChanges.size);
|
|
768
|
+
for (const [key, value] of itemFamilyChanges){
|
|
769
|
+
console.debug(key + ' => ' + value);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
return itemFamilyChanges;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
async getEventsForPublishChangeData(publishChangeData: PublishChangeData): Promise<SeasonalPayload[]> {
|
|
778
|
+
console.info('getEventsForPublishChangeData-start');
|
|
779
|
+
const seasonalPayloads: SeasonalPayload[] = [];
|
|
780
|
+
for (const itemFamilyChange of publishChangeData.itemFamilyChanges.values()) {
|
|
781
|
+
const events = await this.getEventsForItemFamilyChanges(itemFamilyChange, publishChangeData.assortmentId, publishChangeData.seasonFed, publishChangeData.itemToFederatedIdMapping);
|
|
782
|
+
if (events.length > 0) {
|
|
783
|
+
seasonalPayloads.push(...events);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return seasonalPayloads;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**Returns the events for a given ItemFamilyChanges object
|
|
790
|
+
*
|
|
791
|
+
* Cases:
|
|
792
|
+
* Add just family:
|
|
793
|
+
* Add option to family (with existing option):
|
|
794
|
+
* Add option to family (no options on assortment):
|
|
795
|
+
* Remove family and option:
|
|
796
|
+
* Remove only option and leave family:
|
|
797
|
+
* Remove one option of multiple for family:
|
|
798
|
+
*
|
|
799
|
+
* @param itemFamilyChanges
|
|
800
|
+
* @param assortmentId
|
|
801
|
+
* @param assortmentFederationId
|
|
802
|
+
* @param itemToFederatedIdMapping
|
|
803
|
+
* @returns
|
|
804
|
+
*/
|
|
805
|
+
async getEventsForItemFamilyChanges(itemFamilyChanges: ItemFamilyChanges, assortmentId: string, seasonFed: SeasonFederation, itemToFederatedIdMapping: Map<string, string>): Promise<SeasonalPayload[]> {
|
|
806
|
+
console.info('getEventsForItemFamilyChanges()');
|
|
807
|
+
const events: SeasonalPayload[] = [];
|
|
808
|
+
const LCSSeason = Object.assign({}, seasonFed);
|
|
809
|
+
|
|
810
|
+
const projectItem = this.getProjectItem(itemFamilyChanges, itemFamilyChanges.itemFamilyId);
|
|
811
|
+
const familyAssortmentItem = itemFamilyChanges.familyAdd || itemFamilyChanges.familyUpdate || itemFamilyChanges.familyDelete
|
|
812
|
+
|| itemFamilyChanges.colorAdds.length > 0 || itemFamilyChanges.colorUpdates.length > 0 || itemFamilyChanges.colorDeletes.length > 0
|
|
813
|
+
|| (projectItem && this.updatedSinceDate(projectItem, itemFamilyChanges.sinceDate));
|
|
814
|
+
//familyItemRemoved is used when adding the first option to an assortment
|
|
815
|
+
//and will have updates for the family item.
|
|
816
|
+
|
|
817
|
+
if (familyAssortmentItem) {
|
|
818
|
+
//Product-season add
|
|
819
|
+
const entityReference = itemFamilyChanges.itemFamilyId;
|
|
820
|
+
const prodEntityData = (itemFamilyChanges.assortmentItemFullChangeMap.has(entityReference))
|
|
821
|
+
? itemFamilyChanges.assortmentItemFullChangeMap.get(entityReference)['item']
|
|
822
|
+
: itemFamilyChanges.itemFamilyObject;
|
|
823
|
+
|
|
824
|
+
let seasonalData = await this.getSeasonalData(projectItem);
|
|
825
|
+
seasonalData = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonalData, 'LCSProductSeasonLink', 'vibe2flex');
|
|
826
|
+
|
|
827
|
+
const LCSProduct: ProductFederation = await this.getProductFederation(entityReference, itemToFederatedIdMapping, prodEntityData);
|
|
828
|
+
const psUpsert: SeasonalPayload = {
|
|
829
|
+
eventType: 'UPSERT_ON_SEASON',
|
|
830
|
+
objectClass: 'LCSProductSeasonLink',
|
|
831
|
+
entityReference: 'item:' + entityReference,
|
|
832
|
+
LCSSeason,
|
|
833
|
+
LCSProduct,
|
|
834
|
+
data: seasonalData
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
if('ASSORTMENT' === projectItem.addedFromSource && projectItem.addedFromAssortment){
|
|
838
|
+
const carriedFromSeason = await this.getCarriedFromSeason(projectItem);
|
|
839
|
+
if(carriedFromSeason){
|
|
840
|
+
psUpsert['carriedFromSeason'] = carriedFromSeason;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
events.push(psUpsert);
|
|
845
|
+
}
|
|
846
|
+
//colorway-season adds
|
|
847
|
+
//colorAdds
|
|
848
|
+
//colorUpdates
|
|
849
|
+
const colorwayChanges = [];
|
|
850
|
+
colorwayChanges.push(...itemFamilyChanges.colorAdds);
|
|
851
|
+
colorwayChanges.push(...itemFamilyChanges.colorUpdates);
|
|
852
|
+
colorwayChanges.push(...itemFamilyChanges.colorDeletes);
|
|
853
|
+
|
|
854
|
+
for (const entityReference of colorwayChanges) {
|
|
855
|
+
let item = {};
|
|
856
|
+
if (itemFamilyChanges.assortmentItemFullChangeMap.has(entityReference)) {
|
|
857
|
+
const fullAi = itemFamilyChanges.assortmentItemFullChangeMap.get(entityReference);
|
|
858
|
+
item = fullAi['item'];
|
|
859
|
+
}
|
|
860
|
+
const projectItem = this.getProjectItem(itemFamilyChanges, entityReference);
|
|
861
|
+
let seasonalData = await this.getSeasonalData(projectItem);
|
|
862
|
+
seasonalData = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonalData, 'LCSSKUSeasonLink', 'vibe2flex');
|
|
863
|
+
|
|
864
|
+
const LCSSKU: SkuFederation = await this.getSKUFederation(entityReference, itemToFederatedIdMapping, item);
|
|
865
|
+
const csUpsert: SeasonalPayload = {
|
|
866
|
+
eventType: 'UPSERT_ON_SEASON',
|
|
867
|
+
objectClass: 'LCSSKUSeasonLink',
|
|
868
|
+
entityReference: 'item:' + entityReference,
|
|
869
|
+
LCSSeason,
|
|
870
|
+
LCSSKU,
|
|
871
|
+
data: seasonalData
|
|
872
|
+
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
if('ASSORTMENT' === projectItem.addedFromSource && projectItem.addedFromAssortment){
|
|
876
|
+
const carriedFromSeason = await this.getCarriedFromSeason(projectItem);
|
|
877
|
+
if(carriedFromSeason){
|
|
878
|
+
csUpsert['carriedFromSeason'] = carriedFromSeason;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
events.push(csUpsert);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
return events;
|
|
886
|
+
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
getProjectItem(itemFamilyChanges: ItemFamilyChanges, id: string) {
|
|
890
|
+
let projectItem: any = {};
|
|
891
|
+
if (itemFamilyChanges?.assortmentItemFullChangeMap.has(id)) {
|
|
892
|
+
const aItem = itemFamilyChanges?.assortmentItemFullChangeMap.get(id);
|
|
893
|
+
projectItem = aItem?.projectItem;
|
|
894
|
+
}
|
|
895
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
896
|
+
//Get from option assortmentItem because family not in assortmentItemFullChangeMap:start
|
|
897
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
898
|
+
if (id === itemFamilyChanges.itemFamilyId && Object.keys(projectItem).length == 0) {
|
|
899
|
+
for (const asstItem of itemFamilyChanges.assortmentItemFullChangeMap.values()) {
|
|
900
|
+
if(asstItem?.familyProjectItem){
|
|
901
|
+
projectItem = asstItem?.familyProjectItem;
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
907
|
+
//Get from option assortmentItem because family not in assortmentItemFullChangeMap:end
|
|
908
|
+
/////////////////////////////////////////////////////////////////////////////
|
|
909
|
+
return projectItem;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
async getSeasonalData(projectItem): Promise<object> {
|
|
913
|
+
return await this.dc.getFlexPLMObjectData(projectItem, [], true);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
protected async getProductFederation(entityReference: string, itemToFederatedIdMapping: Map<string, string>, itemFamilyObject): Promise<ProductFederation> {
|
|
917
|
+
const identifierKeys: string[] = this.config?.identifierAtts?.LCSProduct;
|
|
918
|
+
const itemObj = await this.dc.getFlexPLMObjectData(itemFamilyObject, [], true);
|
|
919
|
+
let prodObj: ProductFederation = {
|
|
920
|
+
entityReference: 'item:' + entityReference,
|
|
921
|
+
objectClass: 'LCSProduct' as const,
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
for (const key of identifierKeys) {
|
|
925
|
+
if (itemObj[key]) {
|
|
926
|
+
prodObj[key] = itemObj[key];
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (!prodObj?.vibeIQIdentifier) {
|
|
931
|
+
prodObj.vibeIQIdentifier = itemFamilyObject?.identifier || itemFamilyObject?.itemNumber;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
prodObj = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, prodObj, 'LCSProduct', 'vibe2flex');
|
|
935
|
+
return prodObj as ProductFederation;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
protected async getSKUFederation(entityReference: string, itemToFederatedIdMapping: Map<string, string>, itemObject): Promise<SkuFederation> {
|
|
939
|
+
const identifierKeys: string[] = this.config?.identifierAtts?.LCSSKU;
|
|
940
|
+
const itemObj = await this.dc.getFlexPLMObjectData(itemObject, [], true);
|
|
941
|
+
let skuObj: SkuFederation = {
|
|
942
|
+
entityReference: 'item:' + entityReference,
|
|
943
|
+
objectClass: 'LCSSKU' as const,
|
|
944
|
+
};
|
|
945
|
+
for (const key of identifierKeys) {
|
|
946
|
+
if (itemObj[key]) {
|
|
947
|
+
skuObj[key] = itemObj[key];
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if (!skuObj?.vibeIQIdentifier) {
|
|
952
|
+
skuObj.vibeIQIdentifier = itemObject?.identifier || itemObject?.itemNumber;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
skuObj = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, skuObj, 'LCSSKU', 'vibe2flex');
|
|
956
|
+
return skuObj as SkuFederation;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
async getCarriedFromSeason(projectItem): Promise<SeasonFederation> {
|
|
960
|
+
const addedFromAssortment = projectItem.addedFromAssortment;
|
|
961
|
+
if(this.cache.carriedFromSeason[addedFromAssortment]){
|
|
962
|
+
return this.cache.carriedFromSeason[addedFromAssortment];
|
|
963
|
+
}
|
|
964
|
+
let seasonFederation = undefined;
|
|
965
|
+
const assortmentEntity = await this.getAssormentEntityFromId(addedFromAssortment);
|
|
966
|
+
if(assortmentEntity){
|
|
967
|
+
seasonFederation = await this.getSeasonFederationFromAssortment(assortmentEntity);
|
|
968
|
+
}
|
|
969
|
+
this.cache.carriedFromSeason[addedFromAssortment] = seasonFederation;
|
|
970
|
+
|
|
971
|
+
return seasonFederation;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
async getAssormentEntityFromId(assortmentId) {
|
|
975
|
+
let assortment;
|
|
976
|
+
try {
|
|
977
|
+
assortment = await new Entities().get({
|
|
978
|
+
entityName: 'assortment',
|
|
979
|
+
id: assortmentId
|
|
980
|
+
});
|
|
981
|
+
} catch (e) {
|
|
982
|
+
console.warn(`No Assortment found for id: ${assortmentId}`);
|
|
983
|
+
}
|
|
984
|
+
return assortment;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
async getSeasonFederationFromAssortment(assortment): Promise<SeasonFederation>{
|
|
988
|
+
|
|
989
|
+
assortment['flex2vibeMapKeyRoot'] = 'LCSSeason';
|
|
990
|
+
const flexPLMTypePath = await TypeConversionUtils.getObjectTypePath(this.transformMapFile, this.mapFileUtil, assortment);
|
|
991
|
+
|
|
992
|
+
let seasonObj: any = {
|
|
993
|
+
entityReference: 'assortment:' + assortment.id,
|
|
994
|
+
objectClass: 'LCSSeason' as const,
|
|
995
|
+
flexPLMTypePath
|
|
996
|
+
};
|
|
997
|
+
|
|
998
|
+
try{
|
|
999
|
+
const assortmentObj = await this.dc.getFlexPLMObjectData(assortment, [], true);
|
|
1000
|
+
const identifierKeys: string[] = await TypeConversionUtils.getIdentifierProperties(this.transformMapFile, this.mapFileUtil, assortment);
|
|
1001
|
+
const informationKeys: string[] = await TypeConversionUtils.getInformationalProperties(this.transformMapFile, this.mapFileUtil, assortment);
|
|
1002
|
+
for(const key of identifierKeys.concat(informationKeys)){
|
|
1003
|
+
if(assortmentObj[key]){
|
|
1004
|
+
seasonObj[key] = assortmentObj[key];
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
const objectKeys = Object.keys(seasonObj);
|
|
1008
|
+
const hasAllIdentifiers = identifierKeys.every(key => objectKeys.includes(key));
|
|
1009
|
+
|
|
1010
|
+
if(!hasAllIdentifiers){
|
|
1011
|
+
return undefined;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const mapKey:string = await TypeConversionUtils.getMapKey(this.transformMapFile, this.mapFileUtil, assortment, TypeConversionUtils.VIBE2FLEX_DIRECTION);
|
|
1015
|
+
seasonObj = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, seasonObj, mapKey, TypeConversionUtils.VIBE2FLEX_DIRECTION);
|
|
1016
|
+
} finally {
|
|
1017
|
+
delete assortment['flex2vibeMapKeyRoot'];
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
return seasonObj as SeasonFederation;
|
|
1021
|
+
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
}
|