Skip to content

프로퍼티 실제 점유기간 백엔드 계약

As-built

정본 마이그레이션은 supabase/migrations/20260717104811_lease_hospitality_occupancy_ledgers.sql이다. 공간 식별은 Wave 5의 property_spaces를 그대로 사용하고, 임대와 호스피탈리티는 상태머신·시간 정밀도가 달라 별도 원장으로 둔다.

원장기간상태겹침 기준
lease_contractsdaterange [starts_on, ends_on+1)draft → active → ended, draft → cancelledactive/ended만 같은 Office·공간에서 금지
hospitality_stayststzrange [checked_in_at, checked_out_at)checked_in → checked_out모든 실제 스테이에서 같은 Office·공간 금지

두 표는 (property_space_id, company_id, office_id) 복합 FK를 가진다. 쓰기 RPC는 lease_unit_key 또는 hospitality_room_id alias를 해석한 뒤 직접 FK를 저장한다. legacy key 자체는 추적·화면 호환용이며 join 정본이 아니다.

명령 계약

  • create_lease_contract
  • activate_lease_contract
  • end_lease_contract
  • cancel_lease_contract
  • check_in_hospitality_stay
  • check_out_hospitality_stay

공개 함수는 security invoker, 구현 함수는 빈 search_path의 private security definer다. 모든 변경 명령은 회사 범위 요청 키, payload 동일성, advisory transaction lock을 사용한다. 자동 관리코드와 서버 기본 시각은 요청 payload 비교 뒤에 생성하므로 null 기본값 재시도도 동일 응답을 돌려준다.

클라이언트는 check-in/out/end/cancel command signature별 request key를 성공 응답 전까지 보존해야 한다. 타임아웃·네트워크 종료 여부가 불확실한 재시도에서 새 키를 만들면 중복 전이가 될 수 있다.

전이는 expected_revision을 요구하고 trigger가 revision과 updated_at을 증가시킨다. 직접 UPDATE/DELETE는 trigger로 거부한다. 임대 종료만 ends_on을 바꿀 수 있다. 활성 임대 취소는 허용하지 않고 종료로 이력을 남긴다.

보안과 감사

RLS SELECT는 기존 can_read_property_space_office를 사용한다. authenticated에는 테이블 SELECT와 공개 RPC EXECUTE만 명시적으로 부여한다. 쓰기는 manager 검사를 통과한 RPC로만 한다. private security-definer payload helper는 authenticated EXECUTE를 주지 않고 service_role과 소유자 함수 호출에만 제한한다. 각 성공 명령은 operational_audit_events에 before/after와 공간 ID를 남긴다.

금액 원장 게이트

lease_contracts 자체는 점유기간과 식별·상태를 유지한다. 새 v2 계약의 금액은 임대 제품 소유 lease_contract_terms, lease_rent_charges, lease_deposit_ledger_entries에 저장한다. 이전 계약의 null 금액은 0원으로 변환하지 않는다. v2 종료는 finalize_lease_move_out_settlement만 사용해 정산·보증금 공제·점유 종료를 원자 처리한다. 기존 end_lease_contract는 금액 없는 legacy 점유기간 호환 API다.

불변식과 오류

  • lease_contract_period_overlap: 확정 임대기간 겹침
  • hospitality_stay_actual_period_overlap: 실제 스테이 겹침
  • property_occupancy_revision_conflict: 낡은 revision
  • property_occupancy_request_key_payload_conflict: 같은 요청 키의 다른 payload
  • *_space_link_required: legacy key에 대응하는 공간 alias 없음

예약, 소유권, 관리비 납부자 지정은 이 원장의 FK나 자동 파생으로 넣지 않는다. source_reservation_key는 체크인 근거의 선택적 외부 참조일 뿐 예약 원장이 아니다.

검증

  • 실행형 rollback: supabase/tests/property_occupancy_periods_smoke.sql
  • 정적 계약: src/composables/__tests__/propertyOccupancyPeriodMigration.spec.js

스모크는 생성·전이 재시도, revision 충돌, 두 종류 기간 겹침, 직접 쓰기 차단, 교차 회사 RLS, private payload helper 권한, 감사 5건을 검증한다.