@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,655 +1,655 @@
1
- import { Cache } from 'cache-manager';
2
- import * as moment from 'moment';
3
- import {
4
- escape,
5
- findIndex,
6
- get,
7
- includes,
8
- isArray,
9
- isBoolean,
10
- isEmpty,
11
- isString,
12
- join,
13
- toSafeInteger,
14
- toString,
15
- trim,
16
- truncate,
17
- } from 'lodash';
18
-
19
- import { CACHE_MANAGER } from '@nestjs/cache-manager';
20
- import { Inject, Injectable } from '@nestjs/common';
21
-
22
- import { DePostalCodesEntryInterface } from '@bytebrand/car-basic-data-scope/dist/interfaces/DePostalCodesInterface';
23
- import { de, deVehicleDetailsPage, deVehicleProps } from '@bytebrand/i18n-dictionaries';
24
- import { DePostalCodesService } from '@bytebrand/car-basic-data-scope';
25
- import { ImboImageResponseInterface } from '@bytebrand/chunkable';
26
- import { TranslatorExtended } from '@bytebrand/schema-tools';
27
- import { DataSchemes } from '@bytebrand/car-schema';
28
-
29
- import { Description } from '../interfaces/description.interface';
30
- import { FbDescription } from './fb-description.transformer';
31
- import { ConfigService, ImagesService } from '../services';
32
- import { FbEquipment } from './fb-equipment.transformer';
33
- import { isValidVin } from '../utils';
34
- import { UserService } from 'src/services';
35
- import { ImageType } from '../enums';
36
- import {
37
- DEFAULT_ADDR1,
38
- DEFAULT_CITY,
39
- DEFAULT_ZIP,
40
- FB_MAX_TRIM_LENGTH,
41
- FB_MIN_MILEAGES,
42
- TIMESTAMP_FIRST_YEAR,
43
- } from '../constants/fb.constants';
44
-
45
- @Injectable()
46
- export class FbTransformer {
47
- private translator: TranslatorExtended;
48
- private postalCodesService: DePostalCodesService;
49
- private imagesService: ImagesService;
50
-
51
- constructor(
52
- @Inject(CACHE_MANAGER) private cacheManager: Cache,
53
- private readonly configService: ConfigService,
54
- private readonly userService: UserService,
55
- ) {
56
- this.imagesService = new ImagesService(configService, userService);
57
- const dict = { ...de, ...deVehicleProps, ...deVehicleDetailsPage };
58
- this.translator = new TranslatorExtended(dict);
59
- this.postalCodesService = DePostalCodesService.getInstance();
60
- }
61
-
62
- async transform({
63
- car,
64
- images,
65
- owner,
66
- }: {
67
- car: DataSchemes.Car;
68
- images: ImboImageResponseInterface[];
69
- owner: unknown;
70
- }): Promise<unknown> {
71
- const autodeId = toString(get(car, '_id', ''));
72
- const year = this.getYear(car);
73
- const dealerInfo = this.getDealerItems(car);
74
- // console.time(`Address ${car._id}`);
75
- const address = await this.getAddress(car, owner);
76
- // console.timeEnd(`Address ${car._id}`);
77
- // console.time(`Images ${car._id}`);
78
- const image_links = await this.imagesService.getImages(car, images, ImageType.marketing, false);
79
- // console.timeEnd(`Images ${car._id}`);
80
- const transformedImages = image_links.map(el => {
81
- return { url: el };
82
- });
83
-
84
- return {
85
- fb_page_id: '176306386132',
86
- ...address,
87
- ...dealerInfo,
88
- vehicle_id: get(car, 'metaData.vehicleId', autodeId),
89
- title: this.getTitle(car),
90
- features: this.getHighlights(car),
91
- description: this.getDescription(car), // be careful it changes car object
92
- url: `https://www.${this.configService.config.domain}/search/vehicle/${autodeId}`,
93
- dealer_url: `https://www.${this.configService.config.domain}/search/vehicle/${autodeId}?source=FACEBOOK&medium=direkt&campaign=marketplace_link`,
94
- make: toString(get(car, 'mainData.make', '')),
95
- model: toString(get(car, 'mainData.model', '')),
96
- trim: this.getTrim(car),
97
- image: transformedImages,
98
- year: toString(year),
99
- mileage: {
100
- value: this.getMileage(car),
101
- unit: 'KM',
102
- },
103
- drivetrain: this.mapDrivetrain(car),
104
- vin: this.getVin(car),
105
- body_style: this.mapBodyStyle(car),
106
- fuel_type: this.mapFuel(car),
107
- transmission: this.mapTransmission(car),
108
- price: this.getPrice(car),
109
- exterior_color: this.getExteriorColor(car),
110
- interior_color: this.getInteriorColor(car),
111
- state_of_vehicle: this.mapState(car),
112
- energy_efficiency_class_eu: this.getEnergyEfficiencyClass(car),
113
- vehicle_specifications: [
114
- {
115
- type: 'gas_mileage_combined',
116
- units: 'l/100km',
117
- value: get(car, 'consumption.consumptionCombined', ''),
118
- },
119
- {
120
- type: 'gas_mileage_city',
121
- units: 'l/100km',
122
- value: get(car, 'consumption.consumptionInner', ''),
123
- },
124
- {
125
- type: 'gas_mileage_highway',
126
- units: 'l/100km',
127
- value: get(car, 'consumption.consumptionOuter', ''),
128
- },
129
- {
130
- type: 'CO2_emissions',
131
- units: 'g/km',
132
- value: get(car, 'environmentEmissions.co2', ''),
133
- },
134
- ],
135
- };
136
- }
137
-
138
- private getYear(car: DataSchemes.Car): number {
139
- const firstModelsProductionDate = get(car, 'mainData.firstModelsProductionDate', 0) as number;
140
- const firstModelsProductionYear = moment(firstModelsProductionDate * 1000).format('YYYY');
141
-
142
- const firstRegistration = get(car, 'mainData.firstRegistration', 0) as number;
143
- const firstRegistrationYear = moment(firstRegistration * 1000).format('YYYY');
144
-
145
- const constructionYear = get(car, 'mainData.constructionYear', '') as string;
146
-
147
- const metaCreationDate = get(car, 'metaData.creationDate') as number;
148
- const metaCreationYear = moment(metaCreationDate * 1000).format('YYYY');
149
-
150
- const currentYear = moment().format('YYYY');
151
-
152
- return getFirstValidYear(
153
- firstModelsProductionYear,
154
- firstRegistrationYear,
155
- constructionYear,
156
- metaCreationYear,
157
- currentYear,
158
- );
159
- }
160
-
161
- private getDealerItems(car: DataSchemes.Car): {
162
- dealer_communication_channel: string;
163
- dealer_id: unknown;
164
- dealer_privacy_policy_url: string;
165
- dealer_email: string;
166
- dealer_phone: string;
167
- } {
168
- return {
169
- dealer_communication_channel: 'LEAD_FORM',
170
- dealer_id: get(car, 'metaData.ownerId'),
171
- dealer_privacy_policy_url: 'https://www.auto.de/einwilligungserklaerung-datenschutz-agb/',
172
- dealer_email: 'auto@auto.de',
173
- dealer_phone: '+498000008300',
174
- };
175
- }
176
-
177
- private getTitle(car: DataSchemes.Car): string {
178
- const year = toString(get(car, 'mainData.constructionYear', ''));
179
- const make = toString(get(car, 'mainData.make', ''));
180
- const model = toString(get(car, 'mainData.model', ''));
181
- const subModel = toString(get(car, 'mainData.subModel', ''));
182
- return trim(escape(join([year, make, model, subModel], ' ')));
183
- }
184
-
185
- private getTrim(car: DataSchemes.Car): string {
186
- const trim = toString(get(car, 'mainData.subModel', ''));
187
- return truncate(trim, { length: FB_MAX_TRIM_LENGTH, separator: ' ' });
188
- }
189
-
190
- private getDescription(car: DataSchemes.Car): Description[] {
191
- return FbDescription.create(car, this.translator, new FbEquipment()).getDescriptionArray();
192
- }
193
-
194
- private mapDrivetrain(car: DataSchemes.Car): string {
195
- const available = {
196
- selector_unknown: 'Other',
197
- selector_driveType_frontWheelDrive: 'FWD',
198
- selector_driveType_rearWheelDrive: 'RWD',
199
- selector_driveType_allWheelDrive: 'AWD',
200
- };
201
- const driveType = get(car, 'driveSuspension.driveType', 'selector_unknown');
202
- return get(available, driveType);
203
- }
204
-
205
- private getVin(car: DataSchemes.Car): string {
206
- let vin: string = get(car, 'metaData.vin', '');
207
-
208
- if (!isValidVin(vin)) {
209
- vin = get(car, 'marketing.fakeVIN', '');
210
- }
211
-
212
- return vin;
213
- }
214
-
215
- private mapBodyStyle(car: DataSchemes.Car): string {
216
- const available = {
217
- selector_unknown: 'OTHER',
218
- selector_category_combi: 'WAGON',
219
- selector_category_sedan: 'SEDAN',
220
- selector_category_compact: 'SMALL_CAR',
221
- selector_category_coupe: 'COUPE',
222
- selector_category_convertible: 'CONVERTIBLE',
223
- selector_category_suv: 'SUV',
224
- selector_category_van: 'VAN',
225
- selector_category_transporter: 'TRUCK',
226
- selector_category_minibus: 'MINIVAN',
227
- };
228
- const category = get(car, 'mainData.category', 'selector_unknown');
229
- return get(available, category);
230
- }
231
-
232
- private mapFuel(car: DataSchemes.Car): string {
233
- const available = {
234
- selector_fuel_petrol: 'GASOLINE',
235
- selector_fuel_diesel: 'DIESEL',
236
- selector_fuel_electrical: 'ELECTRIC',
237
- selector_fuel_hybridElectricalPetrol: 'HYBRID',
238
- selector_fuel_hybridElectricalDiesel: 'HYBRID',
239
- selector_fuel_carGas: 'OTHER',
240
- selector_fuel_naturalGas: 'OTHER',
241
- selector_fuel_ethanol: 'OTHER',
242
- selector_fuel_hydrogen: 'OTHER',
243
- selector_unknown: 'OTHER',
244
- };
245
- const fuel = get(car, 'consumption.fuel', 'selector_unknown');
246
- return get(available, fuel);
247
- }
248
-
249
- private mapTransmission(car: DataSchemes.Car): string {
250
- const available = {
251
- selector_gearbox_manualShift: 'MANUAL',
252
- selector_gearbox_semiAutomatic: 'AUTOMATIC',
253
- selector_gearbox_automatic: 'AUTOMATIC',
254
- selector_unknown: 'OTHER',
255
- };
256
- const transmission = get(car, 'driveSuspension.gearbox', 'selector_unknown');
257
- return get(available, transmission);
258
- }
259
-
260
- private getPrice(car: DataSchemes.Car): string {
261
- const price = get(car, 'price.currentSalesPrice', 0);
262
- return `${price} EUR`;
263
- }
264
-
265
- private async getAddress(
266
- car: DataSchemes.Car,
267
- owner: unknown,
268
- ): Promise<{
269
- address: { city: string; region: string; country: string; postal_code: string; addr1: string; };
270
- latitude: number;
271
- longitude: number;
272
- }> {
273
- let addressPath = 'businessAddress';
274
- if (this.isEmptyAddress(get(owner, addressPath, {}))) {
275
- addressPath = 'mainAddress';
276
- }
277
- let city: string = get(owner, `${addressPath}.city`, '');
278
- let zip: string = get(owner, `${addressPath}.zip`, '');
279
- let addr1 = get(owner, `${addressPath}.street`, '');
280
- let locationEntity = await this.getLocationByInfo({ city, zip: toSafeInteger(zip) });
281
-
282
- if (this.isEmptyLocation(locationEntity)) {
283
- addr1 = DEFAULT_ADDR1;
284
- city = DEFAULT_CITY;
285
- zip = DEFAULT_ZIP;
286
- locationEntity = await this.getLocationByInfo({ city, zip: toSafeInteger(zip) });
287
- }
288
-
289
- const coords = await this.getCoords(car, locationEntity);
290
- const country: string = get(owner, `${addressPath}.country.name`, '');
291
- return {
292
- ...coords,
293
- address: {
294
- addr1,
295
- city,
296
- region: get(locationEntity, 'Place Name', ''),
297
- country: country || 'Deutschland',
298
- postal_code: zip,
299
- },
300
- };
301
- }
302
-
303
- private isEmptyLocation(location: DePostalCodesEntryInterface): boolean {
304
- if (!location) return true;
305
- return !location['Place Name'] || !(location.Latitude && location.Longitude);
306
- }
307
-
308
- private isEmptyAddress(address: unknown): boolean {
309
- if (!address) return true;
310
- return ['city', 'zip', 'country'].every((key: string) => isEmpty(address[key]));
311
- }
312
-
313
- private async getCoords(
314
- car: DataSchemes.Car,
315
- locationEntry: DePostalCodesEntryInterface,
316
- ): Promise<Pick<DataSchemes.Location, 'latitude' | 'longitude'>> {
317
- const data: Pick<DataSchemes.Location, 'latitude' | 'longitude'> = {
318
- latitude: get(car, 'location.latitude'),
319
- longitude: get(car, 'location.longitude'),
320
- };
321
-
322
- if (data.latitude && data.longitude) {
323
- return data;
324
- }
325
-
326
- return {
327
- longitude: get(locationEntry, 'Longitude'),
328
- latitude: get(locationEntry, 'Latitude'),
329
- };
330
- }
331
-
332
- private async getLocationByInfo(info: { zip: number; city: string }): Promise<DePostalCodesEntryInterface> {
333
- const cacheKey = `${info.city}_${info.zip}`;
334
-
335
- let entry = await this.cacheManager.get(cacheKey);
336
- if (entry) {
337
- return entry as DePostalCodesEntryInterface;
338
- }
339
- if (entry === 'no_data') {
340
- return undefined;
341
- }
342
-
343
- entry = await this.postalCodesService.getLocationByPostalCode(info.zip);
344
- entry && (await this.cacheManager.set(cacheKey, entry));
345
-
346
- if (!entry) {
347
- entry = await this.postalCodesService.getLocationByCity(info.city);
348
- if (entry) {
349
- await this.cacheManager.set(cacheKey, entry);
350
- } else {
351
- await this.cacheManager.set(cacheKey, 'no_data');
352
- }
353
- }
354
- return entry as DePostalCodesEntryInterface;
355
- }
356
-
357
- private getExteriorColor(car: DataSchemes.Car): string {
358
- let colorSelection = get(car, 'exterior.exteriorColor', 'other');
359
- if (!colorSelection || colorSelection === 'selector_unknown') {
360
- colorSelection = 'other';
361
- }
362
- return this.translator.translateVal(colorSelection);
363
- }
364
-
365
- private getInteriorColor(car: DataSchemes.Car): string {
366
- let colorSelection = get(car, 'interior.interiorColor', 'other');
367
- if (!colorSelection || colorSelection === 'selector_unknown') {
368
- colorSelection = 'other';
369
- }
370
- return this.translator.translateVal(colorSelection);
371
- }
372
-
373
- private mapState(car: DataSchemes.Car): string {
374
- const available = {
375
- selector_condition_new: 'CPO',
376
- selector_condition_used: 'USED',
377
- selector_unknown: 'USED',
378
- };
379
- const condition = get(car, 'mainData.condition', 'selector_unknown');
380
- return get(available, condition);
381
- }
382
-
383
- private getEnergyEfficiencyClass(car: DataSchemes.Car): string {
384
- const available = {
385
- selector_energyEfficiencyClass_appp: 'A+',
386
- selector_energyEfficiencyClass_app: 'A+',
387
- selector_energyEfficiencyClass_ap: 'A+',
388
- selector_energyEfficiencyClass_a: 'A',
389
- selector_energyEfficiencyClass_b: 'B',
390
- selector_energyEfficiencyClass_c: 'C',
391
- selector_energyEfficiencyClass_d: 'D',
392
- selector_energyEfficiencyClass_e: 'E',
393
- selector_energyEfficiencyClass_f: 'F',
394
- selector_energyEfficiencyClass_g: 'G',
395
- selector_unknown: 'G',
396
- };
397
- const energyClass = get(car, 'environmentEmissions.energyEfficiencyClass', 'selector_unknown');
398
- return get(available, energyClass);
399
- }
400
-
401
- private getMileage(car: DataSchemes.Car): number {
402
- const mileage = get(car, 'mainData.mileage', 0);
403
- return mileage <= FB_MIN_MILEAGES ? FB_MIN_MILEAGES + 1 : mileage;
404
- }
405
-
406
- private getHighlights(car: DataSchemes.Car): { type: unknown; value: unknown }[] {
407
- const facebookFeatures = [
408
- { key: 'assistants.abs', type: 'abs_brakes', value: 'ABS' },
409
- { key: 'assistants.adaptiveCruiseControl', type: 'adaptive_cruise_control', value: 'Abstandsregeltempomat' },
410
- { key: 'theftProtection.alarmSystem', type: 'alarm', value: 'Alarmanlage' },
411
- { key: 'multimedia.auxiliaryAudioInput', type: 'aux_audio_input', value: 'AUX-Anschluss' },
412
- { key: 'assistants.blindSpotDetection', type: 'blind_spot_alert', value: 'Totwinkel-Assistent' },
413
- { key: 'multimedia.bluetooth', type: 'bluetooth', value: 'Bluetooth' },
414
- { key: 'assistants.collisionAvoidance', type: 'collision_avoidance', value: 'Notbrems Assistent' },
415
- { key: 'assistants.cruiseControl', type: 'cruise_control', value: 'Tempomat' },
416
- { key: 'lights.daytimeRunningLights', type: 'daytime_running_lights', value: 'Tagfahrlicht' },
417
- { key: 'lights.daytimeRunningLightsLED', type: 'daytime_running_lights', value: 'Tagfahrlicht LED' },
418
- { key: 'lights.frontFogLights', type: 'fog_lights', value: 'Nebelscheinwerfer' },
419
- { key: 'wheel.heatedSteeringWheel', type: 'heated_steering_wheel', value: 'Lenkradheizung' },
420
- { key: 'lights.xenonHeadlights', type: 'hid_headlights', value: 'Xenonscheinwerfer' },
421
- { key: 'assistants.hillStartDrivingAssistant', type: 'hill_start_assist', value: 'Berganfahrhilfe' },
422
- { key: 'doors.keylessEntry', type: 'keyless_entry', value: 'Keyless-Go' },
423
- { key: 'doors.keylessEntry', type: 'keyless_start', value: 'Keyless-Go' },
424
- { key: 'assistants.laneAssistant', type: 'lane_keep_assist', value: 'Spurhalte-Assistent' },
425
- { key: 'lights.ledHeadlights', type: 'led_headlights', value: 'LED Scheinwerfer' },
426
- { key: 'window.sunroof', type: 'moon_roof', value: 'Schiebedach' },
427
- { key: 'multimedia.mp3Interface', type: 'mp3 MP3', value: 'Schnittstelle' },
428
- { key: 'multimedia.navigationSystem', type: 'navigation', value: 'Navigationssystem' },
429
- { key: 'assistants.nightVisionAssistant', type: 'night_view', value: 'Nachsicht Assistent' },
430
- { key: 'window.panoramicGlassRoof', type: 'panorama_roof', value: 'Panoramaschiebedach' },
431
- { key: 'doors.centralLocking', type: 'power_door_locks', value: 'Zentralverriegelung' },
432
- { key: 'doors.electricTailGate', type: 'power_liftgate', value: 'elektrische Heckklappe' },
433
- { key: 'assistants.powerAssistedSteering', type: 'power_steering', value: 'powerAssistedSteering' },
434
- { key: 'window.electricWindows', type: 'power_windows', value: 'Elektrische Fenster' },
435
- { key: 'extras.roofBars', type: 'roof_rack', value: 'Dachgepäckträger' },
436
- { key: 'extras.roofRails', type: 'roof_rack', value: 'Dachreling' },
437
- { key: 'assistants.esp', type: 'stability_control', value: 'ESP' },
438
- { key: 'multimedia.onBoardComputer', type: 'telematics', value: 'Bordcomputer' },
439
- { key: 'wheel.adjustableSteeringWheel', type: 'tilt_steering_wheel', value: 'höhenverst Lenkrad' },
440
- { key: 'window.tintedWindowBack', type: 'tinted_windows', value: 'getönte Scheiben hinten' },
441
- { key: 'assistants.tirePressureMonitor', type: 'tire_pressure_monitor', value: 'Reifendruckkontrolle' },
442
- { key: 'trailerCoupling.trailerCouplingFix', type: 'tow_package', value: 'Anhängerkupplung fest' },
443
- { key: 'trailerCoupling.trailerCouplingRemovable', type: 'tow_package', value: 'Anhängerkupplung abnehmbar' },
444
- { key: 'trailerCoupling.trailerCouplingRotatable', type: 'tow_package', value: 'Anhängerkupplung schwenkbar' },
445
- { key: 'assistants.tractionControlSystem', type: 'traction_control', value: 'Traktionskontrolle' },
446
- {
447
- key: 'heatingCooling.climatisation',
448
- type: 'air_conditioning',
449
- selector: 'selector_unknown',
450
- value: 'Unbekannt',
451
- },
452
- {
453
- key: 'heatingCooling.climatisation',
454
- type: 'air_conditioning',
455
- selector: 'selector_climatisation_no_airCondition',
456
- value: 'Keine Klimaanlage oder -automatik',
457
- },
458
- {
459
- key: 'heatingCooling.climatisation',
460
- type: 'air_conditioning',
461
- selector: 'selector_climatisation_airCondition',
462
- value: 'Klimaanlage oder -automatik',
463
- },
464
- {
465
- key: 'heatingCooling.climatisation',
466
- type: 'air_conditioning',
467
- selector: 'selector_climatisation_automaticAirCondition',
468
- value: 'Klimaautomatik',
469
- },
470
- {
471
- key: 'heatingCooling.climatisation',
472
- type: 'air_conditioning',
473
- selector: 'selector_climatisation_2_automaticAirCondition',
474
- value: '2-Zonen Klimaautomatik',
475
- },
476
- {
477
- key: 'heatingCooling.climatisation',
478
- type: 'air_conditioning',
479
- selector: 'selector_climatisation_3_automaticAirCondition',
480
- value: '3-Zonen Klimaautomatik',
481
- },
482
- {
483
- key: 'heatingCooling.climatisation',
484
- type: 'air_conditioning',
485
- selector: 'selector_climatisation_4_automaticAirCondition',
486
- value: '4-Zonen Klimaautomatik',
487
- },
488
- {
489
- key: 'wheels.summerTiresRims',
490
- type: 'alloy_wheels',
491
- selector: 'selector_tiresRims_alloy',
492
- value: 'Leichtmetallfelgen',
493
- },
494
- {
495
- key: 'assistants.parkingAssistants',
496
- type: 'backup_camera',
497
- selector: 'selector_parkingAssistants_camera',
498
- value: 'Rückfahrkamera',
499
- },
500
- {
501
- key: 'seats.driverSeats',
502
- type: 'heated_seats',
503
- selector: 'selector_driverSeats_electricHeated',
504
- value: 'Sitzheizung Fahrersitz',
505
- },
506
- {
507
- key: 'seats.coDriverSeats',
508
- type: 'heated_seats',
509
- selector: 'selector_coDriverSeats_electricHeated',
510
- value: 'Sitzheizung Beifahrersitz',
511
- },
512
- {
513
- key: 'seats.backSeats',
514
- type: 'heated_seats',
515
- selector: 'selector_backSeats_electricHeated',
516
- value: 'Sitzheizung Rücksitz',
517
- },
518
- {
519
- key: 'interior.interiorType',
520
- type: 'leather',
521
- selector: 'selector_interiorType_fullLeather',
522
- value: 'Vollleder',
523
- },
524
- {
525
- key: 'interior.interiorType',
526
- type: 'leather',
527
- selector: 'selector_interiorType_partLeather',
528
- value: 'Teilleder',
529
- },
530
- { key: 'assistants.parkingAssistants', type: 'park_assist', selector: 'selector_unknown', value: 'Unbekannt' },
531
- {
532
- key: 'assistants.parkingAssistants',
533
- type: 'park_assist',
534
- selector: 'selector_parkingAssistants_other',
535
- value: 'Andere',
536
- },
537
- {
538
- key: 'assistants.parkingAssistants',
539
- type: 'park_assist',
540
- selector: 'selector_parkingAssistants_front',
541
- value: 'Vorderseite',
542
- },
543
- {
544
- key: 'assistants.parkingAssistants',
545
- type: 'park_assist',
546
- selector: 'selector_parkingAssistants_rear',
547
- value: 'Rückseite',
548
- },
549
- {
550
- key: 'assistants.parkingAssistants',
551
- type: 'park_assist',
552
- selector: 'selector_parkingAssistants_camera',
553
- value: 'Kamera',
554
- },
555
- {
556
- key: 'assistants.parkingAssistants',
557
- type: 'park_assist',
558
- selector: 'selector_parkingAssistants_selfSteeringSystems',
559
- value: 'Selbstlenkende Systeme',
560
- },
561
- {
562
- key: 'assistants.parkingAssistants',
563
- type: 'park_assist',
564
- selector: 'selector_parkingAssistants_360Camera',
565
- value: '360° Kamera',
566
- },
567
- {
568
- key: 'seats.driverSeats',
569
- type: 'power_seats',
570
- selector: 'selector_driverSeats_electricAdjustable',
571
- value: 'Fahrersitz elektr. einstellbar',
572
- },
573
- {
574
- key: 'seats.coDriverSeats',
575
- type: 'power_seats',
576
- selector: 'selector_coDriverSeats_electricAdjustable',
577
- value: 'Beifahrersitz elektr. einstellbar',
578
- },
579
- {
580
- key: 'assistants.parkingAssistants',
581
- type: 'surround_view_camera',
582
- selector: 'selector_parkingAssistants_360Camera',
583
- value: '360° Kamera',
584
- },
585
- {
586
- key: 'seats.driverSeats',
587
- type: 'ventilated_seats',
588
- selector: 'selector_driverSeats_seatVentilationSystem',
589
- value: 'Sitzbelüftung Fahrersitz',
590
- },
591
- {
592
- key: 'seats.coDriverSeats',
593
- type: 'ventilated_seats',
594
- selector: 'selector_coDriverSeats_seatVentilationSystem',
595
- value: 'Sitzbelüftung Beifahrersitz',
596
- },
597
- {
598
- key: 'seats.backSeats',
599
- type: 'ventilated_seats',
600
- selector: 'selector_backSeats_seatVentilationSystem',
601
- value: 'Sitzbelüftung Rücksitz',
602
- },
603
- ];
604
- return facebookFeatures
605
- .map(feature => {
606
- const value = get(car, feature.key);
607
- if (isBoolean(value)) {
608
- return this.getBooleanValue(value, feature);
609
- }
610
- if (isString(value)) {
611
- return this.getStringValue(value, feature);
612
- }
613
- if (isArray(value) && includes(value, feature.selector)) {
614
- const index = findIndex(value, item => item === feature.selector);
615
- return this.getStringValue(value[index], feature);
616
- }
617
- return null;
618
- })
619
- .filter(el => el);
620
- }
621
-
622
- private getBooleanValue(
623
- value: boolean,
624
- feature:
625
- | { key: string; type: string; value: string; selector?: undefined }
626
- | { key: string; type: string; selector: string; value: string },
627
- ): { type: string; value: string } {
628
- if (value) {
629
- return {
630
- type: feature.type,
631
- value: feature.value,
632
- };
633
- }
634
- return null;
635
- }
636
-
637
- private getStringValue(
638
- selector: string,
639
- feature:
640
- | { key: string; type: string; value: string; selector?: undefined }
641
- | { key: string; type: string; selector: string; value: string },
642
- ): { type: string; value: string } {
643
- if (selector === feature.selector) {
644
- return {
645
- type: feature.type,
646
- value: feature.value,
647
- };
648
- }
649
- return null;
650
- }
651
- }
652
-
653
- function getFirstValidYear(...years: string[]): number {
654
- return years.map((year: string) => toSafeInteger(year)).filter((year: number) => year > TIMESTAMP_FIRST_YEAR)[0];
655
- }
1
+ import { Cache } from 'cache-manager';
2
+ import * as moment from 'moment';
3
+ import {
4
+ escape,
5
+ findIndex,
6
+ get,
7
+ includes,
8
+ isArray,
9
+ isBoolean,
10
+ isEmpty,
11
+ isString,
12
+ join,
13
+ toSafeInteger,
14
+ toString,
15
+ trim,
16
+ truncate,
17
+ } from 'lodash';
18
+
19
+ import { CACHE_MANAGER } from '@nestjs/cache-manager';
20
+ import { Inject, Injectable } from '@nestjs/common';
21
+
22
+ import { DePostalCodesEntryInterface } from '@bytebrand/car-basic-data-scope/dist/interfaces/DePostalCodesInterface';
23
+ import { de, deVehicleDetailsPage, deVehicleProps } from '@bytebrand/i18n-dictionaries';
24
+ import { DePostalCodesService } from '@bytebrand/car-basic-data-scope';
25
+ import { ImboImageResponseInterface } from '@bytebrand/chunkable';
26
+ import { TranslatorExtended } from '@bytebrand/schema-tools';
27
+ import { DataSchemes } from '@bytebrand/car-schema';
28
+
29
+ import { Description } from '../interfaces/description.interface';
30
+ import { FbDescription } from './fb-description.transformer';
31
+ import { ConfigService, ImagesService } from '../services';
32
+ import { FbEquipment } from './fb-equipment.transformer';
33
+ import { isValidVin } from '../utils';
34
+ import { UserService } from 'src/services';
35
+ import { ImageType } from '../enums';
36
+ import {
37
+ DEFAULT_ADDR1,
38
+ DEFAULT_CITY,
39
+ DEFAULT_ZIP,
40
+ FB_MAX_TRIM_LENGTH,
41
+ FB_MIN_MILEAGES,
42
+ TIMESTAMP_FIRST_YEAR,
43
+ } from '../constants/fb.constants';
44
+
45
+ @Injectable()
46
+ export class FbTransformer {
47
+ private translator: TranslatorExtended;
48
+ private postalCodesService: DePostalCodesService;
49
+ private imagesService: ImagesService;
50
+
51
+ constructor(
52
+ @Inject(CACHE_MANAGER) private cacheManager: Cache,
53
+ private readonly configService: ConfigService,
54
+ private readonly userService: UserService,
55
+ ) {
56
+ this.imagesService = new ImagesService(configService, userService);
57
+ const dict = { ...de, ...deVehicleProps, ...deVehicleDetailsPage };
58
+ this.translator = new TranslatorExtended(dict);
59
+ this.postalCodesService = DePostalCodesService.getInstance();
60
+ }
61
+
62
+ async transform({
63
+ car,
64
+ images,
65
+ owner,
66
+ }: {
67
+ car: DataSchemes.Car;
68
+ images: ImboImageResponseInterface[];
69
+ owner: unknown;
70
+ }): Promise<unknown> {
71
+ const autodeId = toString(get(car, '_id', ''));
72
+ const year = this.getYear(car);
73
+ const dealerInfo = this.getDealerItems(car);
74
+ // console.time(`Address ${car._id}`);
75
+ const address = await this.getAddress(car, owner);
76
+ // console.timeEnd(`Address ${car._id}`);
77
+ // console.time(`Images ${car._id}`);
78
+ const image_links = await this.imagesService.getImages(car, images, ImageType.marketing, false);
79
+ // console.timeEnd(`Images ${car._id}`);
80
+ const transformedImages = image_links.map(el => {
81
+ return { url: el };
82
+ });
83
+
84
+ return {
85
+ fb_page_id: '176306386132',
86
+ ...address,
87
+ ...dealerInfo,
88
+ vehicle_id: get(car, 'metaData.vehicleId', autodeId),
89
+ title: this.getTitle(car),
90
+ features: this.getHighlights(car),
91
+ description: this.getDescription(car), // be careful it changes car object
92
+ url: `https://www.${this.configService.config.domain}/search/vehicle/${autodeId}`,
93
+ dealer_url: `https://www.${this.configService.config.domain}/search/vehicle/${autodeId}?source=FACEBOOK&medium=direkt&campaign=marketplace_link`,
94
+ make: toString(get(car, 'mainData.make', '')),
95
+ model: toString(get(car, 'mainData.model', '')),
96
+ trim: this.getTrim(car),
97
+ image: transformedImages,
98
+ year: toString(year),
99
+ mileage: {
100
+ value: this.getMileage(car),
101
+ unit: 'KM',
102
+ },
103
+ drivetrain: this.mapDrivetrain(car),
104
+ vin: this.getVin(car),
105
+ body_style: this.mapBodyStyle(car),
106
+ fuel_type: this.mapFuel(car),
107
+ transmission: this.mapTransmission(car),
108
+ price: this.getPrice(car),
109
+ exterior_color: this.getExteriorColor(car),
110
+ interior_color: this.getInteriorColor(car),
111
+ state_of_vehicle: this.mapState(car),
112
+ energy_efficiency_class_eu: this.getEnergyEfficiencyClass(car),
113
+ vehicle_specifications: [
114
+ {
115
+ type: 'gas_mileage_combined',
116
+ units: 'l/100km',
117
+ value: get(car, 'consumption.consumptionCombined', ''),
118
+ },
119
+ {
120
+ type: 'gas_mileage_city',
121
+ units: 'l/100km',
122
+ value: get(car, 'consumption.consumptionInner', ''),
123
+ },
124
+ {
125
+ type: 'gas_mileage_highway',
126
+ units: 'l/100km',
127
+ value: get(car, 'consumption.consumptionOuter', ''),
128
+ },
129
+ {
130
+ type: 'CO2_emissions',
131
+ units: 'g/km',
132
+ value: get(car, 'environmentEmissions.co2', ''),
133
+ },
134
+ ],
135
+ };
136
+ }
137
+
138
+ private getYear(car: DataSchemes.Car): number {
139
+ const firstModelsProductionDate = get(car, 'mainData.firstModelsProductionDate', 0) as number;
140
+ const firstModelsProductionYear = moment(firstModelsProductionDate * 1000).format('YYYY');
141
+
142
+ const firstRegistration = get(car, 'mainData.firstRegistration', 0) as number;
143
+ const firstRegistrationYear = moment(firstRegistration * 1000).format('YYYY');
144
+
145
+ const constructionYear = get(car, 'mainData.constructionYear', '') as string;
146
+
147
+ const metaCreationDate = get(car, 'metaData.creationDate') as number;
148
+ const metaCreationYear = moment(metaCreationDate * 1000).format('YYYY');
149
+
150
+ const currentYear = moment().format('YYYY');
151
+
152
+ return getFirstValidYear(
153
+ firstModelsProductionYear,
154
+ firstRegistrationYear,
155
+ constructionYear,
156
+ metaCreationYear,
157
+ currentYear,
158
+ );
159
+ }
160
+
161
+ private getDealerItems(car: DataSchemes.Car): {
162
+ dealer_communication_channel: string;
163
+ dealer_id: unknown;
164
+ dealer_privacy_policy_url: string;
165
+ dealer_email: string;
166
+ dealer_phone: string;
167
+ } {
168
+ return {
169
+ dealer_communication_channel: 'LEAD_FORM',
170
+ dealer_id: get(car, 'metaData.ownerId'),
171
+ dealer_privacy_policy_url: 'https://www.auto.de/einwilligungserklaerung-datenschutz-agb/',
172
+ dealer_email: 'auto@auto.de',
173
+ dealer_phone: '+498000008300',
174
+ };
175
+ }
176
+
177
+ private getTitle(car: DataSchemes.Car): string {
178
+ const year = toString(get(car, 'mainData.constructionYear', ''));
179
+ const make = toString(get(car, 'mainData.make', ''));
180
+ const model = toString(get(car, 'mainData.model', ''));
181
+ const subModel = toString(get(car, 'mainData.subModel', ''));
182
+ return trim(escape(join([year, make, model, subModel], ' ')));
183
+ }
184
+
185
+ private getTrim(car: DataSchemes.Car): string {
186
+ const trim = toString(get(car, 'mainData.subModel', ''));
187
+ return truncate(trim, { length: FB_MAX_TRIM_LENGTH, separator: ' ' });
188
+ }
189
+
190
+ private getDescription(car: DataSchemes.Car): Description[] {
191
+ return FbDescription.create(car, this.translator, new FbEquipment()).getDescriptionArray();
192
+ }
193
+
194
+ private mapDrivetrain(car: DataSchemes.Car): string {
195
+ const available = {
196
+ selector_unknown: 'Other',
197
+ selector_driveType_frontWheelDrive: 'FWD',
198
+ selector_driveType_rearWheelDrive: 'RWD',
199
+ selector_driveType_allWheelDrive: 'AWD',
200
+ };
201
+ const driveType = get(car, 'driveSuspension.driveType', 'selector_unknown');
202
+ return get(available, driveType);
203
+ }
204
+
205
+ private getVin(car: DataSchemes.Car): string {
206
+ let vin: string = get(car, 'metaData.vin', '');
207
+
208
+ if (!isValidVin(vin)) {
209
+ vin = get(car, 'marketing.fakeVIN', '');
210
+ }
211
+
212
+ return vin;
213
+ }
214
+
215
+ private mapBodyStyle(car: DataSchemes.Car): string {
216
+ const available = {
217
+ selector_unknown: 'OTHER',
218
+ selector_category_combi: 'WAGON',
219
+ selector_category_sedan: 'SEDAN',
220
+ selector_category_compact: 'SMALL_CAR',
221
+ selector_category_coupe: 'COUPE',
222
+ selector_category_convertible: 'CONVERTIBLE',
223
+ selector_category_suv: 'SUV',
224
+ selector_category_van: 'VAN',
225
+ selector_category_transporter: 'TRUCK',
226
+ selector_category_minibus: 'MINIVAN',
227
+ };
228
+ const category = get(car, 'mainData.category', 'selector_unknown');
229
+ return get(available, category);
230
+ }
231
+
232
+ private mapFuel(car: DataSchemes.Car): string {
233
+ const available = {
234
+ selector_fuel_petrol: 'GASOLINE',
235
+ selector_fuel_diesel: 'DIESEL',
236
+ selector_fuel_electrical: 'ELECTRIC',
237
+ selector_fuel_hybridElectricalPetrol: 'HYBRID',
238
+ selector_fuel_hybridElectricalDiesel: 'HYBRID',
239
+ selector_fuel_carGas: 'OTHER',
240
+ selector_fuel_naturalGas: 'OTHER',
241
+ selector_fuel_ethanol: 'OTHER',
242
+ selector_fuel_hydrogen: 'OTHER',
243
+ selector_unknown: 'OTHER',
244
+ };
245
+ const fuel = get(car, 'consumption.fuel', 'selector_unknown');
246
+ return get(available, fuel);
247
+ }
248
+
249
+ private mapTransmission(car: DataSchemes.Car): string {
250
+ const available = {
251
+ selector_gearbox_manualShift: 'MANUAL',
252
+ selector_gearbox_semiAutomatic: 'AUTOMATIC',
253
+ selector_gearbox_automatic: 'AUTOMATIC',
254
+ selector_unknown: 'OTHER',
255
+ };
256
+ const transmission = get(car, 'driveSuspension.gearbox', 'selector_unknown');
257
+ return get(available, transmission);
258
+ }
259
+
260
+ private getPrice(car: DataSchemes.Car): string {
261
+ const price = get(car, 'price.currentSalesPrice', 0);
262
+ return `${price} EUR`;
263
+ }
264
+
265
+ private async getAddress(
266
+ car: DataSchemes.Car,
267
+ owner: unknown,
268
+ ): Promise<{
269
+ address: { city: string; region: string; country: string; postal_code: string; addr1: string; };
270
+ latitude: number;
271
+ longitude: number;
272
+ }> {
273
+ let addressPath = 'businessAddress';
274
+ if (this.isEmptyAddress(get(owner, addressPath, {}))) {
275
+ addressPath = 'mainAddress';
276
+ }
277
+ let city: string = get(owner, `${addressPath}.city`, '');
278
+ let zip: string = get(owner, `${addressPath}.zip`, '');
279
+ let addr1 = get(owner, `${addressPath}.street`, '');
280
+ let locationEntity = await this.getLocationByInfo({ city, zip: toSafeInteger(zip) });
281
+
282
+ if (this.isEmptyLocation(locationEntity)) {
283
+ addr1 = DEFAULT_ADDR1;
284
+ city = DEFAULT_CITY;
285
+ zip = DEFAULT_ZIP;
286
+ locationEntity = await this.getLocationByInfo({ city, zip: toSafeInteger(zip) });
287
+ }
288
+
289
+ const coords = await this.getCoords(car, locationEntity);
290
+ const country: string = get(owner, `${addressPath}.country.name`, '');
291
+ return {
292
+ ...coords,
293
+ address: {
294
+ addr1,
295
+ city,
296
+ region: get(locationEntity, 'Place Name', ''),
297
+ country: country || 'Deutschland',
298
+ postal_code: zip,
299
+ },
300
+ };
301
+ }
302
+
303
+ private isEmptyLocation(location: DePostalCodesEntryInterface): boolean {
304
+ if (!location) return true;
305
+ return !location['Place Name'] || !(location.Latitude && location.Longitude);
306
+ }
307
+
308
+ private isEmptyAddress(address: unknown): boolean {
309
+ if (!address) return true;
310
+ return ['city', 'zip', 'country'].every((key: string) => isEmpty(address[key]));
311
+ }
312
+
313
+ private async getCoords(
314
+ car: DataSchemes.Car,
315
+ locationEntry: DePostalCodesEntryInterface,
316
+ ): Promise<Pick<DataSchemes.Location, 'latitude' | 'longitude'>> {
317
+ const data: Pick<DataSchemes.Location, 'latitude' | 'longitude'> = {
318
+ latitude: get(car, 'location.latitude'),
319
+ longitude: get(car, 'location.longitude'),
320
+ };
321
+
322
+ if (data.latitude && data.longitude) {
323
+ return data;
324
+ }
325
+
326
+ return {
327
+ longitude: get(locationEntry, 'Longitude'),
328
+ latitude: get(locationEntry, 'Latitude'),
329
+ };
330
+ }
331
+
332
+ private async getLocationByInfo(info: { zip: number; city: string }): Promise<DePostalCodesEntryInterface> {
333
+ const cacheKey = `${info.city}_${info.zip}`;
334
+
335
+ let entry = await this.cacheManager.get(cacheKey);
336
+ if (entry) {
337
+ return entry as DePostalCodesEntryInterface;
338
+ }
339
+ if (entry === 'no_data') {
340
+ return undefined;
341
+ }
342
+
343
+ entry = await this.postalCodesService.getLocationByPostalCode(info.zip);
344
+ entry && (await this.cacheManager.set(cacheKey, entry));
345
+
346
+ if (!entry) {
347
+ entry = await this.postalCodesService.getLocationByCity(info.city);
348
+ if (entry) {
349
+ await this.cacheManager.set(cacheKey, entry);
350
+ } else {
351
+ await this.cacheManager.set(cacheKey, 'no_data');
352
+ }
353
+ }
354
+ return entry as DePostalCodesEntryInterface;
355
+ }
356
+
357
+ private getExteriorColor(car: DataSchemes.Car): string {
358
+ let colorSelection = get(car, 'exterior.exteriorColor', 'other');
359
+ if (!colorSelection || colorSelection === 'selector_unknown') {
360
+ colorSelection = 'other';
361
+ }
362
+ return this.translator.translateVal(colorSelection);
363
+ }
364
+
365
+ private getInteriorColor(car: DataSchemes.Car): string {
366
+ let colorSelection = get(car, 'interior.interiorColor', 'other');
367
+ if (!colorSelection || colorSelection === 'selector_unknown') {
368
+ colorSelection = 'other';
369
+ }
370
+ return this.translator.translateVal(colorSelection);
371
+ }
372
+
373
+ private mapState(car: DataSchemes.Car): string {
374
+ const available = {
375
+ selector_condition_new: 'CPO',
376
+ selector_condition_used: 'USED',
377
+ selector_unknown: 'USED',
378
+ };
379
+ const condition = get(car, 'mainData.condition', 'selector_unknown');
380
+ return get(available, condition);
381
+ }
382
+
383
+ private getEnergyEfficiencyClass(car: DataSchemes.Car): string {
384
+ const available = {
385
+ selector_energyEfficiencyClass_appp: 'A+',
386
+ selector_energyEfficiencyClass_app: 'A+',
387
+ selector_energyEfficiencyClass_ap: 'A+',
388
+ selector_energyEfficiencyClass_a: 'A',
389
+ selector_energyEfficiencyClass_b: 'B',
390
+ selector_energyEfficiencyClass_c: 'C',
391
+ selector_energyEfficiencyClass_d: 'D',
392
+ selector_energyEfficiencyClass_e: 'E',
393
+ selector_energyEfficiencyClass_f: 'F',
394
+ selector_energyEfficiencyClass_g: 'G',
395
+ selector_unknown: 'G',
396
+ };
397
+ const energyClass = get(car, 'environmentEmissions.energyEfficiencyClass', 'selector_unknown');
398
+ return get(available, energyClass);
399
+ }
400
+
401
+ private getMileage(car: DataSchemes.Car): number {
402
+ const mileage = get(car, 'mainData.mileage', 0);
403
+ return mileage <= FB_MIN_MILEAGES ? FB_MIN_MILEAGES + 1 : mileage;
404
+ }
405
+
406
+ private getHighlights(car: DataSchemes.Car): { type: unknown; value: unknown }[] {
407
+ const facebookFeatures = [
408
+ { key: 'assistants.abs', type: 'abs_brakes', value: 'ABS' },
409
+ { key: 'assistants.adaptiveCruiseControl', type: 'adaptive_cruise_control', value: 'Abstandsregeltempomat' },
410
+ { key: 'theftProtection.alarmSystem', type: 'alarm', value: 'Alarmanlage' },
411
+ { key: 'multimedia.auxiliaryAudioInput', type: 'aux_audio_input', value: 'AUX-Anschluss' },
412
+ { key: 'assistants.blindSpotDetection', type: 'blind_spot_alert', value: 'Totwinkel-Assistent' },
413
+ { key: 'multimedia.bluetooth', type: 'bluetooth', value: 'Bluetooth' },
414
+ { key: 'assistants.collisionAvoidance', type: 'collision_avoidance', value: 'Notbrems Assistent' },
415
+ { key: 'assistants.cruiseControl', type: 'cruise_control', value: 'Tempomat' },
416
+ { key: 'lights.daytimeRunningLights', type: 'daytime_running_lights', value: 'Tagfahrlicht' },
417
+ { key: 'lights.daytimeRunningLightsLED', type: 'daytime_running_lights', value: 'Tagfahrlicht LED' },
418
+ { key: 'lights.frontFogLights', type: 'fog_lights', value: 'Nebelscheinwerfer' },
419
+ { key: 'wheel.heatedSteeringWheel', type: 'heated_steering_wheel', value: 'Lenkradheizung' },
420
+ { key: 'lights.xenonHeadlights', type: 'hid_headlights', value: 'Xenonscheinwerfer' },
421
+ { key: 'assistants.hillStartDrivingAssistant', type: 'hill_start_assist', value: 'Berganfahrhilfe' },
422
+ { key: 'doors.keylessEntry', type: 'keyless_entry', value: 'Keyless-Go' },
423
+ { key: 'doors.keylessEntry', type: 'keyless_start', value: 'Keyless-Go' },
424
+ { key: 'assistants.laneAssistant', type: 'lane_keep_assist', value: 'Spurhalte-Assistent' },
425
+ { key: 'lights.ledHeadlights', type: 'led_headlights', value: 'LED Scheinwerfer' },
426
+ { key: 'window.sunroof', type: 'moon_roof', value: 'Schiebedach' },
427
+ { key: 'multimedia.mp3Interface', type: 'mp3 MP3', value: 'Schnittstelle' },
428
+ { key: 'multimedia.navigationSystem', type: 'navigation', value: 'Navigationssystem' },
429
+ { key: 'assistants.nightVisionAssistant', type: 'night_view', value: 'Nachsicht Assistent' },
430
+ { key: 'window.panoramicGlassRoof', type: 'panorama_roof', value: 'Panoramaschiebedach' },
431
+ { key: 'doors.centralLocking', type: 'power_door_locks', value: 'Zentralverriegelung' },
432
+ { key: 'doors.electricTailGate', type: 'power_liftgate', value: 'elektrische Heckklappe' },
433
+ { key: 'assistants.powerAssistedSteering', type: 'power_steering', value: 'powerAssistedSteering' },
434
+ { key: 'window.electricWindows', type: 'power_windows', value: 'Elektrische Fenster' },
435
+ { key: 'extras.roofBars', type: 'roof_rack', value: 'Dachgepäckträger' },
436
+ { key: 'extras.roofRails', type: 'roof_rack', value: 'Dachreling' },
437
+ { key: 'assistants.esp', type: 'stability_control', value: 'ESP' },
438
+ { key: 'multimedia.onBoardComputer', type: 'telematics', value: 'Bordcomputer' },
439
+ { key: 'wheel.adjustableSteeringWheel', type: 'tilt_steering_wheel', value: 'höhenverst Lenkrad' },
440
+ { key: 'window.tintedWindowBack', type: 'tinted_windows', value: 'getönte Scheiben hinten' },
441
+ { key: 'assistants.tirePressureMonitor', type: 'tire_pressure_monitor', value: 'Reifendruckkontrolle' },
442
+ { key: 'trailerCoupling.trailerCouplingFix', type: 'tow_package', value: 'Anhängerkupplung fest' },
443
+ { key: 'trailerCoupling.trailerCouplingRemovable', type: 'tow_package', value: 'Anhängerkupplung abnehmbar' },
444
+ { key: 'trailerCoupling.trailerCouplingRotatable', type: 'tow_package', value: 'Anhängerkupplung schwenkbar' },
445
+ { key: 'assistants.tractionControlSystem', type: 'traction_control', value: 'Traktionskontrolle' },
446
+ {
447
+ key: 'heatingCooling.climatisation',
448
+ type: 'air_conditioning',
449
+ selector: 'selector_unknown',
450
+ value: 'Unbekannt',
451
+ },
452
+ {
453
+ key: 'heatingCooling.climatisation',
454
+ type: 'air_conditioning',
455
+ selector: 'selector_climatisation_no_airCondition',
456
+ value: 'Keine Klimaanlage oder -automatik',
457
+ },
458
+ {
459
+ key: 'heatingCooling.climatisation',
460
+ type: 'air_conditioning',
461
+ selector: 'selector_climatisation_airCondition',
462
+ value: 'Klimaanlage oder -automatik',
463
+ },
464
+ {
465
+ key: 'heatingCooling.climatisation',
466
+ type: 'air_conditioning',
467
+ selector: 'selector_climatisation_automaticAirCondition',
468
+ value: 'Klimaautomatik',
469
+ },
470
+ {
471
+ key: 'heatingCooling.climatisation',
472
+ type: 'air_conditioning',
473
+ selector: 'selector_climatisation_2_automaticAirCondition',
474
+ value: '2-Zonen Klimaautomatik',
475
+ },
476
+ {
477
+ key: 'heatingCooling.climatisation',
478
+ type: 'air_conditioning',
479
+ selector: 'selector_climatisation_3_automaticAirCondition',
480
+ value: '3-Zonen Klimaautomatik',
481
+ },
482
+ {
483
+ key: 'heatingCooling.climatisation',
484
+ type: 'air_conditioning',
485
+ selector: 'selector_climatisation_4_automaticAirCondition',
486
+ value: '4-Zonen Klimaautomatik',
487
+ },
488
+ {
489
+ key: 'wheels.summerTiresRims',
490
+ type: 'alloy_wheels',
491
+ selector: 'selector_tiresRims_alloy',
492
+ value: 'Leichtmetallfelgen',
493
+ },
494
+ {
495
+ key: 'assistants.parkingAssistants',
496
+ type: 'backup_camera',
497
+ selector: 'selector_parkingAssistants_camera',
498
+ value: 'Rückfahrkamera',
499
+ },
500
+ {
501
+ key: 'seats.driverSeats',
502
+ type: 'heated_seats',
503
+ selector: 'selector_driverSeats_electricHeated',
504
+ value: 'Sitzheizung Fahrersitz',
505
+ },
506
+ {
507
+ key: 'seats.coDriverSeats',
508
+ type: 'heated_seats',
509
+ selector: 'selector_coDriverSeats_electricHeated',
510
+ value: 'Sitzheizung Beifahrersitz',
511
+ },
512
+ {
513
+ key: 'seats.backSeats',
514
+ type: 'heated_seats',
515
+ selector: 'selector_backSeats_electricHeated',
516
+ value: 'Sitzheizung Rücksitz',
517
+ },
518
+ {
519
+ key: 'interior.interiorType',
520
+ type: 'leather',
521
+ selector: 'selector_interiorType_fullLeather',
522
+ value: 'Vollleder',
523
+ },
524
+ {
525
+ key: 'interior.interiorType',
526
+ type: 'leather',
527
+ selector: 'selector_interiorType_partLeather',
528
+ value: 'Teilleder',
529
+ },
530
+ { key: 'assistants.parkingAssistants', type: 'park_assist', selector: 'selector_unknown', value: 'Unbekannt' },
531
+ {
532
+ key: 'assistants.parkingAssistants',
533
+ type: 'park_assist',
534
+ selector: 'selector_parkingAssistants_other',
535
+ value: 'Andere',
536
+ },
537
+ {
538
+ key: 'assistants.parkingAssistants',
539
+ type: 'park_assist',
540
+ selector: 'selector_parkingAssistants_front',
541
+ value: 'Vorderseite',
542
+ },
543
+ {
544
+ key: 'assistants.parkingAssistants',
545
+ type: 'park_assist',
546
+ selector: 'selector_parkingAssistants_rear',
547
+ value: 'Rückseite',
548
+ },
549
+ {
550
+ key: 'assistants.parkingAssistants',
551
+ type: 'park_assist',
552
+ selector: 'selector_parkingAssistants_camera',
553
+ value: 'Kamera',
554
+ },
555
+ {
556
+ key: 'assistants.parkingAssistants',
557
+ type: 'park_assist',
558
+ selector: 'selector_parkingAssistants_selfSteeringSystems',
559
+ value: 'Selbstlenkende Systeme',
560
+ },
561
+ {
562
+ key: 'assistants.parkingAssistants',
563
+ type: 'park_assist',
564
+ selector: 'selector_parkingAssistants_360Camera',
565
+ value: '360° Kamera',
566
+ },
567
+ {
568
+ key: 'seats.driverSeats',
569
+ type: 'power_seats',
570
+ selector: 'selector_driverSeats_electricAdjustable',
571
+ value: 'Fahrersitz elektr. einstellbar',
572
+ },
573
+ {
574
+ key: 'seats.coDriverSeats',
575
+ type: 'power_seats',
576
+ selector: 'selector_coDriverSeats_electricAdjustable',
577
+ value: 'Beifahrersitz elektr. einstellbar',
578
+ },
579
+ {
580
+ key: 'assistants.parkingAssistants',
581
+ type: 'surround_view_camera',
582
+ selector: 'selector_parkingAssistants_360Camera',
583
+ value: '360° Kamera',
584
+ },
585
+ {
586
+ key: 'seats.driverSeats',
587
+ type: 'ventilated_seats',
588
+ selector: 'selector_driverSeats_seatVentilationSystem',
589
+ value: 'Sitzbelüftung Fahrersitz',
590
+ },
591
+ {
592
+ key: 'seats.coDriverSeats',
593
+ type: 'ventilated_seats',
594
+ selector: 'selector_coDriverSeats_seatVentilationSystem',
595
+ value: 'Sitzbelüftung Beifahrersitz',
596
+ },
597
+ {
598
+ key: 'seats.backSeats',
599
+ type: 'ventilated_seats',
600
+ selector: 'selector_backSeats_seatVentilationSystem',
601
+ value: 'Sitzbelüftung Rücksitz',
602
+ },
603
+ ];
604
+ return facebookFeatures
605
+ .map(feature => {
606
+ const value = get(car, feature.key);
607
+ if (isBoolean(value)) {
608
+ return this.getBooleanValue(value, feature);
609
+ }
610
+ if (isString(value)) {
611
+ return this.getStringValue(value, feature);
612
+ }
613
+ if (isArray(value) && includes(value, feature.selector)) {
614
+ const index = findIndex(value, item => item === feature.selector);
615
+ return this.getStringValue(value[index], feature);
616
+ }
617
+ return null;
618
+ })
619
+ .filter(el => el);
620
+ }
621
+
622
+ private getBooleanValue(
623
+ value: boolean,
624
+ feature:
625
+ | { key: string; type: string; value: string; selector?: undefined }
626
+ | { key: string; type: string; selector: string; value: string },
627
+ ): { type: string; value: string } {
628
+ if (value) {
629
+ return {
630
+ type: feature.type,
631
+ value: feature.value,
632
+ };
633
+ }
634
+ return null;
635
+ }
636
+
637
+ private getStringValue(
638
+ selector: string,
639
+ feature:
640
+ | { key: string; type: string; value: string; selector?: undefined }
641
+ | { key: string; type: string; selector: string; value: string },
642
+ ): { type: string; value: string } {
643
+ if (selector === feature.selector) {
644
+ return {
645
+ type: feature.type,
646
+ value: feature.value,
647
+ };
648
+ }
649
+ return null;
650
+ }
651
+ }
652
+
653
+ function getFirstValidYear(...years: string[]): number {
654
+ return years.map((year: string) => toSafeInteger(year)).filter((year: number) => year > TIMESTAMP_FIRST_YEAR)[0];
655
+ }