Skip to content

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) + c-kim(김철수 12/1631) 승계. 홍길동 = 101(c-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 변경 자동 적응). 시드 스케일/구조 변경 시 매직넘버 금지.