Skip to content

호스피탈리티 예약 백엔드 계약

As-built

정본은 20260717124402_hospitality_reservation_room_allocation.sql이다. hospitality_roomsproperty_spaces에 직접 연결한 호스피탈리티 소유 재고이고, hospitality_reservationshospitality_room_allocations가 예약 기간과 룸 홀드·확정 배정을 소유한다. 임대, 관리비, 청구, 회계, CRM 표나 RPC를 참조하지 않는다.

상태는 다음과 같다.

text
draft ──hold──> held ──confirm──> confirmed ──check-in──> checked_in
  └────────────── cancel <──────────────┘

held allocation은 만료 시각 전까지 availability 검사에 포함된다. 확정·체크인 allocation은 PostgreSQL exclusion constraint로 같은 Office·룸·tstzrange [start,end) 겹침을 차단한다. 홀드는 room별 advisory transaction lock과 활성 hold 조회로 동시 요청을 직렬화한다.

RPC

  • create_hospitality_room: hospitality_room_id alias를 해석해 property space에 연결
  • create_hospitality_reservation: draft 생성 또는 룸을 포함한 held 생성
  • update_hospitality_reservation: draft/held 속성과 기간 수정
  • hold_hospitality_reservation: 새 룸 홀드 및 기존 홀드 해제
  • confirm_hospitality_reservation
  • cancel_hospitality_reservation: 활성 allocation을 released로 전환
  • check_in_hospitality_reservation: 기존 hospitality_stays를 생성하고 reservation/allocation을 한 트랜잭션에서 checked_in으로 전환

모든 공개 RPC는 auth.uid() actor, Company·Office manager 권한, private.require_office_product(..., 'hospitality')를 검사한다. 모든 명령은 private.hospitality_reservation_commands에 command별 request key, payload, response를 보존한다. 재시도 payload가 다르면 거부한다. update/transition은 expected_revision을 요구한다.

authenticated는 RLS가 적용된 세 표의 SELECT와 공개 RPC EXECUTE만 가진다. 직접 INSERT/UPDATE/DELETE는 허용하지 않는다. private helper와 command table은 service role만 접근한다.

체크인 불변식

체크인은 confirmed reservation과 confirmed allocation만 받는다. allocation의 room은 hospitality_rooms → property_spaces 경계 안에서 검증된 룸이고, room operational state가 inspected여야 한다. 기존 stay 함수에 source_reservation_key=reservation.id를 전달하고, stay 생성·예약 전이 중 어느 단계라도 실패하면 전부 rollback된다. 자세한 readiness 계약은 hospitality-room-operations.md를 따른다. lease는 조회하거나 연결하지 않는다.

미구현 경계

위약금, 예약금 환불, 게스트 청구, 회계 분개, OTA 정산은 이 계약에 없다. 예약 취소 성공을 근거로 다른 제품 표를 직접 쓰면 안 된다. 후속 기능은 이벤트/명시적 계약으로 연결해야 한다.

그룹 객실 블록, 외부 예약 채널 가져오기, 채널 정산서 원장·대사 RPC도 as-built 계약에 없다. 예약 콘솔은 이 기능을 호출하거나 샘플 데이터로 대신하지 않는다. 향후 구현은 각각 별도 소유 원장과 멱등 command, 공급자 중립 adapter를 정의한 뒤 예약 원장과 명시적으로 연결해야 한다. 해당 adapter나 원장이 없어도 직접 예약 생성·홀드·확정·취소·체크인은 독립적으로 동작해야 한다.

hospitality_reservations.channel의 원천 식별자는 내부 연동용 데이터다. 프런트 정규화는 직접 예약 식별자만 직접, 그 외 값은 모두 공급사 중립 예약 채널로 표시하며 원천 업체명을 고객 표면에 전달하지 않는다.

검증

  • 실행형 rollback: supabase/tests/hospitality_reservation_room_allocation_smoke.sql
  • 정적 계약: hospitalityReservationMigration.spec.js
  • repo 계약: hospitalityReservationRepo.spec.js

숙박 folio·체크아웃 정산 (2026-07-18)

정본은 20260717173314_hospitality_financial_ledgers.sql이다. 호스피탈리티가 folio, append-only entry, checkout settlement, channel receivable을 소유한다. list_hospitality_stays_v1get_hospitality_folio_v1이 조회 payload를 제공하고, confirm_hospitality_checkout_v2가 expected stay/folio revision과 서버 계산 정산액을 검증한다.

확정 명령은 request key와 payload를 보존한다. settlement·정산 entry·선택 channel receivable·folio open → settled·stay checked_in → checked_out이 한 트랜잭션이다. 회계·청구·공간운영은 직접 호출하지 않으며 버전이 붙은 private.hospitality_integration_outbox와 adapter status만 선택 연결 seam으로 둔다. consumer가 없어도 체크아웃은 완료된다.

과거 혼합 migration의 check_out_hospitality_stay EXECUTE는 forward migration에서 회수했고 이력 파일은 보존했다. 위약금, 환불, 실제 결제 승인, 회계 전기, 외부 청구 생성, 채널 정산서 대사는 미구현이다.

스테이 출시 화면은 위 세 RPC만 재사용한다. 새 스테이·folio·checkout 표나 RPC를 만들지 않으며 예약 체크인의 hospitality_stays → hospitality_folios 생성 결과가 유일한 SSOT다. 목록과 상세에서 실제 folio.entries가 0건이면 그대로 빈 원장으로 반환하고 UI가 가상 posting·분개를 보충하지 않는다. 체크아웃 성공 뒤 동일 Company·Office scope로 목록을 재조회하며, 요청 중 Office가 바뀌면 이전 응답은 폐기한다.

immediate, city_ledger, channel_ledger는 호스피탈리티 정산 원장의 방식이다. city_ledgerchannel_ledger도 회계·청구 상품의 존재를 체크아웃 전제조건으로 삼지 않는다. 선택 outbox consumer가 없으면 adapter 상태는 대기로 남지만 stay checked_out과 folio settled은 유효하다. 고객 표면의 채널명은 직접 또는 공급사 중립 예약 채널만 사용한다.

  • 실행형 rollback: supabase/tests/hospitality_financial_ledgers_smoke.sql
  • 정적 계약: hospitalityFinancialLedgerMigration.spec.js