@bytebrand/export-cars 1.21.6 → 1.21.8

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