다크모드
세금계산서 리스트 (FE 메인테이너)
독자: FE 메인테이너/AI. 빌링 코어 세금계산서 콘솔 리스트의 후보 산출, 전자문서 원장 연결, 상태·이력과 재시도 경계를 설명한다. BE 계약은
docs/handoff/backend/tax-documents.md다.
라우트·진입
- 동적 생성:
router/buildLineRoutes.js, core segmenttax-invoice - 관리비 actual:
/service-charge/actual/console/tax-invoice isCanon = billing.line==='service-charge' && edition==='actual'이면 관리비 배분 기반 후보 행, 그 외는 레거시 mock 행이다.
컴포넌트 트리
tax-invoice/IndexView.vueblocks/DynamicTableOrg.vue: 목록·scope hydrateoverlays/DialogBulkIssueTaxInvoiceArt.vue: 미발행 대상 실집계와 발행 요청overlays/DialogBulkRetryTaxInvoiceArt.vue: 오류 대상 집계와 재시도 요청overlays/SheetReadTaxInvoiceArt.vue: 발행 정보·서버 이벤트 이력·상태별 액션src/composables/useTaxInvoiceIssuance.js: 화면 projectionsrc/composables/electronicDocumentIssueRepo.js: 서버 원장 adapter
후보와 원장 데이터 흐름
- 관리비 actual 후보는
useAllocations().taxInvoicesByProfile()에서{key,name,businessRegistrationNumber,documentType,taxCategory,supply,vat,total}을 산출한다. - 행 id는
${key}-${taxCategory}다. 과세·면세 겸유 시 서로 다른 opaque source record가 된다. DynamicTableOrg가 Company/Office scope로hydrate()를 호출한다.electronicDocumentIssueRepo.list()와listEvents()결과를useTaxInvoiceIssuance가REQUESTS/EVENTSprojection에 넣는다.- 행 상태는
statusOf(id), 상세은recordOf(id)를 읽는다. 서버 요청이 없는 행만미발행이다.
발행 후보 산출은 아직 관리비 composable에 의존하지만, 발행 후 snapshot·상태·이력은 전자문서 서버 원장이 정본이다. 다른 billing line의 mock 행은 canAct=false이며 운영 원장 액션으로 취급하지 않는다.
발행과 상태
DialogBulkIssueTaxInvoiceArt는 issuanceSummary()로 미발행 적격 문서의 건수·공급가액·세액·합계를 집계한다. issue()는 각 문서를 immutable snapshot으로 repo.create()에 전달한다.
- create 전 화면은 local
adapter_pendingprojection을 둘 수 있다. - 서버 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_retryRPC를 호출한다. 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.jssrc/composables/__tests__/useTaxInvoiceIssuance.spec.jssrc/composables/__tests__/electronicDocumentRetryUi.spec.jssrc/composables/__tests__/customerFacingProviderNaming.spec.js
핵심 회귀는 create/retry가 queued에서 멈추고 server event만 success를 만들며, 상세가 append-only events와 실제 retry 오류를 표시하고, 개인·일괄 UI가 loading/error/queued를 구분한다는 점이다.