Bookings for the hotel's car park. Screens under /dashboard/parking. Project
overview in ../general/README.md.
The hotel rents parking spaces to its clients by the day. It is necessary to know which space is free on each date, not book the same one twice, record who comes in and out, and charge the right amount. The module keeps each space's calendar, each booking's lifecycle and the charging, with a real-time garage map and occupancy statistics.
All roles except mantenimiento. Any user with access creates, edits,
charges and closes bookings. Only an admin can delete a vehicle. The amount
charged, its method and reference stay editable in any booking state, also
completed. This is deliberate for now (ADR-032).
- Panel (
/dashboard/parking): occupancy, expected entries and exits and free spaces for today, another day or a date range. - Status map (
/dashboard/parking/status): each space on each floor with its situation (free, reserved, occupied) and direct actions: entry, exit, cancel, no-show and expired bookings. - Booking list (
/dashboard/parking/bookings): search by text, state, date range and quick filters for the day (entries, exits, inside…), with pagination. The backend also allows filtering by space, licence plate, owner or origin, but the screen does not offer it. - New booking: space, vehicle (searched by licence plate or owner, or registered), expected dates, origin (direct, Booking, Expedia, Airbnb, agency), external reference and notes. The price is calculated automatically, but can be set manually. Today the origin and external reference are lost when creating the booking (it stays "direct"); they are only saved if it is edited afterwards.
- Booking detail (
/dashboard/parking/bookings/<code>): edit dates or space, register entry and exit, charge and delete.
Five tables (backend/db-mysql/aiven/04_parking_tables.sql):
parking_spots: the spaces — floor (-2or-3), number, type and whether it is active. Today there are 20: 10 per floor.parking_vehicles: licence plate, owner, model and notes. A vehicle is reused between bookings.parking_bookings: the booking — code, space, vehicle, expected and real dates, state, amount, payment (amount, method, reference, date), origin and who created and modified it.parking_rates: price according to the number of days, from 1 to 30. It does not depend on the space type.parking_availability: a precalculated calendar, with one row per space and day that says whether it is free and which booking occupies it.
Part of the logic lives in the database: the triggers in
05_parking_functions_triggers.sql generate the booking code and keep the
calendar up to date.
The database also has 2 functions (check_availability,
get_total_availability) and 5 procedures (06_parking_procedures.sql) that
nothing calls, neither the code nor the triggers; only the local seeds run
generate_availability, which the backend replaced with extendAvailability.
daily_parking_maintenance would mark expired bookings as no-shows if someone
ran it by hand. They stay in the database (decided on 2026-10-12).
Booking states
reserved ──► checked_in ──► completed
│ │
├──► canceled ◄┘
└──► no_show
- Entry is only registered on a
reservedbooking, and exit on achecked_inone. Cancel is valid for both; "no-show" only forreserved. - Only a
reservedbooking can be deleted. The deletion is definitive. - An expired booking is one that stays
checked_inafter its expected exit.
Availability
- A booking occupies its space from the entry day to the day before the exit.
When creating it, or changing its space or dates while it is
reservedorchecked_in, the backend rejects it if another active booking holds any of those days. The check reads the bookings themselves and locks the space row first, so two requests for the same space at the same time cannot both pass. When creating, a database trigger checks the calendar again. - When moving to
completed,canceledorno_show, the booking's own days are freed automatically; days of other bookings are never touched. - The calendar (
parking_availability) covers the next 365 days. The backend extends it when it starts and every day at 03:00 (Madrid), adding only the missing days and blocking those inside active bookings.
Price
- Calendar days between expected entry and exit are counted, with a minimum of 1: entering on the 25th at 23:00 and leaving on the 26th at 10:00 is 1 day.
- The price comes from
parking_ratesfor that number of days. There are packages: 7 days cost less than 6, and 30 less than 29. From 31 days there is no rate and 15 € per day are charged. - When charging, what is paid is saved separately from the calculated amount, with method (cash, card, transfer, agency) and reference.
Others
- The booking code is generated by the database:
PK-AAAAMMDD-NNNN(creation date and number of the day). It is what is seen in URLs; the internalidis not shown. - Who creates or modifies a booking is taken from the session, not from what the browser sends.
- Listings return 50 bookings by default and 500 as maximum.
page.tsx (server) ─► getParkingDashboardStats ─► GET /api/parking/stats
ParkingStatusClient ─► useParkingStatus (React Query) ─┐
BookingsListClient / BookingDetailClient ──────────────┼─► parkingApi (queries.ts) ─► apiClient
│
/api/parking/{spots,vehicles,bookings,stats}
│
parking · bookings · stats controllers ─► repositories ─► MySQL
│
triggers ─► parking_availability
- The panel is loaded on the server (
getParkingDashboardStatsaction) and arrives with the day's data already. - The status map requests spaces, bookings, expired ones and statistics with React Query. It does not refresh on its own: each action requests the data again, but changes made by another person do not appear until reloading. Left as is (decided on 2026-10-12).
- Creating, moving, closing or cancelling a booking writes to
parking_bookingswithin a transaction; the triggers update the calendar in the same operation. /api/parking/statsworks in three modes: without parameters (today), withdate(one day) or withstartDateandendDate(a range, without pending entries and exits).