fix(airtrail): also treat an endpoint-count > 2 booking as a local multi-leg shape, mirroring the server so a locally-grown chain is not mislabelled removed

This commit is contained in:
Maurice
2026-07-24 12:20:23 +02:00
committed by Maurice
parent 3dc9ac6ac6
commit 791468e5b3
2 changed files with 19 additions and 2 deletions
@@ -493,4 +493,17 @@ describe('ReservationsPanel', () => {
expect(screen.getByText('Not synced')).toBeInTheDocument();
expect(screen.getByTitle('This flight was removed in AirTrail and no longer syncs.')).toBeInTheDocument();
});
it('FE-PLANNER-RESP-049: a flight grown into multiple legs locally (endpoints > 2, no legs array) shows the layover hint, not "Not synced"', () => {
// Matches the server's second hasLocalMultiLegShape criterion (endpoint count > 2).
const res = buildReservation({
title: 'Grown multi-leg', type: 'flight', external_source: 'airtrail', sync_enabled: 0,
endpoints: [{ sequence: 0 }, { sequence: 1 }, { sequence: 2 }],
} as any);
render(<ReservationsPanel {...defaultProps} reservations={[res]} />);
expect(screen.queryByText('Not synced')).not.toBeInTheDocument();
expect(
screen.getByTitle('Imported from AirTrail. A multi-leg flight with a layover has no single AirTrail flight to sync back to, so it stays as a one-time import.'),
).toBeInTheDocument();
});
});
@@ -101,11 +101,15 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
// no single flight to round-trip a layover chain to, so it's created with
// sync_enabled=0 (#1535). Distinguish it from a flight that was removed
// upstream so the badge doesn't falsely claim it "was removed in AirTrail" (#1646).
// Mirror the server's hasLocalMultiLegShape (airtrailSync.ts): a metadata legs
// array of length > 1, OR — for a flight grown into multiple legs locally, which
// carries no legs array — more than two endpoints. Both are detached, not removed.
const airtrailMultiLeg = r.external_source === 'airtrail' && !r.sync_enabled && (() => {
try {
const m = typeof r.metadata === 'string' ? JSON.parse(r.metadata || '{}') : (r.metadata || {})
return Array.isArray(m?.legs) && m.legs.length > 1
} catch { return false }
if (Array.isArray(m?.legs) && m.legs.length > 1) return true
} catch { /* malformed metadata — fall through to the endpoint count */ }
return (r.endpoints || []).length > 2
})()
const attachedFiles = files.filter(f => f.reservation_id === r.id || (f.linked_reservation_ids || []).includes(r.id))
const linked = r.assignment_id ? assignmentLookup[r.assignment_id] : null