Merge branch 'dev' into test
This commit is contained in:
@@ -509,10 +509,12 @@ export function registerTools(server: McpServer, userId: number): void {
|
||||
place_id: z.number().int().positive().optional().describe('Hotel place to link (hotel type only)'),
|
||||
start_day_id: z.number().int().positive().optional().describe('Check-in day (hotel type only; requires place_id and end_day_id)'),
|
||||
end_day_id: z.number().int().positive().optional().describe('Check-out day (hotel type only; requires place_id and start_day_id)'),
|
||||
check_in: z.string().max(10).optional().describe('Check-in time (e.g. "15:00", hotel type only)'),
|
||||
check_out: z.string().max(10).optional().describe('Check-out time (e.g. "11:00", hotel type only)'),
|
||||
assignment_id: z.number().int().positive().optional().describe('Link to a day assignment (restaurant, train, car, cruise, event, tour, activity, other)'),
|
||||
},
|
||||
},
|
||||
async ({ tripId, title, type, reservation_time, location, confirmation_number, notes, day_id, place_id, start_day_id, end_day_id, assignment_id }) => {
|
||||
async ({ tripId, title, type, reservation_time, location, confirmation_number, notes, day_id, place_id, start_day_id, end_day_id, check_in, check_out, assignment_id }) => {
|
||||
if (isDemoUser(userId)) return demoDenied();
|
||||
if (!canAccessTrip(tripId, userId)) return noAccess();
|
||||
|
||||
@@ -542,8 +544,8 @@ export function registerTools(server: McpServer, userId: number): void {
|
||||
let accommodationId: number | null = null;
|
||||
if (type === 'hotel' && place_id && start_day_id && end_day_id) {
|
||||
const accResult = db.prepare(
|
||||
'INSERT INTO day_accommodations (trip_id, place_id, start_day_id, end_day_id, confirmation) VALUES (?, ?, ?, ?, ?)'
|
||||
).run(tripId, place_id, start_day_id, end_day_id, confirmation_number || null);
|
||||
'INSERT INTO day_accommodations (trip_id, place_id, start_day_id, end_day_id, check_in, check_out, confirmation) VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||||
).run(tripId, place_id, start_day_id, end_day_id, check_in || null, check_out || null, confirmation_number || null);
|
||||
accommodationId = accResult.lastInsertRowid as number;
|
||||
}
|
||||
const result = db.prepare(`
|
||||
@@ -599,9 +601,11 @@ export function registerTools(server: McpServer, userId: number): void {
|
||||
place_id: z.number().int().positive().describe('The hotel place to link'),
|
||||
start_day_id: z.number().int().positive().describe('Check-in day ID'),
|
||||
end_day_id: z.number().int().positive().describe('Check-out day ID'),
|
||||
check_in: z.string().max(10).optional().describe('Check-in time (e.g. "15:00")'),
|
||||
check_out: z.string().max(10).optional().describe('Check-out time (e.g. "11:00")'),
|
||||
},
|
||||
},
|
||||
async ({ tripId, reservationId, place_id, start_day_id, end_day_id }) => {
|
||||
async ({ tripId, reservationId, place_id, start_day_id, end_day_id, check_in, check_out }) => {
|
||||
if (isDemoUser(userId)) return demoDenied();
|
||||
if (!canAccessTrip(tripId, userId)) return noAccess();
|
||||
const reservation = db.prepare('SELECT * FROM reservations WHERE id = ? AND trip_id = ?').get(reservationId, tripId) as Record<string, unknown> | undefined;
|
||||
@@ -619,12 +623,12 @@ export function registerTools(server: McpServer, userId: number): void {
|
||||
const isNewAccommodation = !accommodationId;
|
||||
db.transaction(() => {
|
||||
if (accommodationId) {
|
||||
db.prepare('UPDATE day_accommodations SET place_id = ?, start_day_id = ?, end_day_id = ? WHERE id = ?')
|
||||
.run(place_id, start_day_id, end_day_id, accommodationId);
|
||||
db.prepare('UPDATE day_accommodations SET place_id = ?, start_day_id = ?, end_day_id = ?, check_in = COALESCE(?, check_in), check_out = COALESCE(?, check_out) WHERE id = ?')
|
||||
.run(place_id, start_day_id, end_day_id, check_in || null, check_out || null, accommodationId);
|
||||
} else {
|
||||
const accResult = db.prepare(
|
||||
'INSERT INTO day_accommodations (trip_id, place_id, start_day_id, end_day_id, confirmation) VALUES (?, ?, ?, ?, ?)'
|
||||
).run(tripId, place_id, start_day_id, end_day_id, reservation.confirmation_number || null);
|
||||
'INSERT INTO day_accommodations (trip_id, place_id, start_day_id, end_day_id, check_in, check_out, confirmation) VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||||
).run(tripId, place_id, start_day_id, end_day_id, check_in || null, check_out || null, reservation.confirmation_number || null);
|
||||
accommodationId = accResult.lastInsertRowid as number;
|
||||
}
|
||||
db.prepare('UPDATE reservations SET place_id = ?, accommodation_id = ? WHERE id = ?')
|
||||
|
||||
@@ -74,9 +74,26 @@ router.post('/', authenticate, (req: Request, res: Response) => {
|
||||
if (!checkPermission('trip_create', authReq.user.role, null, authReq.user.id, false))
|
||||
return res.status(403).json({ error: 'No permission to create trips' });
|
||||
|
||||
const { title, description, start_date, end_date, currency, reminder_days } = req.body;
|
||||
const { title, description, currency, reminder_days } = req.body;
|
||||
if (!title) return res.status(400).json({ error: 'Title is required' });
|
||||
if (start_date && end_date && new Date(end_date) < new Date(start_date))
|
||||
|
||||
const toDateStr = (d: Date) => d.toISOString().slice(0, 10);
|
||||
const addDays = (d: Date, n: number) => { const r = new Date(d); r.setDate(r.getDate() + n); return r; };
|
||||
|
||||
let start_date: string | null = req.body.start_date || null;
|
||||
let end_date: string | null = req.body.end_date || null;
|
||||
|
||||
if (!start_date && !end_date) {
|
||||
const tomorrow = addDays(new Date(), 1);
|
||||
start_date = toDateStr(tomorrow);
|
||||
end_date = toDateStr(addDays(tomorrow, 7));
|
||||
} else if (start_date && !end_date) {
|
||||
end_date = toDateStr(addDays(new Date(start_date), 7));
|
||||
} else if (!start_date && end_date) {
|
||||
start_date = toDateStr(addDays(new Date(end_date), -7));
|
||||
}
|
||||
|
||||
if (new Date(end_date!) < new Date(start_date!))
|
||||
return res.status(400).json({ error: 'End date must be after start date' });
|
||||
|
||||
const { trip, tripId, reminderDays } = createTrip(authReq.user.id, { title, description, start_date, end_date, currency, reminder_days });
|
||||
|
||||
@@ -89,9 +89,15 @@ describe('Create trip', () => {
|
||||
expect(days[4].date).toBe('2026-06-05');
|
||||
});
|
||||
|
||||
it('TRIP-002 — POST /api/trips without dates returns 201 and no date-specific days', async () => {
|
||||
it('TRIP-002 — POST /api/trips without dates returns 201 and defaults to a 7-day window', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
|
||||
const addDays = (d: Date, n: number) => { const r = new Date(d); r.setDate(r.getDate() + n); return r; };
|
||||
const toDateStr = (d: Date) => d.toISOString().slice(0, 10);
|
||||
const tomorrow = addDays(new Date(), 1);
|
||||
const expectedStart = toDateStr(tomorrow);
|
||||
const expectedEnd = toDateStr(addDays(tomorrow, 7));
|
||||
|
||||
const res = await request(app)
|
||||
.post('/api/trips')
|
||||
.set('Cookie', authCookie(user.id))
|
||||
@@ -99,12 +105,12 @@ describe('Create trip', () => {
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
expect(res.body.trip).toBeDefined();
|
||||
expect(res.body.trip.start_date).toBeNull();
|
||||
expect(res.body.trip.end_date).toBeNull();
|
||||
expect(res.body.trip.start_date).toBe(expectedStart);
|
||||
expect(res.body.trip.end_date).toBe(expectedEnd);
|
||||
|
||||
// Days with explicit dates should not be present
|
||||
// Should have 8 days (start through end inclusive)
|
||||
const daysWithDate = testDb.prepare('SELECT * FROM days WHERE trip_id = ? AND date IS NOT NULL').all(res.body.trip.id) as any[];
|
||||
expect(daysWithDate).toHaveLength(0);
|
||||
expect(daysWithDate).toHaveLength(8);
|
||||
});
|
||||
|
||||
it('TRIP-001 — POST /api/trips requires a title, returns 400 without one', async () => {
|
||||
|
||||
Reference in New Issue
Block a user