@bytebrand/export-cars 1.22.19 → 1.22.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/formats/google-xml.builder.js +2 -1
- package/dist/formats/google-xml.builder.js.map +1 -1
- package/dist/interfaces/export-item.interface.d.ts +7 -4
- package/dist/transformers/google.transformer.d.ts +5 -0
- package/dist/transformers/google.transformer.js +65 -12
- package/dist/transformers/google.transformer.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/formats/google-xml.builder.ts +3 -2
- package/src/interfaces/export-item.interface.ts +7 -5
- package/src/transformers/google.transformer.ts +81 -13
package/package.json
CHANGED
|
@@ -62,10 +62,11 @@ export class GoogleXmlBuilder extends XMLBuilderBase {
|
|
|
62
62
|
</g:${rootTag}>`;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
private getInstallmentElement(rootTag: string, data: { paybackPeriod: string; monthlyInstallment: string }): string {
|
|
65
|
+
private getInstallmentElement(rootTag: string, data: { paybackPeriod: string; monthlyInstallment: string; apr?: string }): string {
|
|
66
66
|
return `<g:${rootTag}>
|
|
67
67
|
<g:months>${data.paybackPeriod}</g:months>
|
|
68
|
-
<g:amount>${data.monthlyInstallment} EUR</g:amount
|
|
68
|
+
<g:amount>${data.monthlyInstallment} EUR</g:amount>${data.apr ? `
|
|
69
|
+
<g:apr>${data.apr}</g:apr>` : ''}
|
|
69
70
|
</g:${rootTag}>`;
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -172,13 +172,13 @@ export interface GoogleXmlItem {
|
|
|
172
172
|
sale_price?: string;
|
|
173
173
|
availability?: string;
|
|
174
174
|
energy_efficiency_class?: string;
|
|
175
|
-
installment?: { paybackPeriod: any; monthlyInstallment: any };
|
|
175
|
+
installment?: { paybackPeriod: any; monthlyInstallment: any; apr: string };
|
|
176
176
|
|
|
177
177
|
vehicle_fulfillment: {
|
|
178
178
|
option: string;
|
|
179
179
|
store_code: string;
|
|
180
180
|
};
|
|
181
|
-
certification
|
|
181
|
+
certification?: {
|
|
182
182
|
certification_authority: string;
|
|
183
183
|
certification_name: string;
|
|
184
184
|
certification_code?: string;
|
|
@@ -187,16 +187,18 @@ export interface GoogleXmlItem {
|
|
|
187
187
|
link_template: string;
|
|
188
188
|
additional_image_link: string[];
|
|
189
189
|
model: string;
|
|
190
|
-
VIN
|
|
190
|
+
VIN?: string;
|
|
191
191
|
trim: string;
|
|
192
192
|
year: number;
|
|
193
193
|
mileage: string;
|
|
194
194
|
vehicle_option: string[];
|
|
195
|
-
body_style
|
|
195
|
+
body_style?: string,
|
|
196
196
|
engine: string,
|
|
197
197
|
date_first_registered: string,
|
|
198
198
|
fuel_consumption: string,
|
|
199
|
-
energy_consumption
|
|
199
|
+
energy_consumption?: string,
|
|
200
|
+
electric_range?: string,
|
|
201
|
+
fuel_consumption_discharged_battery?: string,
|
|
200
202
|
co2_emissions: string,
|
|
201
203
|
identifier_exists: string,
|
|
202
204
|
ads_redirect?: string;
|
|
@@ -7,6 +7,7 @@ import { ImboImageResponseInterface } from '@bytebrand/chunkable';
|
|
|
7
7
|
import { Injectable } from '@nestjs/common';
|
|
8
8
|
import { ConfigService, ImagesService, TransformerService, UserService } from '../services';
|
|
9
9
|
import { GoogleXmlItem } from '../interfaces';
|
|
10
|
+
import { isValidVin } from '../utils';
|
|
10
11
|
import moment from 'moment';
|
|
11
12
|
|
|
12
13
|
const GOOGLE_MAX_TRIM_LENGTH = 50;
|
|
@@ -50,20 +51,16 @@ export class GoogleTransformer {
|
|
|
50
51
|
...transformedImages.feat
|
|
51
52
|
]
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
const item: GoogleXmlItem = {
|
|
54
55
|
google_product_category: '916',
|
|
55
56
|
vehicle_fulfillment: {
|
|
56
57
|
option: 'online',
|
|
57
58
|
store_code: 'Online_shop'
|
|
58
59
|
},
|
|
59
|
-
certification:
|
|
60
|
-
certification_authority: 'BMWK',
|
|
61
|
-
certification_name: 'Vehicle_CO2_Class',
|
|
62
|
-
certification_value: `${this.getEnergyClass(car)}`
|
|
63
|
-
},
|
|
60
|
+
certification: this.getCertification(car),
|
|
64
61
|
title: this.getTitle(car),
|
|
65
62
|
product_type: this.getCategory(car),
|
|
66
|
-
VIN: this.
|
|
63
|
+
VIN: this.getVin(car),
|
|
67
64
|
id: carId,
|
|
68
65
|
image_link: allImages[0],
|
|
69
66
|
additional_image_link: allImages.slice(1, 10),
|
|
@@ -84,7 +81,9 @@ export class GoogleTransformer {
|
|
|
84
81
|
engine: this.mapFuel(car),
|
|
85
82
|
date_first_registered: this.getDateFirstRegistered(car),
|
|
86
83
|
fuel_consumption: `${this.transformService.fuelConsumption(car)}l/100km`,
|
|
87
|
-
energy_consumption: this.
|
|
84
|
+
energy_consumption: this.getEnergyConsumption(car),
|
|
85
|
+
electric_range: this.getElectricRange(car),
|
|
86
|
+
fuel_consumption_discharged_battery: this.getFuelConsumptionDischargedBattery(car),
|
|
88
87
|
co2_emissions: `${this.transformService.cO2Emission(car)}g/km`,
|
|
89
88
|
description: this.processText((await this.getDescription(car))),
|
|
90
89
|
identifier_exists: 'no',
|
|
@@ -101,18 +100,41 @@ export class GoogleTransformer {
|
|
|
101
100
|
installment: this.getInstallment(car),
|
|
102
101
|
// custom_label_0: `${this.getInstallment(car)} EUR mtl.`,
|
|
103
102
|
};
|
|
103
|
+
|
|
104
|
+
// Google rejects empty/partial values — getElement() emits `<tag/>` for falsy
|
|
105
|
+
// values, which Merchant treats as "missing". Drop optional attributes that have
|
|
106
|
+
// no data (certification when CO2 class unknown, EV-only metrics on non-EV cars,
|
|
107
|
+
// installment when APR is missing) instead of emitting empty tags.
|
|
108
|
+
const optionalKeys: Array<keyof GoogleXmlItem> = [
|
|
109
|
+
'certification',
|
|
110
|
+
'energy_consumption',
|
|
111
|
+
'electric_range',
|
|
112
|
+
'fuel_consumption_discharged_battery',
|
|
113
|
+
'installment',
|
|
114
|
+
'VIN',
|
|
115
|
+
'body_style',
|
|
116
|
+
];
|
|
117
|
+
for (const key of optionalKeys) {
|
|
118
|
+
if (!item[key]) {
|
|
119
|
+
delete item[key];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return item;
|
|
104
124
|
}
|
|
105
125
|
|
|
106
126
|
private checkCarValidity(car: DataSchemes.Car, images: ImboImageResponseInterface[]) {
|
|
107
127
|
if (images.length === 0)
|
|
108
128
|
return false
|
|
109
129
|
|
|
110
|
-
if (!this.mapBodyStyle(car))
|
|
111
|
-
return false
|
|
112
|
-
|
|
113
130
|
return true
|
|
114
131
|
}
|
|
115
132
|
|
|
133
|
+
private getVin(car: DataSchemes.Car): string | undefined {
|
|
134
|
+
const vin = get(car, 'metaData.vin', '');
|
|
135
|
+
return isValidVin(vin) ? vin : undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
116
138
|
private getTitle(car: DataSchemes.Car): string {
|
|
117
139
|
const model = toString(get(car, 'mainData.model', ''));
|
|
118
140
|
const brand = trim(this.getBrand(car));
|
|
@@ -240,16 +262,62 @@ export class GoogleTransformer {
|
|
|
240
262
|
};
|
|
241
263
|
const energyClass = get(car, 'environmentEmissions.energyEfficiencyClass', 'selector_unknown');
|
|
242
264
|
const value = get(available, energyClass);
|
|
243
|
-
return value.replace(/\+/g, '');
|
|
265
|
+
return value ? value.replace(/\+/g, '') : '';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private getCertification(car: DataSchemes.Car) {
|
|
269
|
+
const value = this.getEnergyClass(car);
|
|
270
|
+
if (!value || value === 'default') {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
certification_authority: 'BMWK',
|
|
275
|
+
certification_name: 'Vehicle_CO2_Class',
|
|
276
|
+
certification_value: value,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private getEnergyConsumption(car: DataSchemes.Car): string | undefined {
|
|
281
|
+
const fuel = get(car, 'consumption.fuel');
|
|
282
|
+
const isElectricOrHybrid =
|
|
283
|
+
['selector_fuel_electrical', 'selector_fuel_hybridElectricalPetrol', 'selector_fuel_hybridElectricalDiesel'].includes(fuel) ||
|
|
284
|
+
!!get(car, 'engineData.hybridPlugin');
|
|
285
|
+
if (!isElectricOrHybrid) {
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
const power = get(car, 'consumption.wltpPowerCombined') || get(car, 'consumption.consumptionPowerCombined');
|
|
289
|
+
return power ? `${power}kWh/100km` : undefined;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private getElectricRange(car: DataSchemes.Car): string | undefined {
|
|
293
|
+
const range = get(car, 'battery.batteryRangeElectric') || get(car, 'battery.wltpRange');
|
|
294
|
+
return range ? `${range}km` : undefined;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private getFuelConsumptionDischargedBattery(car: DataSchemes.Car): string | undefined {
|
|
298
|
+
if (!get(car, 'engineData.hybridPlugin')) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
const consumption = get(car, 'consumption.consumptionCombined') || get(car, 'consumption.wltpCombined');
|
|
302
|
+
return consumption ? `${consumption}l/100km` : undefined;
|
|
244
303
|
}
|
|
245
304
|
|
|
246
305
|
private getInstallment(car: DataSchemes.Car) {
|
|
247
306
|
const monthlyInstallment = round(get(car, 'rate.monthlyInstallment', null));
|
|
248
307
|
const paybackPeriod = round(get(car, 'rate.paybackPeriod', null));
|
|
308
|
+
const apr = toString(get(car, 'financing.annualPercentageRate', '')).trim();
|
|
309
|
+
|
|
310
|
+
// Google requires `apr` inside the installment block; a partial installment
|
|
311
|
+
// block (without apr) makes Google reject the whole attribute. If financing
|
|
312
|
+
// data is incomplete, omit installment entirely so the rest of the item stays valid.
|
|
313
|
+
if (!monthlyInstallment || !paybackPeriod || !apr) {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
249
316
|
|
|
250
317
|
return {
|
|
251
318
|
paybackPeriod,
|
|
252
319
|
monthlyInstallment,
|
|
320
|
+
apr,
|
|
253
321
|
};
|
|
254
322
|
}
|
|
255
323
|
|
|
@@ -280,7 +348,7 @@ export class GoogleTransformer {
|
|
|
280
348
|
selector_unknown: 'Other',
|
|
281
349
|
};
|
|
282
350
|
const fuel = get(car, 'consumption.fuel', 'selector_unknown');
|
|
283
|
-
let engine = get(available, fuel)
|
|
351
|
+
let engine = get(available, fuel, 'Other')
|
|
284
352
|
const hybridPlugin = car?.engineData?.hybridPlugin;
|
|
285
353
|
if (engine === 'Hybrid' && hybridPlugin)
|
|
286
354
|
engine = 'plug-in_hybrid';
|