다크모드
FE 핸드오프 — 유닛(호수) 시트 + 공실 워터폴
독자: FE 메인테이너/AI. 2026-06-22 신설. 부과/수납 리스트의 "유닛 개수" 셀 → 호수별 소유자/사용자/현재 관리비 부담자 시트의 라우트·컴포넌트·데이터 흐름·확장 포인트. 도메인 정본:
docs/REFERENCE-OCCUPANT-TERMINOLOGY-AND-VACANCY-WATERFALL.md· BE 핸드오프docs/handoff/backend/unit-occupancy.md.
1. 무엇인가
부과(invoicing)·수납(collecting) 계약 리스트의 유닛 컬럼(배지 = 호수 개수)을 클릭하면, 그 계약이 부과하는 호수별로 소유자 / 사용자 / 사용 기간 / 현재 관리비 부담자(공실 워터폴 결과)를 보여주는 시트가 열린다. 읽기 전용.
2. 진입 · 컴포넌트
- 진입: 관리비 앱 → 실제(actual) 에디션 콘솔 → 부과 또는 수납 → 계약 탭 → 행의
유닛배지 클릭.- 클릭 가능은 캐논(
service-charge+actual) 에디션에서만. 그 외(mock/잠정)는 정적 배지(시트 없음).
- 클릭 가능은 캐논(
- 시트 컴포넌트:
components/billing/_core/console/overlays/SheetUnitsArt.vue(유형 B — 단일 콘텐츠,table-static직접 배치,id="sheet-units",sheet-width-lg). - 선택 상태:
components/billing/_core/console/overlays/selectedUnits.js싱글톤 ref{ title, unitCodes[] }. - 마운트처(배지 +
<SheetUnitsArt />+openUnits):- 수납:
components/billing/_core/console/collecting/contract/blocks/DynamicTableOrg.vue - 부과:
components/billing/_core/console/invoicing/contract/blocks/DynamicTableOrg.vue(2026-06-22 배선)
- 수납:
3. 데이터 흐름 (읽기 전용)
계약 행 클릭 → openUnits(row): selectedUnits = { title: 계약명, unitCodes: row.unitCodes }
→ command="show-modal" commandfor="sheet-units"
SheetUnitsArt: useUnitOccupancy().unitsOf(selectedUnits.unitCodes, ASOF)
→ [{ unitCode, owner, occupant, period, payer, role, vacant, unknown }]row.unitCodes=useAllocations.unitCodesByContract(contractCode)— 배분 라인에서 distinctunitCode. 배지 숫자(unitCount)·시트 행수의 단일 소스.useUnitOccupancy(src/composables/useUnitOccupancy.js): 데모 점유 테이블(OCCUPANCY) + 시점 워터폴payerAt(unitCode, date)/ 목록unitsOf(unitCodes, date).ASOF='2025-12-15'(데모 부과월). Vue 무관 순수 로직 — 빌링 캐논(useAllocations)과 별개 표시·resolution 레이어(캐논 무편집·회귀 0).- 표시명: 코어 중립어(소유자/사용자). 에디션별 표시명 분기(입주자/사용자 · 구분소유자/점유자 · 임대인/임차인)는 후속(현재 중립어 표기).
4. unitCount 버그 정정 (2026-06-22)
- 수납(과거 정정): 행 빌더가
unitCount: unitCodesByContract(r.key).length사용 —DynamicTableOrg.vue. - 부과(이번 정정):
useAllocations.invoicingByContract().unitCount를units.length(= charge×unit 배분 라인 수, 차수배로 부풀려짐) →new Set(units.map(u => u.unit?.unitCode)).size(distinct 호수)로 소스 정정.- 부과 펼침(유닛 라인) 서브행도
aggregateByUnit()로 호수별 합산 — 배지 개수 = 펼침 행수 = 시트 행수 일치. 합계 불변(라인 금액 합 보존). invoicingByContract().unitCount의 유일 소비처는 이 배지(useForwarding·useBillingJournal 미사용·테스트 미assert) → 소스 정정 안전.
- 부과 펼침(유닛 라인) 서브행도
5. 테스트
src/composables/__tests__/useUnitOccupancy.spec.js— 워터폴 11종: 자가·공실 폴백·임차 중·종료일 경계(to 포함)·종료 익일·시작 이전·미상 유닛·부담자 불변식·unitsOf매핑/빈배열/미상.src/composables/__tests__/useAllocations.spec.js—invoicing unitCount = distinct 유닛 수회귀 방어(unitCount === unitCodesByContract(계약).length).
6. 확장 포인트
- 에디션별 표시명 분기(입주자/구분소유자/임대인 …) — 현재 중립어. 활성 에디션 → 라벨 맵.
- 실데이터 점유 모델 — 현재
useUnitOccupancy.OCCUPANCY데모 상수. 유닛 마스터에 owner(상시) +occupants[](기간) 연결 시 교체. - 일할 분할 — 한 부과월 내 임차 종료/공실 전환 시 부담자 일할 분할(현재 ASOF 단일 시점 resolution).
- 세대/멤버 축 시트 — 현재 계약 축만. 유닛/멤버 탭에서도 동일 시트 재사용 가능.