# Roomio тарифници и серверски пресметани цени

Ажурирано: 09.10.2026. Endpoint: `POST https://vps.xnet.mk:50443/api/commands`,
`client.appId: "roomio"`, v1 envelope со проверени `authentication.username/password`
во секое барање. LAN/offline endpoint-от има идентични операции на
`http://127.0.0.1:5088/api/commands`.

## Операции

- `room.rates.list`: `{ companyId }` → `{ rooms, rates, canEdit }`.
- `room.rates.save`: `{ companyId, requestId, rate }` → зачуван `RoomRatePlan` со нова `version`.
- `room.rates.quote`: `{ companyId, roomId, checkIn, checkOut, adults, children }` → серверски зачуван `RoomPriceQuote` со рок од 15 минути.
- `room.reservation.request-status`: `{ companyId, requestId }` → `{ status, reservation }` за истиот actor/company.
- `room.reservation.availability` го задржува `floors, roomTypes, rooms, canEdit` и додава `priceQuotes` за достапните соби што имаат тарифник.

`RoomRatePlan` има `roomId`, `version`, `standardNightPrice`,
`standardExtraBedPrice`, `currency` и `seasons`. Сезоната има `id`, `from`, `to`,
`nightPrice`, `extraBedPrice`; двата датума се инклузивни. Преклопување, вклучувајќи
заеднички граничен датум, се одбива со `rates-overlap`.

`RoomPriceQuote` има `quoteId`, itinerary, guests, `currency`, `total`, `nights`,
`expiresAtUtc` и `canOverride`. Check-out е ексклузивен. Цената е за целата соба
по ноќ. Дополнителни легла се `max(0, adults + children - room.beds)` и мора да
се во `room.extraBeds`. Сезонската цена целосно ја заменува стандардната за таа ноќ.

Од Listener `1.5.1`, batch quote читањето почнува дури откако inventory reader-от
е затворен. Соби без тарифник и понатаму се враќаат во `rooms`, без запис во
`priceQuotes`; недостиг на тарифник не се претвора во празна availability листа.

## Резервациски snapshot

`ReservationRecord` додава `pricingMode: "manual"|"tariff"`, `pricingQuoteId` и
`priceNights`. За `tariff` серверот ја проверува понудата, actor/company, соба,
период, гости, рок и rate revision во истата трансакција и сам го зачувува snapshot-от.
Постојните записи се читаат како `manual` со оригиналните `price/currency`.
Промена на тарифник никогаш не ги менува старите snapshot-и.

`room.rates.save` и `room.reservation.save` се идемпотентни по `requestId`, actor и
canonical payload hash. Replay се решава пред version/quote проверки. Рачна нова
или изменета договорена цена бара owner/admin/manager. Legacy manual запис со
непроменет престој и цена го задржува постојниот edit пат.

## Грешки

- `rates-validation` — HTTP 400, дефинитивно без mutation.
- `rates-overlap`, `rates-conflict`, `rates-missing`, `rates-quote-expired`,
  `rates-quote-conflict`, `request-conflict` — HTTP 409, дефинитивно без mutation.
- `rates-override-denied`, `hotel-access-denied` — HTTP 403.

Не се користат клиентски `priceNights` како авторитет и нема автоматски write retry
по неодреден мрежен исход. Клиентот го проверува истиот `requestId` со
`room.reservation.request-status`.
