다크모드
FE 메인테이너 — 선수금·가수금 콘솔
독자: FE 메인테이너/AI. "이 콘솔을 이어 개발하려면 무엇을 알아야 하나"를 정의한다. BE 계약:
docs/handoff/backend/advance-suspense-gl-vouchers.md(전표 3종·불변식). 정본:docs/PROCESS-RECEIPTS-CANON-2026-06-19.md§3-F/§3-G.
1. 라우트 / 컴포넌트 트리
- 라우트:
/service-charge/actual/console/advance-receipt(src/router/buildLineRoutes.js의'advance-receipt'키). advance-receipt/IndexView.vue(src/views/billing/_core/console/advance-receipt/) →HeaderOrg.vue+MainOrg.vue(src/components/billing/_core/console/advance-receipt/).
IndexView.vue
├─ HeaderOrg.vue
└─ MainOrg.vue
├─ section: 선수금 현황(advanceRows 테이블 + 계약별 [환급] 버튼)
├─ section: 가수금(미식별)(suspense.entries 테이블 + 행별 [식별·재분류] 버튼)
└─ dialog#dialog-reclassify-suspense ← 코-로케이트(같은 파일 내)두 섹션(선수금·가수금)과 재분류 다이얼로그가 모두 MainOrg.vue 한 파일에 있다(카드 규율 대상 밖 — 시트가 아니라 콘솔 페이지 유형 B 상당).
2. 소비 컴포저블
| 컴포저블 | 사용처 |
|---|---|
useReceipts | suspenseBalance()(가수금 목록), advanceBalanceByContract()(선수금 잔액 목록), reclassifySuspense(receiptId, contractCode, at)(재분류 확정), refundAdvance(contractCode, amount, at)(환급 확정) |
useAllocations | contracts(계약 셀렉트 옵션 소스 — { contractCode, name }[] reactive ref) |
nameOf(code)는 로컬 헬퍼(contracts.value.find(...).name ?? code) — 계약코드→계약명 표시용.
3. 데이터 흐름
가수금 식별·재분류
가수금 행 [식별·재분류] 클릭
→ openReclassify(receiptId)
selectedReceiptId.value = receiptId
selectedContractCode.value = contracts.value[0]?.contractCode ?? '' ← 다이얼로그 오픈 시 초기값(첫 계약)만, 확정은 아님
#dialog-reclassify-suspense.showModal()
→ 사용자가 Select에서 계약 선택 (selectedContractCode v-model)
→ [확인] 클릭 → onConfirmReclassify()
reclassifySuspense(selectedReceiptId, selectedContractCode, '2025-12-28') ← 데모 식별일 고정
dialog.close()
selectedReceiptId.value = null
→ useReceipts 싱글톤 상태 변이 → advanceBalanceByContract()/suspenseBalance() computed 재계산 → 목록 반응형 갱신contracts[0] 하드코딩 제거가 이 작업의 핵심: 과거 reclassifySuspense(id, contracts[0].contractCode, ...)처럼 항상 첫 계약으로 재분류하던 것을, selectedContractCode(Select v-model) + 다이얼로그 확인 절차로 바꿔 사용자가 실제 매칭 계약을 고른다. contracts.value[0]은 이제 다이얼로그 오픈 시 Select의 초기 선택값(편의상 첫 항목) 역할만 하고, 최종 반영값은 항상 사용자가 확정한 selectedContractCode다.
환급
선수금 행 [환급] 클릭 → onRefund(contractCode, balance) → refundAdvance(contractCode, balance, '2026-06-19') ← 데모 환급일 고정, 확인 다이얼로그 없음(즉시 실행)환급은 재분류와 달리 다이얼로그 없이 즉시 실행(잔액 전액 환급, 계약 이미 확정된 행 컨텍스트라 추가 입력 불필요).
4. DS 패턴
- 다이얼로그:
dialog dialog-md dialog-inset-edged dialog-divide-y dialog-filled modal+dialog-header/dialog-body/dialog-footer+command="close" commandfor="dialog-reclassify-suspense"(닫기 버튼, popover 없이<dialog>네이티브 API 조합). - 계약 셀렉트:
@/registry/vue/select(Select/SelectTrigger/SelectValue/SelectContent/SelectItem) +v-model="selectedContractCode". - 폼:
field field-md+field-label— phantom 클래스 금지(CLAUDE.md 정본). - 테이블:
table table-sm table-static w-full— 두 섹션 모두 정적 목록(인라인 편집 없음), 행 액션은button button-xs(환급=button-neutral-minimal, 식별·재분류=button-primary-minimal). - i18n:
billing.advanceConsole.*(src/i18n/locales/{ko,en}.json) —advanceTitle·suspenseTitle·contract·balance·payer·receivedAt·amount·refund·reclassify·reclassifyDialogTitle·reclassifyDialogDesc·selectContract·cancel·confirm·emptyAdvance·emptySuspense·count.
5. 확장 포인트
- 식별일 입력: 현재 재분류 확정 시 식별일이 데모 상수
'2025-12-28'로 고정(onConfirmReclassify내 리터럴). 실서비스는 날짜 입력(@/registry/vue/date-picker, 수납 상세 시트paymentDate패턴 참고)으로 대체 필요 — GL 전표(E-reclass) 일자가 이 값을 그대로 쓰므로(r.identifiedAt || r.receivedAt) 정확한 식별일 입력이 회계 정합에 직결. - 환급일 입력: 현재
onRefund가 데모 상수'2026-06-19'로 고정. 실서비스는 환급 실행일 입력(또는 오늘 날짜 자동)으로 대체. - 다계약 분할 재분류: 가수금 1건을 여러 계약에 나눠 재분류하는 UI는 미구현(BE도 미지원 —
docs/handoff/backend/advance-suspense-gl-vouchers.md§5 엣지케이스 참조). 현재는 Select 단일 선택 → 전액 1개 계약 귀속. - 환급 확인 다이얼로그: 현재 즉시 실행. 금액이 크거나 오조작 리스크가 있으면 재분류처럼 확인 다이얼로그 추가 검토.
6. 테스트 위치
전용 컴포넌트 테스트는 아직 없음(신규 시 src/components/billing/_core/console/advance-receipt/__tests__/MainOrg.spec.js 신설 검토). GL 전표 파생 로직은 src/composables/__tests__/useBillingJournal.spec.js(E5·E-ref·가수금 재분류 그룹), 원장/재분류 로직은 src/composables/__tests__/useReceipts.spec.js.