diff --git a/server/src/services/airtrail/airtrailMapper.ts b/server/src/services/airtrail/airtrailMapper.ts index 40c4fdd6..dd300b06 100644 --- a/server/src/services/airtrail/airtrailMapper.ts +++ b/server/src/services/airtrail/airtrailMapper.ts @@ -15,6 +15,15 @@ export function entityCode(e: AirtrailNamedCode | null | undefined): string | nu return e?.icao || e?.iata || null; } +/** + * Human-readable name for an airline/aircraft (e.g. "Lufthansa"), falling back to the + * code when AirTrail doesn't provide a name. Used for what TREK displays/stores; the + * raw code stays available via entityCode for the writeback payload (#1334). + */ +export function entityName(e: AirtrailNamedCode | null | undefined): string | null { + return e?.name || e?.icao || e?.iata || null; +} + /** * Local calendar date + clock time for an instant at a given IANA zone. * AirTrail stores `departure`/`arrival` as instants (ISO w/ offset) plus a local @@ -57,7 +66,7 @@ export function normalizeFlight(raw: AirtrailFlightRaw): AirtrailFlight { date: raw.date ?? null, departure: raw.departureScheduled ?? null, arrival: raw.arrivalScheduled ?? null, - airline: entityCode(raw.airline), + airline: entityName(raw.airline), flightNumber: raw.flightNumber ?? null, aircraft: entityCode(raw.aircraft), seatClass: (raw.seats?.find(s => s.userId) ?? raw.seats?.[0])?.seatClass ?? null, @@ -142,10 +151,14 @@ export function mapFlightToReservation(raw: AirtrailFlightRaw): MappedReservatio } const seat = raw.seats?.find(s => s.userId) ?? raw.seats?.[0]; + const airlineName = entityName(raw.airline); const airlineCode = entityCode(raw.airline); const aircraftCode = entityCode(raw.aircraft); const metadata: Record = {}; - if (airlineCode) metadata.airline = airlineCode; + // Display the airline name; keep the code in airline_code for the AirTrail writeback, + // which expects a code, not a name (#1334 / #1240). + if (airlineName) metadata.airline = airlineName; + if (airlineCode) metadata.airline_code = airlineCode; if (raw.flightNumber) metadata.flight_number = raw.flightNumber; if (aircraftCode) metadata.aircraft = aircraftCode; if (raw.aircraftReg) metadata.aircraft_reg = raw.aircraftReg; diff --git a/server/src/services/airtrail/airtrailSync.ts b/server/src/services/airtrail/airtrailSync.ts index 9e3d164f..bf76b3a8 100644 --- a/server/src/services/airtrail/airtrailSync.ts +++ b/server/src/services/airtrail/airtrailSync.ts @@ -216,9 +216,10 @@ export function buildSavePayload(reservation: any, existing: AirtrailFlightRaw): arrivalScheduledTime: arr.time, // These are AirTrail-owned details TREK doesn't surface in its edit UI — a TREK // edit can leave them out of `metadata`. Preserve AirTrail's current value when - // TREK has none rather than nulling it out (#1240). entityCode mirrors the + // TREK has none rather than nulling it out (#1240). Use airline_code (not the + // display name in metadata.airline, #1334); both it and entityCode mirror the // import/hash code-selection so a writeback stays a no-op for the hash. - airline: meta.airline ?? entityCode(existing.airline) ?? null, + airline: meta.airline_code ?? entityCode(existing.airline) ?? null, flightNumber: meta.flight_number ?? existing.flightNumber ?? null, aircraft: meta.aircraft ?? entityCode(existing.aircraft) ?? null, aircraftReg: meta.aircraft_reg ?? existing.aircraftReg ?? null, diff --git a/server/tests/unit/services/airtrailMapper.test.ts b/server/tests/unit/services/airtrailMapper.test.ts index d3418ad1..10d9b5c5 100644 --- a/server/tests/unit/services/airtrailMapper.test.ts +++ b/server/tests/unit/services/airtrailMapper.test.ts @@ -47,7 +47,7 @@ describe('airtrailMapper.normalizeFlight', () => { fromCode: 'JFK', toCode: 'LHR', date: '2021-09-01', - airline: 'BAW', + airline: 'British Airways', flightNumber: 'BA178', seatClass: 'economy', }); @@ -98,12 +98,19 @@ describe('airtrailMapper.mapFlightToReservation', () => { it('carries flight metadata', () => { const m = mapFlightToReservation(flight()); - expect(m.metadata).toMatchObject({ airline: 'BAW', flight_number: 'BA178', aircraft: 'B772', aircraft_reg: 'G-VIIL', flight_reason: 'leisure', seat: '12A' }); + // #1334: display the airline name, keep the code in airline_code for the writeback. + expect(m.metadata).toMatchObject({ airline: 'British Airways', airline_code: 'BAW', flight_number: 'BA178', aircraft: 'B772', aircraft_reg: 'G-VIIL', flight_reason: 'leisure', seat: '12A' }); expect(m.type).toBe('flight'); expect(m.status).toBe('confirmed'); expect(m.notes).toBe('window seat'); }); + it('#1334 falls back to the airline code when AirTrail provides no name', () => { + const a = { id: 9, icao: 'EWG', iata: 'EW' }; + expect(normalizeFlight(flight({ airline: a })).airline).toBe('EWG'); + expect(mapFlightToReservation(flight({ airline: a })).metadata).toMatchObject({ airline: 'EWG', airline_code: 'EWG' }); + }); + it('uses only the seat number for the seat, not the cabin class (#1246)', () => { // AirTrail often has a class but no seat number until check-in; the class // must not leak into the seat field.