Skip to content

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. 소비 컴포저블

컴포저블사용처
useReceiptssuspenseBalance()(가수금 목록), advanceBalanceByContract()(선수금 잔액 목록), reclassifySuspense(receiptId, contractCode, at)(재분류 확정), refundAdvance(contractCode, amount, at)(환급 확정)
useAllocationscontracts(계약 셀렉트 옵션 소스 — { 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.