fix(trips): count trip dates in calendar days, the same on REST and MCP

Filling in a missing end date and keeping a trip's length when its start
moved both shifted a local-time Date, so a trip crossing a DST change
lost or gained a day. Both now use addIsoDays. The one-date-makes-a-week
default moves from the REST controller into TripsService.create, so the
MCP create_trip tool no longer stores a half-dated trip, and the trip and
its days are written in one transaction.
This commit is contained in:
Maurice
2026-10-07 16:11:41 +02:00
parent bfa512589c
commit e0aedbb2c6
6 changed files with 65 additions and 29 deletions
@@ -442,6 +442,20 @@ describe('TripFormModal', () => {
}));
});
it('FE-COMP-TRIPFORM-038: moving the start across a DST change keeps the trip length in calendar days', async () => {
const user = userEvent.setup();
const onSave = vi.fn().mockResolvedValue({});
const trip = buildTrip({ id: 1, title: 'Spring Trip', start_date: '2026-03-20', end_date: '2026-03-29' });
render(<TripFormModal {...defaultProps} trip={trip} onSave={onSave} />);
await changeStartDate(user, '2026-03-25');
await user.click(screen.getByRole('button', { name: /Update/i }));
await user.click(await screen.findByRole('button', { name: /Update/i }));
await waitFor(() => expect(onSave).toHaveBeenCalled());
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ start_date: '2026-03-25', end_date: '2026-04-03' }));
});
it('FE-COMP-TRIPFORM-036: picking "Shift everything" sends shift_all', async () => {
const user = userEvent.setup();
const onSave = vi.fn().mockResolvedValue({});
@@ -11,7 +11,7 @@ import { useTranslation } from '../../i18n'
import { CustomDatePicker } from '../shared/CustomDateTimePicker'
import { normalizeImageFile } from '../../utils/convertHeic'
import { getApiErrorMessage, type Trip } from '../../types'
import { MAX_TRIP_DAYS, tripSpanDays, type TripCreateRequest } from '@trek/shared'
import { MAX_TRIP_DAYS, addIsoDays, tripSpanDays, type TripCreateRequest } from '@trek/shared'
import { NumericInput } from '../shared/NumericInput'
import { currenciesWith, SYMBOLS } from '../Budget/BudgetPanel.constants'
import TripDateReview, { dateReviewIsWide } from './TripDateReview'
@@ -373,12 +373,9 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
if (!prev.end_date || prev.end_date < value) {
next.end_date = value
} else if (prev.start_date) {
const oldStart = new Date(prev.start_date + 'T00:00:00Z')
const oldEnd = new Date(prev.end_date + 'T00:00:00Z')
const duration = Math.round((oldEnd.getTime() - oldStart.getTime()) / 86400000)
const newEnd = new Date(value + 'T00:00:00Z')
newEnd.setDate(newEnd.getDate() + duration)
next.end_date = newEnd.toISOString().split('T')[0]
// Keep the trip's length, counted in UTC calendar days: a local-time
// shift across a DST change used to drop or add a day.
next.end_date = addIsoDays(value, tripSpanDays(prev.start_date, prev.end_date) - 1)
}
}
return next