feat(logistics): departure-after-arrival validation + travel/visa CSV export
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m11s

- upsertFlightDetail throws BAD_REQUEST when departureAt < arrivalAt
- Travel tab: Download CSV button (project/attendee/email/flight fields/status/visa)
- Visas tab: Download CSV button (project/attendee/nationality/status/dates/notes)
- TDD: 2 new tests (rejects invalid, accepts valid); all 6 flight tests pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt
2026-06-04 16:56:22 +02:00
parent 53b623fb20
commit 97951deb68
4 changed files with 203 additions and 9 deletions

View File

@@ -174,6 +174,64 @@ describe('logistics flight detail procedures', () => {
expect(count).toBe(1)
})
it('upsertFlightDetail rejects when departureAt < arrivalAt', async () => {
const admin = await createTestUser('SUPER_ADMIN')
userIds.push(admin.id)
const { program, lead, attendingMember } = await setupConfirmedFinalist(
`flight-dep-before-arr-${uid()}`,
)
programIds.push(program.id)
userIds.push(lead.id)
const caller = createCaller(logisticsRouter, {
id: admin.id,
email: admin.email,
role: 'SUPER_ADMIN',
})
const arrivalDate = new Date('2026-06-28T14:00:00Z')
const departureBeforeArrival = new Date('2026-06-28T10:00:00Z') // 4 hours earlier
await expect(
caller.upsertFlightDetail({
attendingMemberId: attendingMember.id,
arrivalAt: arrivalDate,
departureAt: departureBeforeArrival,
arrivalFlightNumber: 'AF7400',
departureFlightNumber: 'AF7405',
}),
).rejects.toMatchObject({ code: 'BAD_REQUEST', message: 'Departure must be after arrival' })
})
it('upsertFlightDetail succeeds when departureAt >= arrivalAt', async () => {
const admin = await createTestUser('SUPER_ADMIN')
userIds.push(admin.id)
const { program, lead, attendingMember } = await setupConfirmedFinalist(
`flight-dep-after-arr-${uid()}`,
)
programIds.push(program.id)
userIds.push(lead.id)
const caller = createCaller(logisticsRouter, {
id: admin.id,
email: admin.email,
role: 'SUPER_ADMIN',
})
const arrivalDate = new Date('2026-06-28T10:00:00Z')
const departureAfterArrival = new Date('2026-06-30T14:00:00Z') // 2 days later
const result = await caller.upsertFlightDetail({
attendingMemberId: attendingMember.id,
arrivalAt: arrivalDate,
departureAt: departureAfterArrival,
arrivalFlightNumber: 'AF7400',
departureFlightNumber: 'AF7405',
})
expect(result.arrivalFlightNumber).toBe('AF7400')
expect(result.departureFlightNumber).toBe('AF7405')
})
it('setFlightStatus toggles PENDING ↔ CONFIRMED', async () => {
const admin = await createTestUser('SUPER_ADMIN')
userIds.push(admin.id)