다크모드
부과항목 마스터 — BE 핸드오프
독자: BE 개발자/AI. 부과항목 정의 + 인스턴스 단일 캐논, active 전파 불변식, 분개 정합 제약, chargeCode 안정 키. 정본:
src/composables/useCharges.js+src/composables/useAllocationBasisValues.js+src/composables/demoSeed.js. 구현 상태: ✅구현 (2026-06-29, worktree feat/invoicing-detail-axis-aggregate).
0. 핵심 원칙 — single-canon
useCharges.charges가 부과항목의 유일한 소스다. 별도 카탈로그 store·마스터 테이블 없음.
- 설정>항목 화면 = 정의 필드(name/nature/taxCategory/taxInvoiceType/chargeBasis/active) 편집.
- charging/assessment 화면 = 인스턴스 필드(금액: baseChargePrincipal 외) 편집.
- charging/assessment의 항목명 드릴다운도 설정>항목과 같은 chargeCode 기반 정의·배분값 조회를 사용한다. 별도 assessment 전용 item-detail DTO를 만들지 않는다.
- 두 화면이 같은 charge 객체의 다른 필드를 담당. sync 불필요.
1. 데이터 모델
1-1. charge 필드 분류
| 필드 | 분류 | 변경 가능 주체 | 설명 |
|---|---|---|---|
chargeCode | 등록코드(§1-3) | 생성 시 자동, 이후 불변 | FK 안정 키. ci-N 시퀀스(데모). BE 실 구현 시 UUID 또는 도메인 코드. |
name | 이름(명) | 설정>항목(updateChargeDef) | 표시명. 중복 허용. |
nature | 정의 | 설정>항목(updateChargeDef) | BILLING_NATURE enum 값. 분개 라우팅 핵심(아래 §3). |
taxCategory | 정의 | 설정>항목(updateChargeDef) | 과세/면세/해당없음. VAT 계산 분기. |
taxInvoiceType | 정의 | 설정>항목(updateChargeDef) | 일반/계산서/없음. 세금계산서 발행 유형. |
chargeBasis | 정의 | 설정>항목(updateChargeDef) | AREA_EXCLUSIVE/AREA_CONTRACT/USAGE/FIXED_SHARE/VARIABLE_SHARE. 배분 행렬 기준. FE 프로토타입은 기존 KO 값도 정규화한다. |
active | 메타 | 설정>항목(toggleActive) | true = 부과 사슬 포함. false = 전 사슬 제외. |
baseChargePrincipal 외 | 인스턴스(금액) | charging/assessment | 발생측·부과측 원금·할증·할인·조정. excl. VAT. |
targetCount | 인스턴스 | charging | 배분 세대수. |
chargeStatus | 인스턴스 | charging(라이프사이클) | 진행/완료. |
1-2. 정의 필드 allowlist (DEF_FIELDS)
updateChargeDef(chargeCode, patch)는 아래 필드만 패치한다. 금액·chargeCode는 allowlist 외 → 불변 보장.
DEF_FIELDS = ['name', 'nature', 'taxCategory', 'taxInvoiceType', 'chargeBasis']BE가 정의 수정 API를 구현할 때도 이 allowlist를 서버 측에서 동일하게 적용해야 한다.
2. active 전파 불변식
2-1. activeCharges 정의
activeCharges = charges.filter(c => c.active !== false)active 필드가 없는 charge는 활성으로 취급(!== false). 기존 시드(active: true)는 영향 없음(회귀 0).
2-2. 부과 사슬 소비처
비활성 항목은 다음 소비처에서 자동 제외된다:
| 소비처 | 역할 | 비고 |
|---|---|---|
useAllocations.js | 세대별 배분 산정 | L21에서 activeCharges 소비 |
useInvoicingDetail.js | 청구 집계·이월 표시 | L17에서 activeCharges 소비 |
charging/assessment/blocks/DynamicTableOrg.vue | 부과 금액 편집 테이블 | activeCharges 소비 |
전파 경로: active=false → activeCharges에서 제외 → 배분 산정 제외 → 청구 집계 제외 → 분개 제외 → 고지서 제외.
2-4. 배분기준 계약
정본은 src/composables/chargeBasis.js, 계산 소비처는 src/composables/useAllocations.js다.
| API enum | FE 호환 값 | 원시수량 |
|---|---|---|
AREA_EXCLUSIVE | 전용 | 호별 전용면적 |
AREA_CONTRACT | 계약 | 호별 계약면적 |
USAGE | 사용량 | 해당 항목·호의 당월 검침 사용량 |
FIXED_SHARE | 고정지분 | 호별 고정지분 |
VARIABLE_SHARE | 가변지분 | 해당 차수의 호별 가변지분 |
공식은 모든 방식이 동일하다.
lineWeight = basisQuantity(chargeBasis) × occupancyDays / monthDays
lineAmount = chargePrincipal × lineWeight / totalWeight- 면적 기준은 전용면적 또는 계약면적만 허용한다. 분양면적 enum·필드는 두지 않는다.
- 계약면적은 allocation line의
basis.contractArea를 우선하고, 없으면 unit master의contractArea, 구데이터는 마지막으로basis.area를 사용한다. totalWeight === 0이면 전액을 마지막 라인에 몰지 않고unallocated = principal로 남긴다. 미배분이 있으면 부과 확정 hard-stop.
2-5. 항목별 호 배분값 저장 계약
호별 값의 저장 원천은 기준에 따라 분리한다.
| 기준 | 읽기/쓰기 원천 | 비고 |
|---|---|---|
| 전용면적 | unit.exclusiveArea | 프로퍼티 마스터 소유, 항목 화면은 읽기 전용 |
| 계약면적 | unit.contractArea | 프로퍼티 마스터 소유, 항목 화면은 읽기 전용 |
| 사용량 | (chargeCode, unitCode) 누적 지침 | UI의 당월 사용량은 currentMonth = prevMonth + usage로 저장 |
| 고정지분 | allocation line basis.fixedShare | 같은 chargeCode+unitCode의 모든 점유기간 라인에 동일 값 |
| 가변지분 | allocation line basis.variableShare | 차수 스냅샷 대상, 같은 호의 점유기간 라인에 동일 값 |
신규 항목 생성 시 현재 계약 원자(contractCode × unitCode × memberCode × 점유기간)를 항목의 allocation line으로 함께 생성한다. ensureChargeLines(chargeCode)는 멱등이며 이미 한 줄이라도 있으면 추가하지 않는다. 신규 고정지분·가변지분·사용량은 0으로 시작한다. 항목 삭제 시 해당 항목의 allocation line과 지침도 함께 삭제한다.
2-3. 시드 불변식
데모 시드(demoSeed.buildSeed().charges) = 전부 active: true. 기존 테스트(부과·배분·분개·고지서)가 active 전환 이전과 동일 결과. 시드 회귀 없음.
3. nature enum 제약 — 분개 정합
3-1. BILLING_NATURE
src/composables/billingNature.js export. 현재 허용 값:
| nature | 분개 성격 | 대변 계정 |
|---|---|---|
| 관리비 | 과세/면세 용역 매출 | 0412 용역매출 + VAT(과세만) |
| 수도료 | 면세 용역 매출 | 0412 용역매출 |
| 장기수선충당금 | 부채 적립(비매출) | 9112(공동주택) / 9113(집합건물) |
| 예비비적립금 | 부채 적립(비매출) | 9201 |
신규 항목의 nature가 BILLING_NATURE 외 값이면 useBillingJournal 분개 라우팅이 fallback 처리됨(분개 누락 또는 오류 계정). BE 실 구현 시 서버 측 enum 검증 필수.
3-2. nature 추가 절차
v1 범위 외. 신규 nature를 추가하려면: billingNature.js 등록 + useBillingJournal 분개 라우팅 확장 + 테스트 보완 후 BE 계정과목 매핑. 아무 화면에서나 자유 입력으로 nature를 추가하는 것은 분개 정합을 깰 수 있으므로 FE UI에서 enum select만 허용.
4. chargeCode 안정 키 정책 (코드체계 §1-3 등록코드)
| 속성 | 값 |
|---|---|
| 생성 | 자동(addCharge 시 시퀀스 부여). 데모: ci-N. 실 API: UUID 또는 도메인 규칙. |
| 변경 | 불가(DEF_FIELDS 외). 설정>항목에서도 비노출(우측 상단 subtitle-sm 읽기 전용). |
| 삭제 | removeCharge로 영구 삭제만 가능. 삭제 후 재사용 없음(시퀀스 continue). |
| FK 역할 | useAllocations·useInvoicingDetail·useBillingJournal 등이 chargeCode로 항목 참조. 변경 금지 이유. |
5. 주요 API (현재 FE 구현, BE 배선 계약)
5-1. 항목 추가
POST /service-charge/charges
Body: { name, nature, taxCategory, taxInvoiceType, chargeBasis }
Response: { chargeCode }active기본값: true.- 금액 필드 기본값: 0.
- chargeCode: 서버 자동 부여.
chargeBasis는 위 5개 enum만 허용.AREA_CONTRACT사용 시 대상 호에 계약면적이 있어야 한다.
5-2. 항목 삭제
DELETE /service-charge/charges/{chargeCode}- 물리 삭제. 연결된 과거 분개 보호 여부는 BE 정책(데모는 물리 삭제).
5-3. 정의 필드 수정
PATCH /service-charge/charges/{chargeCode}
Body: { name?, nature?, taxCategory?, taxInvoiceType?, chargeBasis? } // DEF_FIELDS 외 무시- 금액·chargeCode 패치 불가.
- nature는 서버에서 BILLING_NATURE enum 검증.
5-4. 활성 토글
PATCH /service-charge/charges/{chargeCode}/active
Body: { active: boolean }- 비활성화 시 현재 진행 중인 청구 회차의 부과 데이터가 있다면 BE 정책 필요(데모는 즉시 제외).
5-5. 금액 편집 (charging 소관)
PATCH /service-charge/charges/{chargeCode}/amounts
Body: { baseChargePrincipal?, chargeSurcharge?, chargeDiscount?, chargeAdjustment?, ... }- 정의 필드와 분리된 별도 엔드포인트 권장. DEF_FIELDS 수정과 혼용 금지.
5-6. 호별 배분값 조회·저장
GET /service-charge/charges/{chargeCode}/basis-values
Response: {
chargeCode,
chargeBasis,
rows: [{ unitCode, value }]
}
PUT /service-charge/charges/{chargeCode}/basis-values
Body: {
chargeBasis,
rows: [{ unitCode, value }]
}- 서버는 모든
value가 유한한 0 이상 수인지 검증한다. - 면적 기준 PUT은 허용하지 않고 프로퍼티 unit API로 유도한다.
- 지분은 동일 호의 모든 유효 점유기간 라인에 반영하되, 월중 점유일수는 배분 공식에서 별도로 곱한다.
- 사용량은 검침 원장에 저장하고 allocation line에 중복 원천을 만들지 않는다.
- 진행 차수 저장은 트랜잭션으로 처리하고, 확정 차수 값은 수정하지 않는다.
- 설정>항목과 부과산정 항목명 진입은 동일 엔드포인트를 소비한다. 진입 화면에 따라 복제 저장소나 별도 응답 스키마를 두지 않는다.
6. 엣지케이스 / 불변식
- 비활성 항목 + 기존 미수: 비활성화해도 이전 기간에 발생한 미수·분개는 그대로 유지. 부과 사슬에서 "신규 산정 대상에서 제외"될 뿐.
- 신규 항목 금액 0 + activeCharges 포함: 추가 즉시 activeCharges에 들어가지만 금액 0이므로 배분·분개·합계에 0원 라인으로 합산. 실질 영향 없음.
- chargeCode 충돌: FE 데모는 시퀀스 while loop으로 방지. BE는 unique constraint 필수.
- nature가 BILLING_NATURE 외: 현재 FE에서 enum select로 차단. BE도 서버 검증 필수.
- 단일 기간 데모: 현재
charges는 기간 독립 단일 배열. 다기간(월별 정의 변경 이력)은 v2 설계 필요. - 배분기준 변경: 기준 변경 즉시 진행 중 차수의 배분 결과가 달라진다. 이미 확정된 차수는 effective-dated 정의 또는 스냅샷으로 보존해야 한다(BE 도입 시 필수).
- 기준값 0: 사용량·지분·면적 합계가 0이면 미배분으로 남겨야 하며 자동 균등 배분으로 폴백하지 않는다.
- 월중 입주자 변경: 한 호에 allocation line이 여러 개여도 사용자가 입력하는 값은 호당 하나다. 같은 호의 각 라인에 같은 기준값을 적용한 뒤
occupancyDays / monthDays로 분할한다.