다크모드
임대차 독립 제품 원장 — BE 핸드오프
제품 경계
기본 원장 정본은 20260717123947_lease_product_financial_ledgers.sql, 계약 목록·초안 편집 출시 보강은 20260718200000_lease_contract_draft_release.sql이다. 둘 다 -- product-owner: lease이며 업무 FK는 Company, Office, property_spaces, office_party_assignments와 임대차 자체 테이블만 사용한다. 후속 migration은 기존 lease_contracts·lease_contract_terms를 확장할 뿐 중복 계약 테이블을 만들지 않는다. 회계·관리비·전자문서·숙박 테이블/FK/RPC 의존은 없다.
모든 public RPC 경로는 actor 인증, Office 관리 권한, private.require_office_product(company, office, 'lease')를 검증한다. 공개 테이블은 RLS+authenticated SELECT만 허용하고 쓰기는 RPC 전용이다. private.can_read_lease_office는 Office 접근과 유효한 lease entitlement를 함께 요구하므로 상품이 없는 Office의 계약·조건·차임·보증금·정산 행은 읽히지 않는다. public RPC는 invoker wrapper, private impl은 actor·권한을 다시 검증한다. request key는 Company 범위에서 멱등이며 같은 key의 다른 payload를 거부한다.
계약과 금액
lease_contracts: 공간·임차인·기간과draft → active → ended,draft → cancelled상태.lease_contract_terms: 월 차임, 약정 보증금, 주거/상가/기타, 과세/면세와 세율, 납부일. 활성화 후 불변.- v2 생성은 legacy alias를 해석하지 않고
property_space_id를 직접 받는다. - 활성화는 active unit·active party assignment·terms·기간 비중복을 서버에서 재검증한다.
update_lease_contract_v2는 draft aggregate 전용이다. 계약의 공간·임차인·표시명·기간과 terms의 임대구분·월 차임·보증금·세율·납부일을 한 트랜잭션에서 수정한다. 관리코드·계약 등록번호는 불변이다.- update는 contract row lock 뒤 expected revision을 확인하고, 공간과 임차인이 같은 Company·Office의 active 대상인지 다시 검증한다. aggregate revision은 contract revision이며 terms 자체 revision도 trigger가 증가시킨다.
- exact request payload에는 contract/space/tenant/기간/금액조건/expected revision이 모두 포함된다. 같은 request key·같은 payload는 저장된 응답을 재생하고 다른 payload는
lease_product_request_key_payload_conflict다. - 취소는 draft 전용, 사유 필수, 보증금 잔액 0을 요구한다.
- v2 계약 종료는 퇴거정산 final에서만 수행한다. legacy 점유기간 RPC는 호환용으로 남아 있다.
- FE의 계약 기간 입력은 SSOT DatePicker로 변경됐지만 RPC와 DB는 기존 ISO
date문자열(YYYY-MM-DD, 종료일 nullable)을 그대로 받는다. API·상태머신·기간 중복 검증에는 변화가 없다.
차임 원장
lease_rent_charges는 계약·월별 산정 스냅샷이다. 시작·종료월은 실제 점유일수를 해당 월 일수로 나눠 일할 계산한다. 과세 계약의 세액은 terms의 basis points를 스냅샷한다.
상태는 draft → confirmed|cancelled, confirmed → cancelled다. 금액·기간·세율은 생성 후 불변이고 확정 오류는 취소 후 재산정한다. confirmed가 외부 청구·회계 어댑터에 넘기는 경계이며 이 migration은 후속 상품을 호출하지 않는다.
FE read 계약은 authenticated RLS를 유지한 채 lease_rent_charges를 company_id + office_id로 필터하고 service_month desc로 조회한다. 읽는 필드는 식별자, 계약 FK, 서비스 기간·납부일, 과세 스냅샷, 공급가액·세액·합계, 상태·취소사유·revision이다. mutation은 기존 public RPC만 사용하며 직접 insert/update/delete하지 않는다.
차임 미수·수납 후속 원장
20260718230837_lease_rent_receivable_collections.sql은 확정 차임과 같은 트랜잭션의 trigger에서 lease_rent_receivable_installments를 한 건 생성한다. 기존 확정 차임도 멱등 backfill한다. 계약 등록번호·계약명·임차인·공간 표시는 lease-rent-receivable-subject.v1 snapshot으로 고정하며 계약 Billing·관리비 테이블을 참조하지 않는다.
lease_rent_receipts: 실제 차임 입금 사실lease_rent_receipt_allocations: 입금을 차임 installment에 충당한 사실lease_rent_receipt_reversals: 원 수납을 삭제하지 않는 1회 정정 사실private.lease_rent_collection_commands: Company request key exact replay/conflict 원장
공개 RPC는 list_lease_rent_receivables_v1, record_lease_rent_receipt_v1, reverse_lease_rent_receipt_v1이다. 수납은 installment advisory lock과 expected outstanding amount를 함께 사용해 동시 초과충당을 막는다. 상태는 KST as-of 기준 current|overdue|paid다. 유효 수납이 남은 확정 차임의 취소는 lease_rent_charge_cancel_after_collection_forbidden으로 거부된다.
네 public 테이블은 RLS와 authenticated SELECT만 제공하고 직접 write를 revoke한다. 명령 RPC는 actor·Office 관리권한·lease entitlement를 재검사한다. 차임 수납·정정은 플랫폼 outbox에 provider-neutral event를 남기며 accounting 상품이 있을 때만 선택 delivery를 요청한다. 회계 미설치·delivery 실패는 임대 차임 원장을 rollback하지 않는다.
보증금 원장은 차임 수납에 재사용하지 않는다. 보증금은 임차인에게 반환할 부채이고 차임 미수는 임차인에게 받을 채권이므로 테이블·잔액·UI를 분리한다.
보증금 원장
lease_deposit_ledger_entries는 append-only다.
receipt: 부채 잔액 증가, 누계가 약정액을 넘을 수 없음.refund: 잔액 감소, 현재 잔액 초과 불가.settlement_offset: final 정산이 내부에서만 추가.reversal: 수취·반환 정정. 같은 원행은 한 번만 reverse 가능.
잔액은 direction 합계로 계산한다. update/delete는 trigger로도 차단한다.
퇴거정산
draft는 실제 공과금 없이 저장할 수 있고 final은 실제 공과금의 명시 입력이 필요하다. 퇴거월에 미취소 차임 산정이 있으면 중복 공제를 막기 위해 저장·확정을 거부한다. final RPC는 settlement/contract를 잠그고 revision을 재검증한 뒤 다음을 한 트랜잭션에서 처리한다.
- 현재 보증금 잔액 재계산
- 공제액만큼
settlement_offset추가 - 정산 final 스냅샷 확정
- 계약을 퇴거일로
ended전환
공제 후 잔액은 반환 예정액이며 실제 지급 시 record_lease_deposit_refund를 호출한다. 공제 초과분은 additional_receivable_amount로 남지만 다른 청구 제품을 자동 호출하지 않는다.
검증
- 구조 테스트:
leaseProductFinancialLedgerMigration.spec.js - repo 매핑:
propertyOccupancyPeriodRepo.spec.js - 차임 조회·normalize:
propertyOccupancyPeriodRepo.spec.js의 Office rent-charge ledger 케이스 - SQL smoke:
supabase/tests/lease_product_financial_ledgers_smoke.sql - 차임 수납 구조 테스트:
src/composables/__tests__/leaseRentCollectionLedgerMigration.spec.js - 차임 수납 SQL smoke:
supabase/tests/lease_rent_receivable_collections_smoke.sql - draft release 구조:
leaseContractDraftReleaseMigration.spec.js - draft update·상품 RLS rollback smoke:
supabase/tests/lease_contract_draft_release_smoke.sql - fresh reset, DB lint/advisors, product boundary audit를 함께 실행한다.