Skip to content

매입 P1(매입확정)/P2(지급충당) — FE 메인테이너 참고

⚠️ 2026-07-07 갱신: payment 콘솔은 P3/P6 슬라이스에서 확장됐다(선지급 현황·미식별출금 큐/식별 다이얼로그 신규, DialogOffsetArt의 초과 차단 UI 제거). 최신 구조는 docs/handoff/frontend/purchasing-p3-p6.md 참고 — 이 문서는 P1/P2 첫 슬라이스 as-built 기록으로 보존한다(§9 확장 포인트 중 "선지급" 항목은 P3/P6로 해소).

정본 설계: docs/superpowers/specs/2026-07-07-purchasing-p1-p2-design.md. BE 계약은 docs/handoff/backend/purchasing-p1-p2.md 참고. 이 문서는 "이 화면을 이어 개발하려면 무엇을 알아야 하나"에 집중한다.

1. 라우트

src/router/buildLineRoutes.jspurchasing 라인이 소유(FRAMEWORK_LINES에 포함):

경로
/purchasing (landing)src/views/purchasing/LandingView.vue
/purchasing/console/transactionsrc/views/purchasing/console/transaction/IndexView.vue
/purchasing/console/paymentsrc/views/purchasing/console/payment/IndexView.vue
/purchasing/console/vendor-ledgersrc/views/purchasing/console/vendor-ledger/IndexView.vue
/purchasing/console/tax-invoice기존 세금계산서 core(이번 슬라이스 범위 밖 — direction=received 메타만 부여)

IndexView.vue는 얇은 셸이고 실제 화면 로직은 src/components/purchasing/console/{transaction,payment,vendor-ledger}/에 있다. 라우트 회귀 테스트: src/router/__tests__/buildLineRoutes.spec.js.

2. 컴포넌트 트리

src/components/purchasing/console/
├── transaction/
│   ├── HeaderOrg.vue
│   ├── MainOrg.vue                              — 목록 카드 + [매입 등록]/[세금계산서 가져오기(disabled)] + 회계 전기 footer
│   ├── blocks/DynamicTableOrg.vue                — usePurchases 실배선 목록(행 클릭→상세 시트)
│   └── overlays/
│       ├── DialogCreatePurchaseArt.vue           — P1 등록 폼(거래처·발생일·항목·계정·세금유형·증빙유형·공급가액)
│       └── SheetReadPurchaseTransactionArt.vue   — 유형 A 상세 시트(매입내역·금액/미지급현황·분개미리보기)
├── payment/
│   ├── HeaderOrg.vue
│   ├── MainOrg.vue                               — 목록 카드 + [지급 등록] + 회계 전기 footer
│   ├── blocks/DynamicTableOrg.vue                — usePayments 실배선 목록(행별 토글 상세 — 충당내역+분개미리보기)
│   └── overlays/
│       ├── DialogOffsetArt.vue                   — P2 1단계: 거래처→열린채무 FIFO 미리보기+초과/음수 가드→draft emit
│       └── DialogPaymentConfirmArt.vue           — P2 2단계: draft prop 수신→recordPayment 커밋
└── vendor-ledger/
    ├── HeaderOrg.vue
    ├── MainOrg.vue                                — 거래처 Select + 요약 스트립(카드 규율 §8-3 비접힘 합계 행) + 테이블
    └── blocks/DynamicTableOrg.vue                 — useVendorLedger 시계열 테이블(vendor-code prop)

각 서브디렉터리 __tests__/에 대응 spec 있음(§9).

3. 소비 컴포저블

3-1. usePurchases (src/composables/usePurchases.js) — 매입 원장 진실원

모듈 reactive 싱글톤(buildSeed deep-clone 시드 — 회귀0 패턴, useForwarding/useAllocations 선례 동형). 노출 API:

js
const {
  purchases,
  recordPurchase,
  payablesByVendor,
  payableBalanceByVendor,
  pay,
  peekPaid,
  unpay,
  resetPurchases,
} = usePurchases();
  • purchases: reactive 배열, 화면에서 직접 순회(테이블 sort는 컴포넌트가 담당).
  • recordPurchase({vendorCode, purchaseDate, category, accountCode, supplyAmount, taxType, documentType, memo}): DialogCreatePurchaseArt가 호출. vatAmount·payable·payableAccountCode는 이 함수가 계산해 반환값(purchase 객체)에 포함.
  • payablesByVendor() / payableBalanceByVendor(): 열린 채무 조회(화면은 이 파생 함수만 사용, paidByPurchase 내부 오버레이를 직접 참조하지 않는다).
  • pay/peekPaid/unpay: 콘솔 컴포넌트는 이 3개를 직접 호출하지 않는다usePayments가 소비. FE가 직접 호출할 일은 없다(테스트 제외).

3-2. usePayments (src/composables/usePayments.js) — 지급 원자 진실원

js
const { payments, recordPayment, cancelPayment, paidByVendor, resetPayments } = usePayments();
  • recordPayment({vendorCode, amount, paymentDate, method, memo}){ ok, id?, applied?, appliedBreakdown?, reason? }. ok:false를 무음으로 흘리지 않는다DialogPaymentConfirmArt.confirm()result.ok를 체크해 error.value에 표시(1단계 가드를 우회한 방어적 케이스만 도달하는 게 정상).
  • cancelPayment(id): 컴포저블엔 있으나 화면 버튼 미배선(§10 확장 포인트).

3-3. usePurchaseJournal (src/composables/usePurchaseJournal.js) — GL 전표 뷰

js
const { vouchersPurchase, vouchersPayment, purchaseVouchers } = usePurchaseJournal();

순수 파생(원자 소유 없음) — 매번 purchases/payments 최신 상태로 재계산. 콘솔은 이 함수들을 직접 호출해 미전기 카운트·전기 버튼·분개 미리보기 테이블을 만든다(재계산 캐싱 없음 — E-시리즈 useBillingJournal 선례 동형).

3-4. useVendorLedger (src/composables/useVendorLedger.js) — 거래처 원장 read-only VM

js
const { vendorLedger, ledgerFor, vendorCodes } = useVendorLedger();

새 원자 없음 — usePurchases.purchases + usePayments.payments를 거래처별 시계열 병합. vendor-ledger/MainOrg.vuevendorLedger()로 Select 옵션을, ledgerFor(vendorCode)로 선택된 거래처의 entries/totalPayable/totalApplied/balance를 가져온다.

4. 데이터 흐름 요약

DialogCreatePurchaseArt.submit()
  → usePurchases().recordPurchase(...)
  → purchases[] 갱신(reactive)
  → transaction/DynamicTableOrg.vue가 자동 재렌더(rows computed)
  → transaction/MainOrg.vue의 unpostedCount(usePurchaseJournal().vouchersPurchase() 필터)도 자동 갱신

DialogOffsetArt(1단계, FIFO 미리보기·가드) → emit('draft', {...})
  → payment/MainOrg.vue: paymentDraft.value = draft
  → DialogPaymentConfirmArt(2단계, :draft="paymentDraft").confirm()
  → usePayments().recordPayment(draft)
  → { ok:true } → emit('registered') → paymentDraft = null
  → payments[] 갱신 → payment/DynamicTableOrg.vue·vendor-ledger 자동 재렌더

두 다이얼로그 간 인계는 prop(:draft)+emit — 별도 store/composable 상태 없음. 등록 폼 검증(가드)은 1단계에서 전부 끝내고, 2단계는 커밋 전용(방어적 재검증만).

5. 회계 게이팅

useModuleSubscription().accountingEnabled — E-시리즈 선례 그대로:

  • transaction/payment MainOrg: v-if="accountingEnabled"로 "회계 전기" 버튼(footer) 숨김. unpostedCount/postAllPurchases/postAllPayments는 게이팅과 무관하게 항상 계산되지만 버튼만 숨긴다.
  • DynamicTableOrg(둘 다): "전기" 컬럼 자체를 v-if="accountingEnabled"로 숨김(colspan도 게이팅 반영 — :colspan="accountingEnabled ? N : N-1").
  • SheetReadPurchaseTransactionArt / payment 행 확장: 분개 미리보기 <details> 섹션을 v-if="accountingEnabled"로 숨김.
  • vendor-ledger: 게이팅 없음(read-only 조망 — brief에서 "회계 게이팅 불요" 명시).

전기 액션 자체은 useGeneralLedger().post(voucherId)를 그대로 재사용(신규 상태머신 없음) — isPosted(id)도 동일 소스.

6. DS 패턴

  • 콘솔 카드 구조: transaction/payment는 단일 card card-md + card-body(테이블) + card-footer(전기 버튼 + more_vert) — 유형 A/B 어느 쪽도 아닌 콘솔 전용 패턴(기존 sales/lease 콘솔 선례 동형).
  • vendor-ledger 요약 스트립: CLAUDE.md 카드 규율 §8-3("핵심 요약 수치는 카드 밖 — 비접힘 합계 스트립") 그대로 — bg-neutral-minimal 표면 위 card card-sm card-inset-edged 비접힘 행, 금액은 title-lg(미지급 잔액만) / 나머지 title-sm.
  • 행 클릭 상세 진입: transaction 테이블의 거래처 셀 = font-medium cursor-pointer hover:underline + table-hover(CLAUDE.md §0-10 IA 컨벤션).
  • 상세 시트: SheetReadPurchaseTransactionArt는 유형 A(다중 카드, bg-neutral-subtle + disclosure 카드 3개 — 매입내역/금액·미지급현황/분개미리보기) — sheet-width-3xl(§0-5 집중폭 준수).
  • P2 2단계 다이얼로그 흐름: DialogOffsetArtDialogPaymentConfirmArt는 표준 sheet 패턴이 아니라 다이얼로그 릴레이(1단계 close+2단계 showModal을 코드에서 직접 호출) — CLAUDE.md §0 R/C/U 통합 규칙과는 별개 축(P2는 "등록 확정" 2스텝 위저드이지 CRUD read↔edit 토글이 아님).
  • 경고/에러: alert alert-error-subtle(초과 채무)·alert alert-warning-subtle(금액 0 이하)·alert alert-info-subtle(안내) — E-시리즈 수납 콘솔 alert 어휘 그대로.

7. 회귀0 가드

  • seedDemoPurchases()/seedDemoPayments()main.js 부트스트랩에서만 호출(모듈 로드 시 자동 소비 안 함) — usePurchases/usePayments를 직접 import하는 회계/GL/재무제표 테스트가 매입 데모로 오염되지 않는다(seedDemoReceipts 선례).
  • resetPurchases()/resetPayments()는 각 스펙 beforeEach에서 상태 초기화용 — 신규 스펙 작성 시 반드시 호출(다른 스펙의 잔여 상태가 섞이는 것 방지).

8. 테스트 위치

src/composables/__tests__/usePurchases.spec.js
src/composables/__tests__/usePayments.spec.js
src/composables/__tests__/usePurchaseJournal.spec.js
src/composables/__tests__/useVendorLedger.spec.js
src/components/purchasing/console/transaction/__tests__/MainOrg.spec.js
src/components/purchasing/console/transaction/blocks/__tests__/DynamicTableOrg.spec.js
src/components/purchasing/console/transaction/overlays/__tests__/DialogCreatePurchaseArt.spec.js
src/components/purchasing/console/transaction/overlays/__tests__/SheetReadPurchaseTransactionArt.spec.js
src/components/purchasing/console/payment/__tests__/MainOrg.spec.js
src/components/purchasing/console/payment/blocks/__tests__/DynamicTableOrg.spec.js
src/components/purchasing/console/payment/overlays/__tests__/DialogOffsetArt.spec.js
src/components/purchasing/console/payment/overlays/__tests__/DialogPaymentConfirmArt.spec.js
src/components/purchasing/console/vendor-ledger/__tests__/MainOrg.spec.js
src/components/purchasing/console/vendor-ledger/blocks/__tests__/DynamicTableOrg.spec.js
src/router/__tests__/buildLineRoutes.spec.js  — purchasing 라인 경로 회귀

9. 확장 포인트

  • 거래처명 마스터 연결: 현재 vendorCode를 코드 그대로 표시(select 옵션도 [...new Set(purchases.map(p=>p.vendorCode))]). 거래처 마스터(등록코드/이름 페어)가 생기면 DialogCreatePurchaseArt의 vendorCode select·DialogOffsetArt/vendor-ledger Select를 이름 표시로 교체하고 usePurchaseJournal.partnerOf() 폴백을 제거.
  • drill-down: vendor-ledger 시계열 행에서 원 매입/지급 상세 시트로 이동하는 링크(현재는 read-only 텍스트 행만).
  • cancelPayment UI: usePayments().cancelPayment(id)는 구현돼 있으나 payment 콘솔에 취소 버튼이 없다. 추가 시 payment/blocks/DynamicTableOrg.vue의 행 확장 영역에 취소 버튼 + 확인 다이얼로그, 취소 후 vouchersPayment()가 자동으로 그 전표를 제외하는지 확인(이미 필터링돼 있음 — pay.status !== '취소').
  • bulk-post 확인 다이얼로그: 현재 postAllPurchases/postAllPayments는 확인 없이 즉시 전기(E-시리즈 선례 동형 — 필요 시 확인 다이얼로그 추가는 두 콘솔 모두에 공통 적용).
  • 세금계산서 가져오기 연동: transaction/MainOrg의 "세금계산서 가져오기" 버튼은 현재 disabled(Phase 4 예정) — 세금계산서 허브 완성 시 recordPurchase 자동 호출 배선.