@bytebrand/export-cars 1.22.18 → 1.22.20
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 +5 -2
- package/dist/services/user.service.js +3 -2
- package/dist/services/user.service.js.map +1 -1
- package/dist/transformers/google.transformer.d.ts +4 -0
- package/dist/transformers/google.transformer.js +57 -9
- 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 +5 -3
- package/src/services/user.service.ts +7 -2
- package/src/transformers/google.transformer.ts +72 -9
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;
|
|
@@ -196,7 +196,9 @@ export interface GoogleXmlItem {
|
|
|
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;
|
|
@@ -21,10 +21,15 @@ export class UserService {
|
|
|
21
21
|
return cachedResponse;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// A valid-but-empty description schema. car-description's MobileDescription does
|
|
25
|
+
// `getBodySchema(schema.body)` / `buildFreeText(schema.{header,footer})` with no guards,
|
|
26
|
+
// so `{}` would crash on `undefined.map`. Empty arrays produce an empty description safely.
|
|
27
|
+
const emptySchema = { body: [], header: [], footer: [] };
|
|
28
|
+
|
|
29
|
+
let result: any = emptySchema;
|
|
25
30
|
try {
|
|
26
31
|
const defaultSummary = await CronicleUtils.retry(() => this.requestService.userActionsRequest.getDefaultSummaryByDealerId(dealerId), 1, 30, true);
|
|
27
|
-
result = get(defaultSummary, 'data.schema',
|
|
32
|
+
result = get(defaultSummary, 'data.schema', emptySchema);
|
|
28
33
|
} catch (e) {
|
|
29
34
|
// Dealer has no default summary template (user-actions returns 404) — fall back to an
|
|
30
35
|
// empty schema instead of letting the rejection crash the whole export job.
|
|
@@ -50,17 +50,13 @@ export class GoogleTransformer {
|
|
|
50
50
|
...transformedImages.feat
|
|
51
51
|
]
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
const item: GoogleXmlItem = {
|
|
54
54
|
google_product_category: '916',
|
|
55
55
|
vehicle_fulfillment: {
|
|
56
56
|
option: 'online',
|
|
57
57
|
store_code: 'Online_shop'
|
|
58
58
|
},
|
|
59
|
-
certification:
|
|
60
|
-
certification_authority: 'BMWK',
|
|
61
|
-
certification_name: 'Vehicle_CO2_Class',
|
|
62
|
-
certification_value: `${this.getEnergyClass(car)}`
|
|
63
|
-
},
|
|
59
|
+
certification: this.getCertification(car),
|
|
64
60
|
title: this.getTitle(car),
|
|
65
61
|
product_type: this.getCategory(car),
|
|
66
62
|
VIN: this.transformService.vinCode(car),
|
|
@@ -84,7 +80,9 @@ export class GoogleTransformer {
|
|
|
84
80
|
engine: this.mapFuel(car),
|
|
85
81
|
date_first_registered: this.getDateFirstRegistered(car),
|
|
86
82
|
fuel_consumption: `${this.transformService.fuelConsumption(car)}l/100km`,
|
|
87
|
-
energy_consumption: this.
|
|
83
|
+
energy_consumption: this.getEnergyConsumption(car),
|
|
84
|
+
electric_range: this.getElectricRange(car),
|
|
85
|
+
fuel_consumption_discharged_battery: this.getFuelConsumptionDischargedBattery(car),
|
|
88
86
|
co2_emissions: `${this.transformService.cO2Emission(car)}g/km`,
|
|
89
87
|
description: this.processText((await this.getDescription(car))),
|
|
90
88
|
identifier_exists: 'no',
|
|
@@ -101,6 +99,25 @@ export class GoogleTransformer {
|
|
|
101
99
|
installment: this.getInstallment(car),
|
|
102
100
|
// custom_label_0: `${this.getInstallment(car)} EUR mtl.`,
|
|
103
101
|
};
|
|
102
|
+
|
|
103
|
+
// Google rejects empty/partial values — getElement() emits `<tag/>` for falsy
|
|
104
|
+
// values, which Merchant treats as "missing". Drop optional attributes that have
|
|
105
|
+
// no data (certification when CO2 class unknown, EV-only metrics on non-EV cars,
|
|
106
|
+
// installment when APR is missing) instead of emitting empty tags.
|
|
107
|
+
const optionalKeys: Array<keyof GoogleXmlItem> = [
|
|
108
|
+
'certification',
|
|
109
|
+
'energy_consumption',
|
|
110
|
+
'electric_range',
|
|
111
|
+
'fuel_consumption_discharged_battery',
|
|
112
|
+
'installment',
|
|
113
|
+
];
|
|
114
|
+
for (const key of optionalKeys) {
|
|
115
|
+
if (!item[key]) {
|
|
116
|
+
delete item[key];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return item;
|
|
104
121
|
}
|
|
105
122
|
|
|
106
123
|
private checkCarValidity(car: DataSchemes.Car, images: ImboImageResponseInterface[]) {
|
|
@@ -240,16 +257,62 @@ export class GoogleTransformer {
|
|
|
240
257
|
};
|
|
241
258
|
const energyClass = get(car, 'environmentEmissions.energyEfficiencyClass', 'selector_unknown');
|
|
242
259
|
const value = get(available, energyClass);
|
|
243
|
-
return value.replace(/\+/g, '');
|
|
260
|
+
return value ? value.replace(/\+/g, '') : '';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private getCertification(car: DataSchemes.Car) {
|
|
264
|
+
const value = this.getEnergyClass(car);
|
|
265
|
+
if (!value || value === 'default') {
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
certification_authority: 'BMWK',
|
|
270
|
+
certification_name: 'Vehicle_CO2_Class',
|
|
271
|
+
certification_value: value,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private getEnergyConsumption(car: DataSchemes.Car): string | undefined {
|
|
276
|
+
const fuel = get(car, 'consumption.fuel');
|
|
277
|
+
const isElectricOrHybrid =
|
|
278
|
+
['selector_fuel_electrical', 'selector_fuel_hybridElectricalPetrol', 'selector_fuel_hybridElectricalDiesel'].includes(fuel) ||
|
|
279
|
+
!!get(car, 'engineData.hybridPlugin');
|
|
280
|
+
if (!isElectricOrHybrid) {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
const power = get(car, 'consumption.wltpPowerCombined') || get(car, 'consumption.consumptionPowerCombined');
|
|
284
|
+
return power ? `${power}kWh/100km` : undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private getElectricRange(car: DataSchemes.Car): string | undefined {
|
|
288
|
+
const range = get(car, 'battery.batteryRangeElectric') || get(car, 'battery.wltpRange');
|
|
289
|
+
return range ? `${range}km` : undefined;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private getFuelConsumptionDischargedBattery(car: DataSchemes.Car): string | undefined {
|
|
293
|
+
if (!get(car, 'engineData.hybridPlugin')) {
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
const consumption = get(car, 'consumption.consumptionCombined') || get(car, 'consumption.wltpCombined');
|
|
297
|
+
return consumption ? `${consumption}l/100km` : undefined;
|
|
244
298
|
}
|
|
245
299
|
|
|
246
300
|
private getInstallment(car: DataSchemes.Car) {
|
|
247
301
|
const monthlyInstallment = round(get(car, 'rate.monthlyInstallment', null));
|
|
248
302
|
const paybackPeriod = round(get(car, 'rate.paybackPeriod', null));
|
|
303
|
+
const apr = toString(get(car, 'financing.annualPercentageRate', '')).trim();
|
|
304
|
+
|
|
305
|
+
// Google requires `apr` inside the installment block; a partial installment
|
|
306
|
+
// block (without apr) makes Google reject the whole attribute. If financing
|
|
307
|
+
// data is incomplete, omit installment entirely so the rest of the item stays valid.
|
|
308
|
+
if (!monthlyInstallment || !paybackPeriod || !apr) {
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
249
311
|
|
|
250
312
|
return {
|
|
251
313
|
paybackPeriod,
|
|
252
314
|
monthlyInstallment,
|
|
315
|
+
apr,
|
|
253
316
|
};
|
|
254
317
|
}
|
|
255
318
|
|
|
@@ -280,7 +343,7 @@ export class GoogleTransformer {
|
|
|
280
343
|
selector_unknown: 'Other',
|
|
281
344
|
};
|
|
282
345
|
const fuel = get(car, 'consumption.fuel', 'selector_unknown');
|
|
283
|
-
let engine = get(available, fuel)
|
|
346
|
+
let engine = get(available, fuel, 'Other')
|
|
284
347
|
const hybridPlugin = car?.engineData?.hybridPlugin;
|
|
285
348
|
if (engine === 'Hybrid' && hybridPlugin)
|
|
286
349
|
engine = 'plug-in_hybrid';
|