Skip to content

세금계산서 리스트 (FE 메인테이너)

독자: FE 메인테이너/AI. 빌링 코어 세금계산서 콘솔 리스트의 후보 산출, 전자문서 원장 연결, 상태·이력과 재시도 경계를 설명한다. BE 계약은 docs/handoff/backend/tax-documents.md다.

라우트·진입

  • 동적 생성: router/buildLineRoutes.js, core segment tax-invoice
  • 관리비 actual: /service-charge/actual/console/tax-invoice
  • isCanon = billing.line==='service-charge' && edition==='actual'이면 관리비 배분 기반 후보 행, 그 외는 레거시 mock 행이다.

컴포넌트 트리

  • tax-invoice/IndexView.vue
  • blocks/DynamicTableOrg.vue: 목록·scope hydrate
  • overlays/DialogBulkIssueTaxInvoiceArt.vue: 미발행 대상 실집계와 발행 요청
  • overlays/DialogBulkRetryTaxInvoiceArt.vue: 오류 대상 집계와 재시도 요청
  • overlays/SheetReadTaxInvoiceArt.vue: 발행 정보·서버 이벤트 이력·상태별 액션
  • src/composables/useTaxInvoiceIssuance.js: 화면 projection
  • src/composables/electronicDocumentIssueRepo.js: 서버 원장 adapter

후보와 원장 데이터 흐름

  1. 관리비 actual 후보는 useAllocations().taxInvoicesByProfile()에서 {key,name,businessRegistrationNumber,documentType,taxCategory,supply,vat,total}을 산출한다.
  2. 행 id는 ${key}-${taxCategory}다. 과세·면세 겸유 시 서로 다른 opaque source record가 된다.
  3. DynamicTableOrg가 Company/Office scope로 hydrate()를 호출한다.
  4. electronicDocumentIssueRepo.list()listEvents() 결과를 useTaxInvoiceIssuanceREQUESTS/EVENTS projection에 넣는다.
  5. 행 상태는 statusOf(id), 상세은 recordOf(id)를 읽는다. 서버 요청이 없는 행만 미발행이다.

발행 후보 산출은 아직 관리비 composable에 의존하지만, 발행 후 snapshot·상태·이력은 전자문서 서버 원장이 정본이다. 다른 billing line의 mock 행은 canAct=false이며 운영 원장 액션으로 취급하지 않는다.

발행과 상태

DialogBulkIssueTaxInvoiceArtissuanceSummary()로 미발행 적격 문서의 건수·공급가액·세액·합계를 집계한다. issue()는 각 문서를 immutable snapshot으로 repo.create()에 전달한다.

  • create 전 화면은 local adapter_pending projection을 둘 수 있다.
  • 서버 create 응답은 adapter_pending|queued이며 화면 라벨은 전송대기다.
  • 브라우저는 create 성공을 전송완료로 승격하지 않는다.
  • submitted|succeeded|failed는 서버 event가 있을 때만 전송중|전송완료|오류가 된다.
  • 청구 rollup transmissionStatusFor(ids)succeeded만 전송 성공으로 센다.

상세와 append-only 이력

SheetReadTaxInvoiceArt는 멤버 이름 클릭으로 열고 다음을 표시한다.

  • zone-top: 문서구분·발행구분·사업자등록번호·현재 상태·관리코드
  • 발행 정보: 작성일자·공급가액·세액·합계·결과 번호
  • 전송 이력: rec.events를 최근순으로 변환한 실제 원장 이벤트

결과 번호는 가장 최근 succeeded event의 approvalNumber|transportReference에서만 읽는다. 성공 event가 없으면 서버 결과 수신 전을 표시한다. 현재 상태를 근거로 발행/성공/실패 history 행을 만들어내지 않는다.

재시도

retryTransmit(id)failed 상태에서만 동작한다.

  • mock repo와 Supabase repo 모두 expectedRevision·failed-only·requestKey 계약을 사용한다.
  • Supabase repo는 public request_electronic_document_delivery_retry RPC를 호출한다. private DB function을 브라우저에서 직접 호출하지 않는다.
  • useTaxInvoiceIssuance.retryTransmit()은 응답 전 optimistic queued를 만들지 않는다. 성공 응답 후 events를 다시 읽고, 실패하면 기존 failed 상태와 사용자용 lastErrorMessage를 유지한다.
  • 서버는 exact replay에 기존 응답을 반환하고 같은 key의 다른 issue/revision을 conflict로 거부한다.
  • retry 성공 projection은 queued/전송대기다. 실제 전송 성공은 후속 server event가 있어야 한다.

SheetReadTaxInvoiceArt는 개인 재시도 동안 버튼을 disabled하고 요청 중…을 표시한다. 응답 뒤 성공/실패 alert를 이력 아래에 유지하며 delivery_retry_queued event를 전송 재시도 대기열 등록으로 번역한다.

DialogBulkRetryTaxInvoiceArt는 시작 시 오류 대상을 snapshot하고 Promise.all로 각각 retry한다. 처리 중 버튼을 막고 다이얼로그를 자동으로 닫지 않으며, 완료 후 성공 n건 · 실패 n건을 보여준다. 성공 문서는 queued가 되어 reactive 오류 대상에서 빠지고 실패 문서는 상세에서 원인을 확인한다.

문서구분·모바일

  • 문서구분 badge: 세금계산서=info, 계산서=success, 영세율세금계산서=warning
  • 공급가액·세액·합계 컬럼을 유지하며 면세·영세율 세액은 0이다.
  • table-truncate + sticky-col(멤버)·sticky-col-end(전송)을 사용한다.
  • 모바일에서는 DS가 끝 고정열을 해제해 단일 고정열만 남긴다.

테스트

  • src/composables/__tests__/electronicDocumentIssueRepo.spec.js
  • src/composables/__tests__/useTaxInvoiceIssuance.spec.js
  • src/composables/__tests__/electronicDocumentRetryUi.spec.js
  • src/composables/__tests__/customerFacingProviderNaming.spec.js

핵심 회귀는 create/retry가 queued에서 멈추고 server event만 success를 만들며, 상세가 append-only events와 실제 retry 오류를 표시하고, 개인·일괄 UI가 loading/error/queued를 구분한다는 점이다.