다크모드
관리비 청구 대상 명부 (BE 참고 매뉴얼)
범위와 상태
관리비관리 단독 상품의 property_space × payer 관계를 저장하고 실제 부과·청구 subject snapshot으로 연결한다. 임대차·숙박 점유 원장을 조회하거나 FK로 요구하지 않는다. Migration 20260719125225_service_charge_subject_roster.sql. ✅구현
데이터 모델
| 테이블 | 역할 |
|---|---|
service_charge_subject_rosters | Company·Office당 optimistic revision 1개 |
service_charge_subject_roster_entries | 활성 유닛당 현재 청구 대상 1개 |
private.service_charge_subject_roster_commands | 요청 payload·응답 exact replay와 거절 이력 |
entry는 다음 UUID 범위 FK를 동시에 만족해야 한다.
(property_space_id, company_id, office_id)→property_spaces(member_assignment_id, office_id, member_party_id)→office_party_assignments(member_party_id, company_id)→master_parties
contract_management_code는 최초 연결 시 SC- + 공간 UUID/배정 UUID의 deterministic hash로 자동 생성한다. 같은 공간·같은 배정을 다시 저장하면 기존 코드를 유지한다. 고객 목록에는 노출하지 않는 관리코드다.
RPC 계약
get_service_charge_subject_roster(company_id, office_id)
반환값:
revisionentries[]: 공간 UUID, 유닛 등록코드/이름, 계약 관리코드, 당사자 UUID, Office 배정 UUID, 대상명·등록번호candidates[]: 활성member또는ar_customer역할 대상summary.unitCount,summary.assignedCount
인증, 관리비 entitlement, Office 읽기 권한을 모두 요구한다.
replace_service_charge_subject_roster(company_id, office_id, expected_revision, rows, request_key)
브라우저의 유일한 mutation gateway다. rows[]는 propertySpaceId, memberPartyId, memberAssignmentId만 허용한다. 서버가 다음을 한 트랜잭션에서 검증한다.
- Company·Office와 관리비 entitlement
- Office 관리 권한
- 요청 키 형식과 exact replay
- expected revision
- 활성 관리비 unit profile 소속 공간
- 같은 Office의 활성 party/assignment 및
member|ar_customer역할 - 공간 중복 없음
- 모든 활성 관리비 유닛 지정
검증 오류는 status=rejected와 errors[]로 명령 ledger에 남는다. 성공은 기존 entry 집합을 전부 교체하고 revision을 정확히 1 올린다. 같은 request key·같은 payload는 원래 응답을 반환하고 다른 payload는 service_charge_subject_roster_idempotency_conflict다.
보안
- 두 public 테이블 모두 RLS enabled
- authenticated/service_role은 public 테이블 SELECT만 명시 grant
- authenticated mutation은 public RPC execute만 허용
- public/anon의 함수 실행과 모든 직접 DML을 revoke
- definer 함수는
search_path = '' - RLS는 관리비 entitlement와
private.can_read_service_charge_office를 함께 확인
부과·청구 불변식
프런트는 roster entry를 useAllocations의 member/contract/allocation 원자로 변환한다. 부과확정 payload의 각 allocation subject는 다음을 동결한다.
text
service-charge-subject.v1
contractManagementCode
propertySpaceId
memberPartyId
memberAssignmentId서버의 기존 subject projection 검증과 invoice v3 검증이 현재 Company·Office의 공간·당사자·배정·역할 및 부과 Fact 금액을 재검증한다. 따라서 roster는 편집 가능한 현재 설정이고, charging/invoice facts가 과거 관계 SSOT다.
테스트와 운영 확인
- SQL:
supabase/tests/service_charge_subject_roster_smoke.sql - migration contract:
serviceChargeSubjectRosterMigration.spec.js - client exact RPC:
serviceChargeSubjectRosterRepo.spec.js - 실제 allocation → charging subject → invoice v3:
serviceChargeOperationalSubjectE2e.spec.js
SQL smoke는 로컬 disposable DB에서만 실행하며 항상 rollback한다.