Skip to content

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) — 배분 라인에서 distinct unitCode. 배지 숫자(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().unitCountunits.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.jsinvoicing unitCount = distinct 유닛 수 회귀 방어(unitCount === unitCodesByContract(계약).length).

6. 확장 포인트

  • 에디션별 표시명 분기(입주자/구분소유자/임대인 …) — 현재 중립어. 활성 에디션 → 라벨 맵.
  • 실데이터 점유 모델 — 현재 useUnitOccupancy.OCCUPANCY 데모 상수. 유닛 마스터에 owner(상시) + occupants[](기간) 연결 시 교체.
  • 일할 분할 — 한 부과월 내 임차 종료/공실 전환 시 부담자 일할 분할(현재 ASOF 단일 시점 resolution).
  • 세대/멤버 축 시트 — 현재 계약 축만. 유닛/멤버 탭에서도 동일 시트 재사용 가능.