@bytebrand/export-cars 1.21.35 → 1.21.38

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebrand/export-cars",
3
- "version": "1.21.35",
3
+ "version": "1.21.38",
4
4
  "description": "",
5
5
  "author": "bytebrand",
6
6
  "license": "UNLICENSED",
@@ -3,6 +3,7 @@ import { ModuleRef } from '@nestjs/core';
3
3
 
4
4
  import { CronicleUtils } from '@bytebrand/cronicle-helper';
5
5
  import { FsChunkInterface } from '@bytebrand/chunkable';
6
+ import { Car } from '@bytebrand/car-schema/dist/schema/data';
6
7
 
7
8
  import { CarService, RulesService, LoaderService, ConfigService } from '../services';
8
9
  import { MobileTransformer } from '../transformers/mobile.transformer';
@@ -35,7 +36,8 @@ export class SantanderExporter implements Exporter {
35
36
  chunk.data = chunk.data.filter(e => this.carService.filterCar(e, this.rulesService.rules));
36
37
 
37
38
  if (chunk && chunk.data) {
38
- for (const data of chunk.data) {
39
+ const filteredData = chunk.data.filter(({ car }) => !(!car.mainData.firstRegistration && car.mainData.mileage > 1000))
40
+ for (const data of filteredData) {
39
41
  const { car, images } = data;
40
42
  const transformedData = await santanderTransformer.transform(car, images);
41
43
  const transformedNormalizedData = await santanderTransformer.normalizeCarData(transformedData);
@@ -413,7 +413,7 @@ export class MobileTransformer {
413
413
  return this.booleanToString(match);
414
414
  }
415
415
 
416
- private dateToString(fieldValue: number, rule: MappingRules): string {
416
+ protected dateToString(fieldValue: number, rule: MappingRules): string {
417
417
  const date = new Date(fieldValue * 1000);
418
418
  if (["['6']", "['7']", "['8']"].includes(rule.importField)) {
419
419
  return `${date.getMonth() + 1}.${date.getFullYear()}`;
@@ -13,19 +13,20 @@ import { isNotEmptyArray } from '../utils';
13
13
  import { ConfigService } from '../services';
14
14
  import { ExportType } from '../enums';
15
15
  import { MobileTransformer } from './mobile.transformer';
16
+ import { Car } from '@bytebrand/car-schema/dist/schema/data';
16
17
 
17
18
  const vinGenerator = require('vin-generator');
18
19
 
19
20
  @Injectable()
20
- export class SantanderTransformer extends MobileTransformer {
21
+ export class SantanderTransformer {
22
+ private currentCar: DataSchemes.Car;
23
+
21
24
  constructor(
22
- mappingService: MappingService,
23
- userService: UserService,
24
- configService: ConfigService,
25
- imagesService: ImagesService,
26
- ) {
27
- super(mappingService, userService, configService, imagesService);
28
- }
25
+ private mappingService: MappingService,
26
+ private userService: UserService,
27
+ private configService: ConfigService,
28
+ private imagesService: ImagesService,
29
+ ) { }
29
30
 
30
31
  async normalizeCarData(transformedCarData: unknown) {
31
32
  const data = transformedCarData as Array<{ [key: string]: any }>;
@@ -44,4 +45,465 @@ export class SantanderTransformer extends MobileTransformer {
44
45
  return outputArray;
45
46
  }
46
47
 
48
+ async transform(carData: DataSchemes.Car, images: ImboImageResponseInterface[]): Promise<unknown> {
49
+ this.currentCar = carData;
50
+ const transformedCarData = {};
51
+ const rules = await this.mappingService.getRules();
52
+ for (const rule of rules) {
53
+ if (rule.custom === 'yes') {
54
+ const transformedField = await this.customTransformers(rule, carData, images);
55
+ if (transformedField) {
56
+ set(transformedCarData, `${rule.importField}`, transformedField);
57
+ }
58
+ } else {
59
+ const fieldValue: any = get(carData, `${rule.autodeField}`, null);
60
+ if (rule.autodeField === 'driveSuspension.gearbox' || rule.autodeField === 'mainData.mileage') {
61
+ set(transformedCarData, `${rule.importField}`, await this.customTransformers(rule, carData, images));
62
+ } else {
63
+ if (!isNil(fieldValue)) {
64
+ const transformedField = await this.transformField(fieldValue, rule);
65
+
66
+ if (!isNil(transformedField)) {
67
+ set(transformedCarData, `${rule.importField}`, transformedField);
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+ set(transformedCarData, 150, toString(get(carData, 'rate.monthlyInstallment', '')));
75
+ set(transformedCarData, 293, toString(get(carData, 'leasing.monthlyInstallment', '')));
76
+ set(transformedCarData, 1, get(carData, 'metaData.vehicleId', null));
77
+
78
+ if (this.configService.config.campaign === 'eauto' && carData.offer.ftpFolder === 'auto_de_eautos') {
79
+ set(transformedCarData, 12, get(carData, 'offer.internalNumber', null));
80
+ }
81
+
82
+ return transformedCarData;
83
+ }
84
+
85
+ private async transformField(fieldValue: any, rule: MappingRules): Promise<string | void> {
86
+ const transformProcedureName = `${rule.autodeFieldType}To${upperFirst(rule.importFieldType)}`;
87
+
88
+ switch (transformProcedureName) {
89
+ case 'stringToString': {
90
+ if (rule.autodeField === 'offer.offerDescription') {
91
+ return await this.offerStringToString();
92
+ }
93
+ return this.stringToString(fieldValue);
94
+ }
95
+ case 'integerToString': {
96
+ return this.numberToString(fieldValue);
97
+ }
98
+ case 'numberToString': {
99
+ return this.numberToString(fieldValue);
100
+ }
101
+ case 'booleanToString': {
102
+ return this.booleanToString(fieldValue);
103
+ }
104
+ case 'selectorToSelector': {
105
+ return this.selectorToSelector(fieldValue, rule);
106
+ }
107
+ case 'selectorToString': {
108
+ return this.selectorToString(fieldValue, rule);
109
+ }
110
+ case 'selector[]ToSelector': {
111
+ return this.selectorArrayToSelector(fieldValue, rule);
112
+ }
113
+ case 'selector[]ToString': {
114
+ return this.selectorArrayToString(fieldValue, rule);
115
+ }
116
+ case 'dateToString': {
117
+ return this.dateToString(fieldValue, rule);
118
+ }
119
+ case 'dateToMobile_delivery': {
120
+ return this.dateToString(fieldValue, rule);
121
+ }
122
+ case 'stringToMobile_model': {
123
+ return this.stringToMobile_model(fieldValue);
124
+ }
125
+ case 'numberToMobile_consumption': {
126
+ return this.numberToMobile_consumption(fieldValue);
127
+ }
128
+ case 'booleanToMobile_vatCalc': {
129
+ return this.booleanToMobile_vatCalc(fieldValue);
130
+ }
131
+ case 'selectorToMobile_extColor_selector': {
132
+ return this.selectorToSelector(fieldValue, rule);
133
+ }
134
+ case 'selectorToCategoryOrClass_selector': {
135
+ return this.selectorToCategoryOrClass_selector(fieldValue);
136
+ }
137
+ default:
138
+ LoggerService.log(`missing transform: ${transformProcedureName}`);
139
+ break;
140
+ }
141
+ }
142
+
143
+ private async offerStringToString(): Promise<string> {
144
+ const dealerId = get(this.currentCar, 'metaData.ownerId');
145
+ const descriptionSchema = await this.userService.getDefaultSummaryByDealerId(dealerId);
146
+ const descriptionBuilder = CarDescriptionFactory.create(DescriptionTypeEnum.Mobile, this.currentCar);
147
+ return descriptionBuilder.transform(descriptionSchema);
148
+ }
149
+
150
+ private async customTransformers(rule: MappingRules, car: DataSchemes.Car, images: ImboImageResponseInterface[]): Promise<unknown> {
151
+ switch (rule.autodeField) {
152
+ case 'image': {
153
+ return this.getImages(car, images);
154
+ }
155
+ case 'autodeUrl': {
156
+ return this.getAutodeUrl(car);
157
+ }
158
+ case 'persistentId': {
159
+ return this.getPersistentId(car);
160
+ }
161
+ case 'price.vatDeductible': {
162
+ return this.getVatDeductible(car);
163
+ }
164
+ case 'mainData.numberOfPreviousOwners': {
165
+ return this.getNumberOfPreviousOwners(car);
166
+ }
167
+ case 'mainData.condition': {
168
+ return this.getUsageType(car);
169
+ }
170
+ case 'price.vatRate': {
171
+ return this.getVatRate(car);
172
+ }
173
+ case 'price.currentSalesPrice': {
174
+ return this.getCurrentSalesPrice(car);
175
+ }
176
+ case 'driveSuspension.gearbox': {
177
+ return this.getGearBox(car);
178
+ }
179
+ case 'mainData.mileage': {
180
+ return this.getMilage(car);
181
+ }
182
+ case 'consumption.consumptionPowerCombined': {
183
+ return this.getWLTP(car);
184
+ }
185
+ case 'environmentEmissions.emissionClass': {
186
+ return this.getEmissionClass(car);
187
+ }
188
+ case 'environmentEmissions.energyEfficiencyClass': {
189
+ return this.getEnergyEfficiencyClass(car);
190
+ }
191
+ case 'environmentEmissions.emissionSticker': {
192
+ return this.getEmissionSticker(car);
193
+ }
194
+ case 'environmentEmissions.co2': {
195
+ return this.getCO2(car);
196
+ }
197
+ case 'metaData.vin': {
198
+ return this.getVinCode(car);
199
+ }
200
+ default:
201
+ return '';
202
+ }
203
+ }
204
+
205
+ private async getPersistentId(car: DataSchemes.Car) {
206
+ switch (this.configService.config.campaign) {
207
+ case ExportType.EAUTO:
208
+ if (car.offer.ftpFolder === 'auto_de_eautos') {
209
+ const dealers = await this.mappingService.getDealersMapping();
210
+ const dealerId = dealers.find(dealer => dealer.firebaseUserId === car.metaData.ownerId)?.dealerId;
211
+ if (dealerId) {
212
+ return dealerId;
213
+ }
214
+ }
215
+ return '416';
216
+ case ExportType.MARKTDE:
217
+ return '04109003';
218
+ case ExportType.EVIUM:
219
+ return '10022';
220
+ default:
221
+ throw new Error(`Wrong export type ${this.configService.config.campaign}`);
222
+ }
223
+ }
224
+
225
+ private getVatDeductible(car: DataSchemes.Car): string {
226
+ return this.booleanToString(!get(car, 'price.vatDeductible', false));
227
+ }
228
+
229
+ private getWLTP(car: DataSchemes.Car): string | void {
230
+ const envkvCompliant = get(car, 'consumption.envkvCompliant');
231
+ const hybridPlagin = get(car, 'engineData.hybridPlugin');
232
+
233
+ if (envkvCompliant && hybridPlagin) {
234
+ const powerCombined = get(car, 'consumption.consumptionPowerCombined');
235
+ return String(powerCombined);
236
+ }
237
+ }
238
+
239
+ private getEmissionClass(car: DataSchemes.Car): string | void {
240
+ const emissionClass = get(car, 'environmentEmissions.emissionClass', 'selector_unknown');
241
+ switch (emissionClass) {
242
+ case 'selector_emissionClass_euro1': {
243
+ return '1';
244
+ }
245
+ case 'selector_emissionClass_euro2': {
246
+ return '2';
247
+ }
248
+ case 'selector_emissionClass_euro3': {
249
+ return '3';
250
+ }
251
+ case 'selector_emissionClass_euro4':
252
+ case 'selector_unknown': {
253
+ return '4';
254
+ }
255
+ case 'selector_emissionClass_euro5': {
256
+ return '5';
257
+ }
258
+ case 'selector_emissionClass_euro6': {
259
+ return '6';
260
+ }
261
+ case 'selector_emissionClass_euro6b': {
262
+ return '6b';
263
+ }
264
+ case 'selector_emissionClass_euro6c': {
265
+ return '6c';
266
+ }
267
+ case 'selector_emissionClass_euro6d': {
268
+ return '6d';
269
+ }
270
+ case 'selector_emissionClass_euro6d_temp': {
271
+ return '6d-TEMP';
272
+ }
273
+ }
274
+ }
275
+
276
+ private getEnergyEfficiencyClass(car: DataSchemes.Car): string {
277
+ const energyEfficiencyClass = get(car, 'environmentEmissions.energyEfficiencyClass', 'selector_unknown');
278
+ switch (energyEfficiencyClass) {
279
+ case 'selector_energyEfficiencyClass_appp': {
280
+ return 'A+++';
281
+ }
282
+ case 'selector_energyEfficiencyClass_app': {
283
+ return 'A++';
284
+ }
285
+ case 'selector_energyEfficiencyClass_ap': {
286
+ return 'A+';
287
+ }
288
+ case 'selector_unknown':
289
+ case null: {
290
+ return 'C';
291
+ }
292
+ default: {
293
+ return energyEfficiencyClass.slice(-1).toUpperCase();
294
+ }
295
+ }
296
+ }
297
+
298
+ private getEmissionSticker(car: DataSchemes.Car): string | void {
299
+ const emissionSticker = get(car, 'environmentEmissions.emissionSticker', 'selector_unknown');
300
+ switch (emissionSticker) {
301
+ case 'selector_emissionSticker_no': {
302
+ return '1';
303
+ }
304
+ case 'selector_emissionSticker_red': {
305
+ return '2';
306
+ }
307
+ case 'selector_emissionSticker_yellow':
308
+ case 'selector_unknown': {
309
+ return '3';
310
+ }
311
+ case 'selector_emissionSticker_green': {
312
+ return '4';
313
+ }
314
+ }
315
+ }
316
+
317
+ private getCO2(car: DataSchemes.Car): string {
318
+ const consumptionFuel = get(car, 'consumption.fuel', 'selector_unknown');
319
+ if (consumptionFuel === 'selector_fuel_electrical') {
320
+ return '0';
321
+ }
322
+ return String(get(car, 'environmentEmissions.co2'));
323
+ }
324
+
325
+ private getNumberOfPreviousOwners(car: DataSchemes.Car): string {
326
+ const usageType = this.getUsageType(car);
327
+ if (usageType === '1' || !get(car, 'mainData.numberOfPreviousOwners')) {
328
+ return '0';
329
+ }
330
+ return String(get(car, 'mainData.numberOfPreviousOwners'));
331
+ }
332
+
333
+ private getUsageType(car: DataSchemes.Car): string {
334
+ const firstRegistration = get(car, 'mainData.firstRegistration');
335
+ if (firstRegistration) {
336
+ return '0';
337
+ }
338
+ return '1';
339
+ }
340
+
341
+ private getVatRate(car: DataSchemes.Car): string {
342
+ const isVatDeductible = get(car, 'price.vatDeductible', false);
343
+ if (isVatDeductible) {
344
+ return String(car.price.vatRate || 19);
345
+ }
346
+ return '0';
347
+ }
348
+
349
+ private getCurrentSalesPrice(car: DataSchemes.Car): string {
350
+ const vat = get(car, 'price.vatDeductible', false);
351
+ const currentSalesPrice = get(car, 'price.currentSalesPrice');
352
+ if (vat) {
353
+ const vatRate = get(car, 'price.vatRate', 19);
354
+ const price = (currentSalesPrice * 100) / (100 + vatRate);
355
+ return String(price.toFixed(2));
356
+ }
357
+ return String(currentSalesPrice);
358
+ }
359
+
360
+ private getVinCode(car: DataSchemes.Car): string {
361
+ const vin = get(car, 'metaData.vin', '');
362
+
363
+ if (this.configService.config.campaign === 'marktde' && !vin) {
364
+ const fakeVin = get(car, 'marketing.fakeVIN', '');
365
+ if (!isEmpty(fakeVin)) {
366
+ return fakeVin;
367
+ }
368
+ return vinGenerator.generateVin();
369
+ }
370
+
371
+ return vin;
372
+ }
373
+
374
+ private getAutodeUrl(car: DataSchemes.Car): string {
375
+ return `https://www.${this.configService.config.domain}${car._id}?source=classifieds&medium=listing&campaign=${this.configService.config.campaign}`;
376
+ }
377
+
378
+ private async getImages(car: DataSchemes.Car, images: ImboImageResponseInterface[]): Promise<string | null> {
379
+ const transformedImages = await this.imagesService.getImages(car, images, this.configService.config.imageType, false);
380
+
381
+ if (this.configService.config.campaign == ExportType.EVIUM) return null;
382
+
383
+ if (isNotEmptyArray(transformedImages)) {
384
+ return transformedImages.map((el: string) => el).join(';');
385
+ }
386
+ return null;
387
+ }
388
+
389
+ private stringToString(fieldValue: string): string {
390
+ return isEmpty(fieldValue) ? null : fieldValue;
391
+ }
392
+
393
+ private numberToString(fieldValue: number): string {
394
+ return String(fieldValue);
395
+ }
396
+
397
+ private booleanToString(fieldValue: boolean): string {
398
+ return String(Number(fieldValue));
399
+ }
400
+
401
+ private selectorToSelector(fieldValue: string, rule: MappingRules): string | void {
402
+ if (fieldValue === 'selector_unknown' && rule.autodeField === 'heatingCooling.climatisation') {
403
+ return '';
404
+ }
405
+
406
+ if (fieldValue === 'selector_unknown' || fieldValue === 'selector_interiorColor_rainbow') {
407
+ return '';
408
+ }
409
+ const pairs = rule.selectors.map(p => p.split(':').reverse());
410
+ const [match] = pairs.filter(p => p[0] === fieldValue);
411
+ // consider adding values not mapped in table
412
+ if (match) {
413
+ return match[1];
414
+ }
415
+ }
416
+
417
+ private selectorToString(fieldValue: string, rule: MappingRules): string {
418
+ return this.booleanToString(fieldValue === rule.autodeFieldSelector);
419
+ }
420
+
421
+ private selectorArrayToSelector(fieldValue: string[], rule: MappingRules): string | void {
422
+ const lastVal = fieldValue[fieldValue.length - 1];
423
+ const match = this.selectorToSelector(lastVal, rule);
424
+
425
+ if (match && match !== 'default') {
426
+ return match;
427
+ }
428
+ }
429
+
430
+ private selectorArrayToString(fieldValue: string[], rule: MappingRules): string {
431
+ const match = fieldValue.includes(rule.autodeFieldSelector);
432
+ return this.booleanToString(match);
433
+ }
434
+
435
+ private dateToString(fieldValue: number, rule: MappingRules): string {
436
+ const date = new Date(fieldValue * 1000);
437
+ if (["['8']"].includes(rule.importField)) {
438
+ if (!fieldValue) {
439
+ const now = new Date()
440
+ const month = String(now.getMonth() + 1);
441
+ const year = now.getFullYear();
442
+ return `${month}.${year}`;
443
+ }
444
+ }
445
+ if (["['6']", "['7']"].includes(rule.importField)) {
446
+ return `${date.getMonth() + 1}.${date.getFullYear()}`;
447
+ }
448
+ return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`;
449
+ }
450
+
451
+ private stringToMobile_model(fieldValue: string): string {
452
+ const model = fieldValue;
453
+ const subModel = get(this.currentCar, 'mainData.subModel');
454
+ return subModel && subModel !== model ? `${model} ${subModel}` : model;
455
+ }
456
+
457
+ private numberToMobile_consumption(fieldValue: number): string {
458
+ const consumption = this.numberToString(fieldValue);
459
+ return consumption.replace('.', ',');
460
+ }
461
+
462
+ private booleanToMobile_vatCalc(fieldValue: boolean): string {
463
+ return this.booleanToString(fieldValue);
464
+ }
465
+
466
+ private selectorToCategoryOrClass_selector(fieldValue: string): string {
467
+ const rules: { [key: string]: string } = {
468
+ selector_category_convertible: 'Cabrio',
469
+ selector_category_combi: 'Kombi',
470
+ selector_category_sedan: 'Limousine',
471
+ selector_category_suv: 'OffRoad',
472
+ selector_category_compact: 'SmallCar',
473
+ selector_category_coupe: 'SportsCar',
474
+ selector_category_van: 'Van',
475
+ selector_category_minibus: 'Van',
476
+ selector_category_transporter: 'OtherCar',
477
+ selector_unknown: 'OtherCar',
478
+ };
479
+
480
+ const found = rules[fieldValue];
481
+ if (found) {
482
+ return `Car.${found}`;
483
+ } else {
484
+ LoggerService.log(`Missing mapping for: ${fieldValue}`);
485
+ return 'Car.OtherCar';
486
+ }
487
+ }
488
+
489
+ private getGearBox(car: DataSchemes.Car): unknown {
490
+ const rules: { [x: string]: string } = {
491
+ selector_gearbox_manualShift: '1',
492
+ selector_gearbox_semiAutomatic: '2',
493
+ selector_gearbox_automatic: '3',
494
+ selector_unknown: '0',
495
+ };
496
+ const autodeGearboxSelector = get(car, 'driveSuspension.gearbox', 'selector_unknown');
497
+
498
+ return rules[autodeGearboxSelector];
499
+ }
500
+
501
+ private getMilage(car: DataSchemes.Car): string {
502
+ // TODO:: fix if cbd
503
+ const mileage = car.mainData.mileage;
504
+ if (mileage <= 0) {
505
+ return '0';
506
+ }
507
+ return mileage.toString();
508
+ }
47
509
  }