다크모드
FE 메인테이너 — 설정>항목 (부과항목 마스터 편집기)
독자: FE 메인테이너/AI. "이 페이지를 이어 개발하려면 무엇을 알아야 하나". 대상: 관리비 설정>항목 페이지 —
useCharges캐논의 정의 편집기. 부과 사슬 activeCharges 전파. 구현 상태: ✅구현 (2026-06-29, worktree feat/invoicing-detail-axis-aggregate).
1. 라우트
| path | view | 상태 |
|---|---|---|
/service-charge/setting/item/general | src/views/service-charge/setting/item/general/IndexView.vue | ✅구현 |
설정>항목은 실제·예정 라인이 공유하는 서비스 레벨 설정이다. 라우트 팩토리 = src/composables/billingLines.js + src/router/buildLineRoutes.js.
2. 컴포넌트 트리
IndexView.vue
├── HeaderOrg.vue
└── MainOrg.vue
├── SearchToolBarOrg.vue
├── CriteriaToolBarOrg.vue
├── DataToolBarOrg.vue (추가 진입·선택삭제)
├── DynamicTableOrg.vue (부과항목 리스트)
├── PaginationToolBarOrg.vue
│
└── (overlays)
├── SheetCuItemArt.vue (부과항목 추가 시트 — addCharge)
└── SheetReadItemArt.vue (부과항목 상세 read↔edit — updateChargeDef)
└── SheetManageChargeBasisValuesArt.vue (호별 배분값 조회·편집)
└── ChargeBasisFieldOrg.vue (배분방식 조건부 폼·계산식 요약)src/components/service-charge/setting/item/general/ 하위에 전부 위치.
3. 소비 컴포저블
3-1. useCharges — 부과항목 캐논 단일 소스
src/composables/useCharges.js. 모듈 스코프 reactive 싱글톤. 설정>항목과 부과 사슬 전체가 같은 인스턴스를 공유.
| 노출 | 종류 | 역할 |
|---|---|---|
charges | ref(Charge[]) | 전체 항목 배열(활성+비활성). demoSeed.buildSeed().charges 초기화. |
activeCharges | computed | charges에서 active !== false인 항목만 필터. 부과 사슬 소비처가 이것을 읽는다. |
addCharge(def) | fn → chargeCode | 신규 정의 1행 추가(금액 0·active true·chargeCode 생성). |
removeCharge(chargeCode) | fn | 항목 제거(영구). charges에서 splice. |
updateChargeDef(chargeCode, patch) | fn | DEF_FIELDS(name/nature/taxCategory/taxInvoiceType/chargeBasis)만 패치. 금액·chargeCode 불변. |
toggleActive(chargeCode, on) | fn | charge.active = on. activeCharges에 즉시 반영. |
amountsOf(charge) | fn | 금액 파생(부과 사슬 전용 — 설정>항목 미사용). |
3-2. useChargeItemUi — UI 선택 상태 (비즈니스 로직 없음)
src/components/service-charge/setting/item/general/useChargeItemUi.js. 모듈 싱글톤.
| 노출 | 역할 |
|---|---|
selectedCode | ref — 상세 시트에 열릴 항목 chargeCode |
checkedCodes | ref — 체크박스 다중 선택(삭제 대상) |
selectItem(code) | 행 클릭 시 selectedCode 세팅 |
toggleCheck(code, on) | 체크박스 on/off |
clearChecked() | 삭제 후 선택 초기화 |
DynamicTableOrg와 SheetReadItemArt·DataToolBarOrg가 공유한다.
useChargeItemUi는 설정 페이지에 한정되지 않는다. 부과산정 표에서도 항목명 클릭 시 selectItem(chargeCode)를 호출한 뒤 같은 SheetReadItemArt를 연다. 라우트가 달라도 선택 키와 상세 컴포넌트를 복제하지 않는다.
3-3. useAllocationBasisValues — 항목별 호 배분값 draft/commit
src/composables/useAllocationBasisValues.js. 항목 정의, allocation line, 검침 지침을 연결하는 모듈 싱글톤이다.
| 노출 | 역할 |
|---|---|
ensureChargeLines(chargeCode) | 현재 계약 원자를 신규 항목의 배분 라인으로 멱등 생성. 생성 시트가 addCharge 직후 호출. |
openBasisValues(chargeCode) | 기준을 읽고 호별 draft를 만든다. 한 호에 점유기간 라인이 여러 개여도 UI 행은 하나. |
draftRows / dirty / valuesValid | 호별 입력 draft와 저장 게이트. 유한한 0 이상 값만 허용. |
commitBasisValues() | 사용량은 useMeterReadings, 지분은 동일 호의 모든 allocation line에 반영. |
summaryFor(chargeCode) | 상세 시트의 대상 호 수·기준값 합계·편집 가능 여부. |
removeChargeLines(chargeCode) | 항목 삭제 시 allocation line과 지침 동반 정리. |
4. 부과항목 데이터 구조
useCharges.charges 각 항목이 가지는 필드:
| 필드 | 분류 | 설명 |
|---|---|---|
chargeCode | 등록코드(코드체계 §1-3) | 생성 시 자동 부여(ci-N 시퀀스), 이후 불변. FK 안정 키. |
name | 이름(명) | 표시명. 수정 가능. |
nature | 정의 | BILLING_NATURE 키 중 하나(관리비/수도료/장기수선충당금/예비비적립금). 분개 라우팅 핵심 — enum 외 값 금지. |
taxCategory | 정의 | 과세/면세/해당없음. |
taxInvoiceType | 정의 | 일반/계산서/없음. |
chargeBasis | 정의 | 전용/계약/사용량/고정지분/가변지분. chargeBasis.js에서 라벨·방식·계산식을 파생. |
active | 메타 | true/false. false이면 activeCharges 제외 → 부과 사슬 전체 제외. |
baseChargePrincipal 외 금액 필드 | 인스턴스 | 부과(charging) 화면에서 v-model 편집. 설정>항목에서 표시·편집 안 함. |
single-canon: 정의 필드와 인스턴스(금액) 필드가 같은 charge 객체 안에 공존. 설정>항목 = 정의 편집 담당, charging/assessment = 금액 편집 담당.
5. 리스트 IA — DynamicTableOrg.vue
컬럼(순서): 체크박스(다중선택) · 활성(토글) · 항목명(주식별자) · 성격 · 세금구분 · 세금계산서유형 · 배분기준.
- 항목명 클릭 = 상세 시트(
sheet-read-charge-item-art) 진입.selectItem(chargeCode)+command="show-modal". 클릭 affordance:cursor-pointer font-medium hover:underline. - 활성 체크박스 =
toggleActive(chargeCode, on)즉시 호출 → activeCharges 반영. - 체크박스 =
toggleCheck→ DataToolBar 삭제 대상. - 금액 컬럼 없음 — charging 소관임을 상기.
6. 오버레이 — 추가 시트 SheetCuItemArt.vue
id="sheet-create-charge-item-art". 유형 B 단일 폼 시트(컬렉션 없음, CLAUDE.md §1-B).
- 필드: 항목명(text input) · 성격(Select, BILLING_NATURE 키만) · 세금구분(Select) · 세금계산서유형(Select) ·
ChargeBasisFieldOrg. - 폼은
배분 방식을 먼저 고른 뒤 필요한 세부 필드만 보인다: 면적 비례→전용/계약, 지분 비례→고정/가변, 사용량 비례→추가 선택 없음. - 선택 아래
배분 계산카드가chargeBasisFormula·chargeBasisDescription을 표시한다. 설명문을 폼 곳곳에 반복하지 않고 현재 선택의 계산 계약만 상시 노출한다. - 모바일은
grid-cols-1, 데스크톱은md:grid-cols-2; Select trigger 폭은 항상w-full. - 성격 select는
BILLING_NATURE키만 허용 — 자유입력 없음. 목적: 분개(journal-by-nature) 라우팅이 BILLING_NATURE를 키로 쓰므로 미정의 값이 들어오면 분개 fallback 발생. - 주석:
"금액은 부과(청구 산정) 화면에서 입력합니다."안내. submit(): 항목명 필수 검증 후addCharge()→ensureChargeLines(chargeCode)→ form reset → 시트 close. 지분·사용량 초깃값은 0이며 면적은 unit master를 읽는다.
7. 오버레이 — 상세 시트 SheetReadItemArt.vue
id="sheet-read-charge-item-art". 항목 속성과 연결 컬렉션(세대별 배분 값)을 함께 갖는 허브 시트. 폭 sheet-width-2xl.
selectedCharge=useCharges().charges.find(c => c.chargeCode === selectedCode)computed.zone-top: 항목명(title-sm) + 세금구분 badge + chargeCode(subtitle-sm, 우측).- READ 모드: disclosure + table-static(항목 정의 5행). 하단 "금액은 부과 화면에서 관리합니다." 안내.
- EDIT 모드: 단일 card 안에서 항목 정의와 배분 설정을 평면 섹션으로 나눈다.
ChargeBasisFieldOrg를 생성 시트와 공유해 선택지·조건·계산식 drift를 막는다.CLAUDE.md§0-6 EDIT 모드 = 전부 펼침, 접기 없음. - READ 모드: 배분방식 라벨(
chargeBasisLabel)과 동일한 계산식 요약을 표시한다. - 연결 컬렉션:
세대별 배분 값disclosure가 대상 호 수와 합계를 보여준다. 면적이면[값 보기], 사용량·지분이면[값 관리]로SheetManageChargeBasisValuesArt를 연다. 항목 정의 edit 모드에서는 속성 커밋에 집중하도록 이 컬렉션을 표시하지 않는다. - sheet-footer 토글: READ =
[편집](button-neutral-subtle), EDIT =[취소]+[저장](button-primary-bold). saveEdit():updateChargeDef(selectedCode, { ...draft })→isEditing = false. 금액·chargeCode는 DEF_FIELDS 외라 패치 불가.- 저장 가드(FB-18, 2026-07-10):
editValid = computed(() => !!draft.value.name?.trim()).saveEdit()는!editValid.value면 조기 return, [저장] 버튼도:disabled="!editValid". 공백 이름 저장을 막는다 — 항목명이 목록 주식별자이자 검침 CSV(importCsv)의 이름 해석 키라 비면 그 항목을 다시 찾을 수 없게 되는 문제(감사 FB-18)를 차단.SheetCuItemArt.vue(생성 시트)의 기존 필수 검증과 대칭. 커밋5f030f138.
7-1. 호별 배분값 관리 시트
SheetManageChargeBasisValuesArt.vue, id="sheet-manage-charge-basis-values-art", 집중 폭 sheet-width-3xl.
- 헤더: 항목명·부과기준·관리코드.
- 계산 카드:
chargeBasisFormula와chargeBasisDescription을 항목 상세와 동일하게 사용. - 값 표: 호/기준값 2열,
max-h-96 overflow-y-auto+sticky-thead; 시트 안 페이지네이션 없음. - 면적:
unit.exclusiveArea/unit.contractArea를 읽기 전용 표시하고 프로퍼티 설정이 수정 원천임을 안내. - 사용량·지분:
input input-bordered input-sm,inputmode="decimal", 0 이상 숫자 입력. 저장은dirty && valuesValid일 때만 활성. - 모바일: 열이 두 개뿐인 고정 정보 구조, 값 입력 폭
w-32; 시트 자체가 모바일 뷰포트에서 전폭으로 동작하고 내부 표만 세로 스크롤한다. - 취소/close는
resetDraft, 저장은commitBasisValues후 close.
7-2. 부과산정 표에서 상세 재사용
src/components/service-charge/actual/console/charging/assessment/에서도 항목 상세 두 컴포넌트를 그대로 마운트한다.
MainOrg.vue
├── DynamicTableOrg.vue
├── SheetReadItemArt.vue
└── SheetManageChargeBasisValuesArt.vueDynamicTableOrg.vue의 항목명 버튼:selectItem(charge.chargeCode)+command="show-modal" commandfor="sheet-read-charge-item-art".- 클릭 affordance는 설정 목록과 동일한
font-medium text-left cursor-pointer hover:underline. - 과거
AccountDetailArt.vue는 선택 항목과 연결되지 않은 정적 mock이라 이 진입점에서 마운트하지 않는다. - 오른쪽 액션 열은
account-charge-detail(차수의 부과 금액 상세)만 유지한다. 항목명과 같은 상세를 여는 중복 아이콘 버튼은 두지 않는다. - 결과적으로 항목명 = 항목 정의·배분값 상세, 계산기 아이콘 = 당기 부과 금액 상세로 역할이 분리된다.
7-3. 벌크 삭제 확인 다이얼로그 (FB-24/30, 2026-07-10)
DataToolBarOrg.vue의 [삭제] 버튼은 더 이상 checkedCodes를 즉시 removeCharge로 넘기지 않는다. 대신 command="show-modal" commandfor="dialog-delete-charges-art"로 DialogDeleteChargesArt.vue(신규, overlays/)를 연다 — 원클릭 무확인 삭제(FB-24)를 확인 다이얼로그 경유로 전환.
DialogDeleteChargesArt.vue:useChargeItemUi().checkedCodes대상 건수 안내 →[삭제]클릭 시 항목별removeChargeLines(code)+removeCharge(code)+clearChecked(). 배분 라인·검침 지침 고아 데이터를 남기지 않는다.- 마운트:
MainOrg.vue(SheetCuItemArt/SheetReadItemArt를 렌더하는 동일 부모)에<DialogDeleteChargesArt />추가. DataToolBarOrg.vue는 이제checkedCodes.length === 0기반disabled계산만 소비 —removeChargeimport·직접 호출 없음.- 같은 커밋(
5f030f138)이 선수금 환급(AdvanceBalanceOrg.vue,billing/_core/console/collecting/detail/blocks/)에도 동형 확인 다이얼로그(dialog-confirm-refund-advance-art)를 적용(FB-30) — 두 삭제/환급류 액션을 같은 정책(확인 다이얼로그 경유)으로 통일.
7-4. 페이지네이션 "N개 선택됨" 배지 (FB-27, 2026-07-10)
PaginationToolBarOrg.vue(설정>항목)가 useChargeItemUi().checkedCodes를 import해 배지를 v-if="checkedCodes.length" + {{ checkedCodes.length }}개 선택됨으로 실바인딩(이전엔 항상 "10개 선택됨" 하드코딩). 초기 데이터(setting/initial-data/general) 쪽의 동형 배지는 선택 모델 자체가 없음(체크박스가 v-model 미연결 장식용)을 확인 후 배지+wrapper를 통째로 제거했다 — 없는 기능을 있는 것처럼 보이는 허위 표시 제거가 목적이라, 실 선택 모델이 없는 화면은 "0개"로 얼버무리지 않고 배지 자체를 없앴다.
8. activeCharges 부과 사슬 전파
toggleActive(chargeCode, false) 또는 removeCharge(chargeCode) 시 activeCharges에서 즉시 제외. 다음 소비처가 reactively 재계산:
| 소비처 | 파일 | 라인 |
|---|---|---|
| 배분(할당) 산정 | src/composables/useAllocations.js | L21 activeCharges as allCharges |
| 청구 집계 상세 | src/composables/useInvoicingDetail.js | L17 activeCharges as charges (L160 루프 자동 반영) |
| 부과 산정 테이블 | src/components/service-charge/actual/console/charging/assessment/blocks/DynamicTableOrg.vue | activeCharges as charges |
| 배분 피벗 — 유닛 | src/components/service-charge/actual/console/charging/allocation-unit/blocks/DynamicTableOrg.vue | activeCharges as charges |
| 배분 피벗 — 계약 | src/components/service-charge/actual/console/charging/allocation-contract/blocks/DynamicTableOrg.vue | activeCharges as charges |
| 배분 피벗 — 멤버 | src/components/service-charge/actual/console/charging/allocation-member/blocks/DynamicTableOrg.vue | activeCharges as charges |
전파 방향: 설정>항목 활성 토글 → activeCharges → 배분 산정 → 배분 피벗 3화면(유닛/계약/멤버) → 청구 집계 → 수납 → 분개 → 고지서.
시드는 전부 active: true이므로 활성 전환 이전 동작은 회귀 없음(기존 vitest 전부 green).
9. chargeCode 코드체계 (CLAUDE.md §1-3 등록코드)
- 생성:
addCharge()내nextChargeCode()→ci-{seq}형태(기존과 충돌 방지 while loop). - 수정: 불가(DEF_FIELDS에 없음,
updateChargeDef패치 대상 아님). - 삭제:
removeCharge()로 영구 제거. 삭제 후 시퀀스 재사용 안전(충돌 방지 loop). - 고객 노출: 상세 시트 zone-top 우측
subtitle-sm으로 최소 노출. - 목록 비노출(정책 준수).
10. nature enum 제약
BILLING_NATURE 키 = src/composables/billingNature.js export. 현재 값: 관리비 · 수도료 · 장기수선충당금 · 예비비적립금.
신규 항목 추가 시 반드시 이 중 하나 선택. 선택 UI(SheetCuItemArt, SheetReadItemArt EDIT)는 Object.keys(BILLING_NATURE)를 Select 옵션으로 렌더링 — 임의 문자열 입력 불가.
신규 nature 종류를 추가하려면 billingNature.js에 먼저 등록 + useBillingJournal(분개 라우팅) 확장이 선행돼야 한다(범위 외).
11. 확장 포인트
- 다기간 정의/인스턴스 분리(미래): 현재 charge 객체가 정의(name/nature 등)와 인스턴스(금액)를 겸한다. 기간별로 다른 금액이 필요해지면
charges= 정의 배열,chargeInstances= 기간×항목 금액 배열로 분리.demoSeed.buildSeed()팩토리가 그 패턴을 미리 준비(defs배열에서 instantiate)하므로 이 분리는 시드 팩토리 교체로 시작. - 실 API 백킹: 현재 모듈 싱글톤(데모). 실제 API 연동 시
chargesref를 스토어/API 반응형으로 교체.addCharge/removeCharge/updateChargeDef/toggleActive계약은 유지. - 항목 그룹:
setting/item/group은 데이터모델(그룹 스토어) 자체가 없어 여전히 별도 트랙이지만, 2026-07-10(펀치리스트 I-1)에 허위 표시는 제거했다 —DynamicTableOrg.vue의 인라인 mock 행(accounts = ref([...]))을ref([])+ 빈상태 안내 행("그룹 관리가 열리면…")으로 교체하고,DataToolBarOrg.vue의 생성/삭제/다운로드 버튼을disabled title="준비 중이에요"로 정직 비활성화했다(974788f2c). 실제 그룹 CRUD 구현은 여전히 미착수. - leysys-design SSOT: card/field/sheet/table/badge 클래스는 전부 SSOT 카피본. DS 결함 즉석 패치 금지.
- 배분기준 확장: 신규 기준은
chargeBasis.jsenum·normalize·method·label·formula·description을 먼저 추가하고useAllocations.basisQuantity를 연결한 뒤 UI에 노출한다. UI 단독 옵션 추가 금지.
12. 배분방식 정본 (2026-07-13)
src/composables/chargeBasis.js가 저장 값 호환과 표시 계약을 소유한다.
| 캐논 값 | 방식 | 조건부 세부선택 | 계산 데이터 |
|---|---|---|---|
전용 / AREA_EXCLUSIVE | 면적 비례 | 전용면적 | line.basis.area |
계약 / AREA_CONTRACT | 면적 비례 | 계약면적 | line.basis.contractArea → unit.contractArea → 구데이터 area fallback |
사용량 / USAGE | 사용량 비례 | 없음 | useMeterReadings().usageOf() |
고정지분 / FIXED_SHARE | 지분 비례 | 고정지분 | line.basis.fixedShare |
가변지분 / VARIABLE_SHARE | 지분 비례 | 가변지분 | line.basis.variableShare |
분양면적은 저장 값·UI 선택지·공식에 존재하지 않는다. 면적 방식은 전용/계약 두 값만 허용한다.