mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 21:31:46 +00:00
feat: support check-in time ranges for hotel accommodations
- Add check_in_end column to day_accommodations (Migration 102) - Server: create/update accommodation accepts check_in_end - Bidirectional sync: check_in_end synced between accommodation and linked reservation metadata (check_in_end_time) - DayDetailPanel: shows check-in range (e.g. "14:00 – 22:00"), new "Until" time picker in hotel form - ReservationModal: new check-in-until field for hotel bookings - ReservationsPanel: displays check-in range in metadata cells - i18n: checkInUntil keys in all 15 languages Closes #366
This commit is contained in:
@@ -1610,6 +1610,11 @@ function runMigrations(db: Database.Database): void {
|
||||
() => {
|
||||
db.prepare("UPDATE addons SET enabled = 1 WHERE id = 'naver_list_import'").run();
|
||||
},
|
||||
|
||||
// Migration 102: Add check_in_end column for check-in time ranges
|
||||
() => {
|
||||
try { db.exec('ALTER TABLE day_accommodations ADD COLUMN check_in_end TEXT'); } catch (err: any) { if (!err.message?.includes('duplicate column name')) throw err; }
|
||||
},
|
||||
];
|
||||
|
||||
if (currentVersion < migrations.length) {
|
||||
|
||||
@@ -334,6 +334,7 @@ function createTables(db: Database.Database): void {
|
||||
start_day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE,
|
||||
end_day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE,
|
||||
check_in TEXT,
|
||||
check_in_end TEXT,
|
||||
check_out TEXT,
|
||||
confirmation TEXT,
|
||||
notes TEXT,
|
||||
|
||||
@@ -73,7 +73,7 @@ accommodationsRouter.post('/', authenticate, requireTripAccess, (req: Request, r
|
||||
return res.status(403).json({ error: 'No permission' });
|
||||
|
||||
const { tripId } = req.params;
|
||||
const { place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes } = req.body;
|
||||
const { place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes } = req.body;
|
||||
|
||||
if (!place_id || !start_day_id || !end_day_id) {
|
||||
return res.status(400).json({ error: 'place_id, start_day_id, and end_day_id are required' });
|
||||
@@ -82,7 +82,7 @@ accommodationsRouter.post('/', authenticate, requireTripAccess, (req: Request, r
|
||||
const errors = dayService.validateAccommodationRefs(tripId, place_id, start_day_id, end_day_id);
|
||||
if (errors.length > 0) return res.status(404).json({ error: errors[0].message });
|
||||
|
||||
const accommodation = dayService.createAccommodation(tripId, { place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes });
|
||||
const accommodation = dayService.createAccommodation(tripId, { place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes });
|
||||
res.status(201).json({ accommodation });
|
||||
broadcast(tripId, 'accommodation:created', { accommodation }, req.headers['x-socket-id'] as string);
|
||||
broadcast(tripId, 'reservation:created', {}, req.headers['x-socket-id'] as string);
|
||||
@@ -98,12 +98,12 @@ accommodationsRouter.put('/:id', authenticate, requireTripAccess, (req: Request,
|
||||
const existing = dayService.getAccommodation(id, tripId);
|
||||
if (!existing) return res.status(404).json({ error: 'Accommodation not found' });
|
||||
|
||||
const { place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes } = req.body;
|
||||
const { place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes } = req.body;
|
||||
|
||||
const errors = dayService.validateAccommodationRefs(tripId, place_id, start_day_id, end_day_id);
|
||||
if (errors.length > 0) return res.status(404).json({ error: errors[0].message });
|
||||
|
||||
const accommodation = dayService.updateAccommodation(id, existing, { place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes });
|
||||
const accommodation = dayService.updateAccommodation(id, existing, { place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes });
|
||||
res.json({ accommodation });
|
||||
broadcast(tripId, 'accommodation:updated', { accommodation }, req.headers['x-socket-id'] as string);
|
||||
});
|
||||
|
||||
@@ -170,6 +170,7 @@ export interface DayAccommodation {
|
||||
start_day_id: number;
|
||||
end_day_id: number;
|
||||
check_in: string | null;
|
||||
check_in_end: string | null;
|
||||
check_out: string | null;
|
||||
confirmation: string | null;
|
||||
notes: string | null;
|
||||
@@ -220,17 +221,18 @@ interface CreateAccommodationData {
|
||||
start_day_id: number;
|
||||
end_day_id: number;
|
||||
check_in?: string;
|
||||
check_in_end?: string;
|
||||
check_out?: string;
|
||||
confirmation?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export function createAccommodation(tripId: string | number, data: CreateAccommodationData) {
|
||||
const { place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes } = data;
|
||||
const { place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes } = data;
|
||||
|
||||
const result = db.prepare(
|
||||
'INSERT INTO day_accommodations (trip_id, place_id, start_day_id, end_day_id, check_in, check_out, confirmation, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
).run(tripId, place_id, start_day_id, end_day_id, check_in || null, check_out || null, confirmation || null, notes || null);
|
||||
'INSERT INTO day_accommodations (trip_id, place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
).run(tripId, place_id, start_day_id, end_day_id, check_in || null, check_in_end || null, check_out || null, confirmation || null, notes || null);
|
||||
|
||||
const accommodationId = result.lastInsertRowid;
|
||||
|
||||
@@ -239,6 +241,7 @@ export function createAccommodation(tripId: string | number, data: CreateAccommo
|
||||
const startDayDate = (db.prepare('SELECT date FROM days WHERE id = ?').get(start_day_id) as { date: string } | undefined)?.date || null;
|
||||
const meta: Record<string, string> = {};
|
||||
if (check_in) meta.check_in_time = check_in;
|
||||
if (check_in_end) meta.check_in_end_time = check_in_end;
|
||||
if (check_out) meta.check_out_time = check_out;
|
||||
db.prepare(`
|
||||
INSERT INTO reservations (trip_id, day_id, title, reservation_time, location, confirmation_number, notes, status, type, accommodation_id, metadata)
|
||||
@@ -258,25 +261,27 @@ export function getAccommodation(id: string | number, tripId: string | number) {
|
||||
|
||||
export function updateAccommodation(id: string | number, existing: DayAccommodation, fields: {
|
||||
place_id?: number; start_day_id?: number; end_day_id?: number;
|
||||
check_in?: string; check_out?: string; confirmation?: string; notes?: string;
|
||||
check_in?: string; check_in_end?: string; check_out?: string; confirmation?: string; notes?: string;
|
||||
}) {
|
||||
const newPlaceId = fields.place_id !== undefined ? fields.place_id : existing.place_id;
|
||||
const newStartDayId = fields.start_day_id !== undefined ? fields.start_day_id : existing.start_day_id;
|
||||
const newEndDayId = fields.end_day_id !== undefined ? fields.end_day_id : existing.end_day_id;
|
||||
const newCheckIn = fields.check_in !== undefined ? fields.check_in : existing.check_in;
|
||||
const newCheckInEnd = fields.check_in_end !== undefined ? fields.check_in_end : existing.check_in_end;
|
||||
const newCheckOut = fields.check_out !== undefined ? fields.check_out : existing.check_out;
|
||||
const newConfirmation = fields.confirmation !== undefined ? fields.confirmation : existing.confirmation;
|
||||
const newNotes = fields.notes !== undefined ? fields.notes : existing.notes;
|
||||
|
||||
db.prepare(
|
||||
'UPDATE day_accommodations SET place_id = ?, start_day_id = ?, end_day_id = ?, check_in = ?, check_out = ?, confirmation = ?, notes = ? WHERE id = ?'
|
||||
).run(newPlaceId, newStartDayId, newEndDayId, newCheckIn, newCheckOut, newConfirmation, newNotes, id);
|
||||
'UPDATE day_accommodations SET place_id = ?, start_day_id = ?, end_day_id = ?, check_in = ?, check_in_end = ?, check_out = ?, confirmation = ?, notes = ? WHERE id = ?'
|
||||
).run(newPlaceId, newStartDayId, newEndDayId, newCheckIn, newCheckInEnd, newCheckOut, newConfirmation, newNotes, id);
|
||||
|
||||
// Sync check-in/out/confirmation to linked reservation
|
||||
const linkedRes = db.prepare('SELECT id, metadata FROM reservations WHERE accommodation_id = ?').get(Number(id)) as { id: number; metadata: string | null } | undefined;
|
||||
if (linkedRes) {
|
||||
const meta = linkedRes.metadata ? JSON.parse(linkedRes.metadata) : {};
|
||||
if (newCheckIn) meta.check_in_time = newCheckIn;
|
||||
if (newCheckInEnd) meta.check_in_end_time = newCheckInEnd;
|
||||
if (newCheckOut) meta.check_out_time = newCheckOut;
|
||||
db.prepare('UPDATE reservations SET metadata = ?, confirmation_number = COALESCE(?, confirmation_number) WHERE id = ?')
|
||||
.run(JSON.stringify(meta), newConfirmation || null, linkedRes.id);
|
||||
|
||||
@@ -123,9 +123,9 @@ export function createReservation(tripId: string | number, data: CreateReservati
|
||||
// Sync check-in/out to accommodation if linked
|
||||
if (accommodation_id && metadata) {
|
||||
const meta = typeof metadata === 'string' ? JSON.parse(metadata) : metadata;
|
||||
if (meta.check_in_time || meta.check_out_time) {
|
||||
db.prepare('UPDATE day_accommodations SET check_in = COALESCE(?, check_in), check_out = COALESCE(?, check_out) WHERE id = ?')
|
||||
.run(meta.check_in_time || null, meta.check_out_time || null, accommodation_id);
|
||||
if (meta.check_in_time || meta.check_in_end_time || meta.check_out_time) {
|
||||
db.prepare('UPDATE day_accommodations SET check_in = COALESCE(?, check_in), check_in_end = COALESCE(?, check_in_end), check_out = COALESCE(?, check_out) WHERE id = ?')
|
||||
.run(meta.check_in_time || null, meta.check_in_end_time || null, meta.check_out_time || null, accommodation_id);
|
||||
}
|
||||
if (confirmation_number) {
|
||||
db.prepare('UPDATE day_accommodations SET confirmation = COALESCE(?, confirmation) WHERE id = ?')
|
||||
@@ -257,9 +257,9 @@ export function updateReservation(id: string | number, tripId: string | number,
|
||||
const resolvedMeta = metadata !== undefined ? metadata : (current.metadata ? JSON.parse(current.metadata as string) : null);
|
||||
if (resolvedAccId && resolvedMeta) {
|
||||
const meta = typeof resolvedMeta === 'string' ? JSON.parse(resolvedMeta) : resolvedMeta;
|
||||
if (meta.check_in_time || meta.check_out_time) {
|
||||
db.prepare('UPDATE day_accommodations SET check_in = COALESCE(?, check_in), check_out = COALESCE(?, check_out) WHERE id = ?')
|
||||
.run(meta.check_in_time || null, meta.check_out_time || null, resolvedAccId);
|
||||
if (meta.check_in_time || meta.check_in_end_time || meta.check_out_time) {
|
||||
db.prepare('UPDATE day_accommodations SET check_in = COALESCE(?, check_in), check_in_end = COALESCE(?, check_in_end), check_out = COALESCE(?, check_out) WHERE id = ?')
|
||||
.run(meta.check_in_time || null, meta.check_in_end_time || null, meta.check_out_time || null, resolvedAccId);
|
||||
}
|
||||
const resolvedConf = confirmation_number !== undefined ? confirmation_number : current.confirmation_number;
|
||||
if (resolvedConf) {
|
||||
|
||||
Reference in New Issue
Block a user