다크모드
BE 참고 — 계약/유닛/멤버 3축 빌링 (as-built)
독자: BE 개발자/AI. 콘솔 프로토의 빌링 3축(부과·청구·수납 × 유닛/멤버/계약) 데이터 모델·집계 규칙·불변식·엣지케이스. 정본 결정:
docs/decisions/PATTERN-TENANCY-AXES-CONTRACT-UNIT-MEMBER-2026-06-22.md(§1~§9). 관련: [[collecting-detail.md]] · [[demo-seed-factory.md]] · [[unit-occupancy.md]]. ⚠ 프로토 = 결정적 데모 시드(src/composables/demoSeed.js). "as-built"(현 구현)과 "실서비스 과제"(BE 모델 필요)를 구분 표기.
1. 핵심 모델 — 계약 = 1유닛 × 1멤버 (호실별)
- 계약(contract) = 1유닛 × 1멤버(캐논 §9). 한 멤버가 다유닛 보유 시 계약 N개(예: 홍길동 =
c-hong/101 +c-hong-102/102). - 배분 라인(line) = 계약 × 유닛 × charge × 기간. 필드:
id, chargeCode, contractCode, unitCode, memberCode, occupancyDays, start, end, basis{area,fixedShare,variableShare,usage}. - 3축 = 같은 라인의 group-by 투영:
- 계약축 = 계약별(원자). 유닛축 = unitCode별(승계 시 다멤버 합쳐짐). 멤버축 = memberCode별(다유닛 합쳐짐).
- 시드 예: 101호 =
c-hong(홍길동 12/115) +31) 승계. 홍길동 = 101(c-kim(김철수 12/16c-hong) + 102(c-hong-102) 다유닛.
2. 부과 배분 (allocate) — 원자1·공식1·피벗3
- 공식: 라인 배분액 =
chargePrincipal × weight/Σweight,weight = lineWeight(line, chargeBasis)(지분×점유일수비율). 마지막 라인 잔차 흡수(Σ = chargePrincipal 정확).useAllocations.allocate(charge)→{line.id: amount}(그 charge 라인만). - 피벗:
unitOccupants(unitCode)(유닛 점유자별),memberHoldings(memberCode)(멤버 보유유닛별),contractAtoms()(계약×유닛 원자). 각 = 라인 GROUP·SUM, Σ = currentPeriod. - 부과 화면 행렬(charging/allocation-{contract,unit,member}): 행=charge, 열=축 엔티티, 셀 =
Σ(allocate(charge)[line])over 엔티티의 그 charge 라인. 열 Σ = 엔티티 총부과 / 행 Σ = charge 풀. - ⚠ as-built:
occupancyDays·start/end시드 고정. 실서비스: 점유이력(체크인/아웃)에서 파생.
3. 정산 (forwarding/수납) — 계약 단위 + 통합 합산
- 정산(미수·연체료·충당·선수금)은 계약(contractCode) 단위.
useForwarding.collect(axis,key,amt)는 호출 축만collected[axis][key]갱신 → 수납은 실질 contract 축에 기록. - 통합(유닛/멤버) 상세 = 관련 계약 합산(
useCollectingDetail):buildOne(feeAxis, feeKey)= 한 계약(또는 prior-only 축행)의 차수·합계.detailFor(axis,key):- 계약 =
[key]/ 멤버 =contracts.filter(memberCode===key)/ 유닛 =lines.filter(unitCode===key)의 distinct contractCode. - 각 계약
buildOne→ 1개면 그대로, N개면mergePeriods(같은 label 합산: 원금·연체료·부과·수납·조정·구성 합, payments/breakdown concat). - prior-only 폴백: 당기 라인 없는 유닛/멤버(부과종료)는 축 네이티브 forwarding 행으로 단일 part.
- 계약 =
- 수납이 계약 귀속 → 계약별 상세 합산이 일부납까지 정합(구 "결함 F" 해소).
- identity:
unitLabel(다유닛=외 N) ·member(유닛 다멤버=외 N) ·aggregate플래그 ·contractCode=ccs[0]/contractCodes. 표기 순서: 멤버 통합=멤버 먼저, 계약·유닛=호수 먼저. - ⚠ 남은 실서비스 과제: ① 통합 화면 수납 입금은 대표계약(
ccs[0])에만 — 다계약 자동분배 미구현(합계·조회는 정확). ② 이월(prior) 점유자 분해 안 됨(계약단위). ③ 빌링 부담자 =useUnitOccupancy워터폴(공실 시 소유자) — tenancy rep ≠ billing payer.
4. 상태·금액 규칙 (목록)
- 총청구금 = 미수금 =
forwarding.total(당기 미납 + 이월 미납, 받을금액). 3축 동일 소스(계약 atomic도forwardingByContract(cc).total). - 상태:
total === 0 ? 완납 : 미수(미수금>0이면 미수 — 당기 미납 포함). 연체(overdue)는 별도overdueCount뱃지("미납 N차수", 납기 경과분만). 당기는 익월 납기라 미도래(overdue=false). - 용어: 총청구금(Amount billed)·미수금(Outstanding)·연체료. "청구액❌/가산금❌" — [[naming-standard-ssot]].
5. 세금계산서 (참고)
taxInvoicesByProfile()= 멤버 taxProfiles(사업장)별 발행. 과세→세금계산서, 면세→계산서, 영세→영세율. 가드: 공급가액>0 ∧ BRN 보유. 상세 [[tax-documents.md]].
6. 회귀 방어 (테스트)
useCollectingDetail.spec(통합=합산·identity 순서),useAllocations.spec(contractAtoms/unitOccupants/memberHoldings Σ),_seedExpect.js(어서션을 시드에서 동적 파생 — householdCount 변경 자동 적응). 시드 스케일/구조 변경 시 매직넘버 금지.