diff --git a/backend/migrations/030_emergency_close_days.sql b/backend/migrations/030_emergency_close_days.sql new file mode 100644 index 0000000..ff9b785 --- /dev/null +++ b/backend/migrations/030_emergency_close_days.sql @@ -0,0 +1 @@ +ALTER TABLE housekeeping_settings ADD COLUMN IF NOT EXISTS emergency_close_days INTEGER NOT NULL DEFAULT 1; diff --git a/backend/src/routes/housekeeping-settings.ts b/backend/src/routes/housekeeping-settings.ts index 8b4e36d..1a77a71 100644 --- a/backend/src/routes/housekeeping-settings.ts +++ b/backend/src/routes/housekeeping-settings.ts @@ -8,6 +8,7 @@ export interface HkSettings { checkout_priority: string inspection_after_clean: boolean require_completion_photo: boolean + emergency_close_days: number } const DEFAULT_SETTINGS: HkSettings = { @@ -15,11 +16,12 @@ const DEFAULT_SETTINGS: HkSettings = { checkout_priority: 'high', inspection_after_clean: true, require_completion_photo: false, + emergency_close_days: 1, } export async function getHkSettings(hotelId: string): Promise { const { rows } = await db.query( - 'SELECT checkout_auto, checkout_priority, inspection_after_clean, require_completion_photo FROM housekeeping_settings WHERE hotel_id = $1', + 'SELECT checkout_auto, checkout_priority, inspection_after_clean, require_completion_photo, emergency_close_days FROM housekeeping_settings WHERE hotel_id = $1', [hotelId], ) return rows[0] ?? DEFAULT_SETTINGS @@ -59,15 +61,16 @@ const housekeepingSettings: FastifyPluginAsync = async (fastify) => { const hotelId = await getHotelId(slug) if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' }) - const { checkout_auto, checkout_priority, inspection_after_clean, require_completion_photo } = request.body + const { checkout_auto, checkout_priority, inspection_after_clean, require_completion_photo, emergency_close_days } = request.body await db.query( - `INSERT INTO housekeeping_settings (hotel_id, checkout_auto, checkout_priority, inspection_after_clean, require_completion_photo) - VALUES ($1, $2, $3, $4, $5) + `INSERT INTO housekeeping_settings (hotel_id, checkout_auto, checkout_priority, inspection_after_clean, require_completion_photo, emergency_close_days) + VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (hotel_id) DO UPDATE SET checkout_auto = COALESCE(EXCLUDED.checkout_auto, housekeeping_settings.checkout_auto), checkout_priority = COALESCE(EXCLUDED.checkout_priority, housekeeping_settings.checkout_priority), inspection_after_clean = COALESCE(EXCLUDED.inspection_after_clean, housekeeping_settings.inspection_after_clean), require_completion_photo = COALESCE(EXCLUDED.require_completion_photo, housekeeping_settings.require_completion_photo), + emergency_close_days = COALESCE(EXCLUDED.emergency_close_days, housekeeping_settings.emergency_close_days), updated_at = NOW()`, [ hotelId, @@ -75,6 +78,7 @@ const housekeepingSettings: FastifyPluginAsync = async (fastify) => { checkout_priority ?? DEFAULT_SETTINGS.checkout_priority, inspection_after_clean ?? DEFAULT_SETTINGS.inspection_after_clean, require_completion_photo ?? DEFAULT_SETTINGS.require_completion_photo, + emergency_close_days ?? DEFAULT_SETTINGS.emergency_close_days, ], ) return getHkSettings(hotelId) diff --git a/backend/src/routes/housekeeping.ts b/backend/src/routes/housekeeping.ts index b553909..d803a65 100644 --- a/backend/src/routes/housekeeping.ts +++ b/backend/src/routes/housekeeping.ts @@ -81,6 +81,24 @@ const housekeeping: FastifyPluginAsync = async (fastify) => { [hotelId, room_id ?? null, type, priority, assignee_id ?? null, notes ?? null, due_date ?? null, category, photos], ) + // If priority=urgent and room_id → close the room for emergency_close_days + if (priority === 'urgent' && room_id && category === 'maintenance') { + const settings = await getHkSettings(hotelId).catch(() => null) + const closeDays = settings?.emergency_close_days ?? 1 + const from = new Date().toISOString().slice(0, 10) + const toDate = new Date() + toDate.setDate(toDate.getDate() + closeDays) + const to = toDate.toISOString().slice(0, 10) + await db.query( + `UPDATE rooms SET status = 'maintenance', maintenance_from = $2, maintenance_to = $3 WHERE id = $1`, + [room_id, from, to], + ) + broadcast(slug, { + type: 'room_updated', + room: { id: room_id, status: 'maintenance', maintenanceFrom: from, maintenanceTo: to }, + }) + } + const { rows } = await db.query( `SELECT t.*, r.number AS room_number, r.type AS room_type, u.name AS assignee_name FROM housekeeping_tasks t diff --git a/src/lib/api.ts b/src/lib/api.ts index 0b5f9f6..87e9392 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -565,6 +565,7 @@ export interface HkSettings { checkout_priority: string inspection_after_clean: boolean require_completion_photo?: boolean + emergency_close_days?: number } export interface HotelPayload { diff --git a/src/pages/HousekeepingPage.tsx b/src/pages/HousekeepingPage.tsx index 898eda2..32f425a 100644 --- a/src/pages/HousekeepingPage.tsx +++ b/src/pages/HousekeepingPage.tsx @@ -114,6 +114,7 @@ export function HousekeepingPage() { checkoutPriority: s.checkout_priority as 'high' | 'medium', inspectionAfterClean: s.inspection_after_clean, requireCompletionPhoto: s.require_completion_photo ?? false, + emergencyCloseDays: s.emergency_close_days ?? 1, })) }).catch(console.error) .finally(() => setLoading(false)) @@ -136,6 +137,7 @@ export function HousekeepingPage() { dailyStartFromDay: 1, onDemandEnabled: true, onDemandPriority: 'medium' as 'high' | 'medium' | 'low', deepCleanEnabled: false, deepCleanEveryDays: 7, inspectionAfterClean: true, autoAssign: false, requireCompletionPhoto: false, + emergencyCloseDays: 1, }) const setP = (k: K, v: typeof plans[K]) => @@ -150,6 +152,7 @@ export function HousekeepingPage() { checkout_priority: plans.checkoutPriority, inspection_after_clean: plans.inspectionAfterClean, require_completion_photo: plans.requireCompletionPhoto, + emergency_close_days: plans.emergencyCloseDays, } await api.housekeeping.saveSettings(slug, settings) setPlanSaved(true) @@ -524,6 +527,34 @@ export function HousekeepingPage() { +
+
+

Экстренное закрытие номера

+

+ При отчёте о критической поломке (статус «Экстренно!») номер автоматически закрывается для бронирования +

+
+
+ +
+ {[1, 2, 3, 5, 7, 14].map(n => ( + + ))} +
+
+
+
{roomLabel}