fix(dkv): fix Kennzeichen matching and NaN/invalid km values in Excel export
Three issues fixed:
1. Kennzeichen normalization: DKV PDF extracts plates without hyphens
("GP JL 740E" vs CSV-imported "GP-JL 740E"). Added _normalizeKennzeichen()
which strips hyphens, spaces, and dots before lookup — resolves vehicle
master match failure that caused Marke/Modell/Fahrer to appear empty.
2. Empty-string NaN: parser used ?? '0' which doesn't catch empty strings,
causing parseDE('') = NaN. Changed to || '0' for km, menge, and totals.
3. Invalid km values: EV charging rows from DKV have misaligned columns —
km position contains a decimal price (e.g. 18.64 EUR or kWh). Added
sanity check: non-integer km values are written as null (empty cell)
instead of a misleading decimal. ExportRow.kilometerstand is now number|null.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -207,13 +207,14 @@ export class DkvParserService {
|
||||
return dates.map((date, i) => ({
|
||||
lieferdatum: date,
|
||||
ort: orts[i] ?? '',
|
||||
// German number format: "19.234,56" → 19234.56 (strip dots, replace comma)
|
||||
kilometerstand: parseDE(kms[i] ?? '0'),
|
||||
// German number format: "19.234,56" → 19234.56 (strip dots, replace comma).
|
||||
// Use || '0' (not ??) so empty strings also fall back to '0' (avoids NaN).
|
||||
kilometerstand: parseDE(kms[i] || '0'),
|
||||
produkt: products[i] ?? '',
|
||||
menge: parseDE(quantities[i] ?? '0'),
|
||||
menge: parseDE(quantities[i] || '0'),
|
||||
einheit: units[i] ?? '',
|
||||
netto: parseDE(totals_netto[i] ?? '0'),
|
||||
brutto: parseDE(totals_brutto[i] ?? '0'),
|
||||
netto: parseDE(totals_netto[i] || '0'),
|
||||
brutto: parseDE(totals_brutto[i] || '0'),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,15 +579,16 @@ export class DkvService {
|
||||
tenantId: string,
|
||||
vehicles: DkvVehicleBlock[],
|
||||
vehicleFormatString: string,
|
||||
): Promise<{ lieferdatum: string; fahrzeug: string; fahrer: string; ort: string; kilometerstand: number }[]> {
|
||||
): Promise<{ lieferdatum: string; fahrzeug: string; fahrer: string; ort: string; kilometerstand: number | null }[]> {
|
||||
// Batch load vehicle master to avoid N+1 queries
|
||||
const masters = await this.prisma.dkvVehicleMaster.findMany({ where: { tenantId } });
|
||||
const masterMap = new Map(masters.map((m) => [m.kennzeichen, m]));
|
||||
// Normalize keys: DKV PDF may omit hyphens or use spaces ("GP JL 740E" vs "GP-JL 740E")
|
||||
const masterMap = new Map(masters.map((m) => [_normalizeKennzeichen(m.kennzeichen), m]));
|
||||
|
||||
const rows: { lieferdatum: string; fahrzeug: string; fahrer: string; ort: string; kilometerstand: number }[] = [];
|
||||
const rows: { lieferdatum: string; fahrzeug: string; fahrer: string; ort: string; kilometerstand: number | null }[] = [];
|
||||
|
||||
for (const vehicle of vehicles) {
|
||||
const master = masterMap.get(vehicle.kennzeichen);
|
||||
const master = masterMap.get(_normalizeKennzeichen(vehicle.kennzeichen));
|
||||
|
||||
// For unknown plates: use empty string values for unknown fields
|
||||
const vehicleForFormat = {
|
||||
@@ -601,12 +602,20 @@ export class DkvService {
|
||||
const fahrer = master?.fahrer ?? '';
|
||||
|
||||
for (const tx of vehicle.transactions) {
|
||||
// Only keep km values that are plausible odometer readings (whole numbers ≥ 0).
|
||||
// Decimal values indicate a misaligned column (e.g. kWh or EUR from EV charging rows).
|
||||
const rawKm = tx.kilometerstand;
|
||||
const km: number | null =
|
||||
Number.isNaN(rawKm) || !Number.isFinite(rawKm) || !Number.isInteger(rawKm)
|
||||
? null
|
||||
: rawKm;
|
||||
|
||||
rows.push({
|
||||
lieferdatum: tx.lieferdatum,
|
||||
fahrzeug,
|
||||
fahrer,
|
||||
ort: tx.ort,
|
||||
kilometerstand: tx.kilometerstand,
|
||||
kilometerstand: km,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -684,3 +693,12 @@ function _parseVehicleCsv(
|
||||
function _delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a Kennzeichen for fuzzy vehicle lookup.
|
||||
* DKV PDF extraction may omit hyphens or use spaces as separators.
|
||||
* Example: "GP JL 740E" == "GP-JL 740E" == "GPJL740E" after normalization.
|
||||
*/
|
||||
function _normalizeKennzeichen(k: string): string {
|
||||
return k.toUpperCase().replace(/[\s\-\.]/g, '');
|
||||
}
|
||||
|
||||
@@ -98,6 +98,6 @@ export interface ExportRow {
|
||||
fahrer: string;
|
||||
/** Service station city from PDF */
|
||||
ort: string;
|
||||
/** Numeric odometer reading */
|
||||
kilometerstand: number;
|
||||
/** Numeric odometer reading; null when not available (e.g. EV charging rows) */
|
||||
kilometerstand: number | null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user