모든 ERP 테이블은 books.id를 NOT NULL FK + ON DELETE RESTRICT로 참조한다.
books가 모든 데이터의 hub이고, 다른 테이블은 books를 향한 위성이다.
flowchart TB
classDef ssot fill:#fffbeb,stroke:#c0392b,stroke-width:2px,color:#222
classDef ext fill:#e3f2fd,stroke:#1565c0,color:#1565c0
classDef side fill:#f8f9fa,stroke:#999,color:#6c6f85,stroke-dasharray: 4 3
classDef phase fill:#fff,stroke:#222
Hub[("📚 books
도서 마스터")]:::ssot
subgraph P1["① 계약 체결"]
direction TB
Modusign[모두싸인 사이트
전자계약]:::ext
Upload[ERP 업로드
드래그앤드롭]
Storage[(Firebase Storage
PDF 원본)]:::ssot
Skill[Claude Code 스킬
PDF Read → 9개 필드]
Contracts[("contracts
인세율·선인세·기한")]:::ssot
Modusign -->|PDF 다운로드| Upload
Upload --> Storage
Upload --> Contracts
Contracts --> Skill
Skill --> Contracts
end
subgraph P2["② 편집 진행"]
Editorial[("editorial_projects
편집 상태·담당자")]:::ssot
end
subgraph P3["③ 출간·제작"]
WO[("work_orders
제작 발주서")]:::ssot
WOF[("work_order_files
1:N 첨부")]:::ssot
PR[("print_runs
인쇄 이력 쇄별")]:::ssot
BT[("book_translations
ko/en/zh/ja")]:::ssot
Dropbox[Dropbox
회사 운영 미러]:::side
WO --- WOF
end
subgraph P4["④ 정산·청구"]
Settle[("settlements
인세 정산 = 계산값")]:::ssot
Inv[("invoice_items
청구서 라인")]:::ssot
Alloc[("invoice_item_allocations
쇄별 분배 트리거")]:::ssot
Settle --> Inv
Inv --> Alloc
PR --> Alloc
end
subgraph P5["⑤ 게시·검색"]
RSC[홈페이지 RSC]
Typesense[Typesense
검색 인덱스]:::side
end
Contracts -.-> Hub
Editorial -.-> Hub
WO -.-> Hub
PR -.-> Hub
BT -.-> Hub
Settle -.-> Hub
Inv -.-> Hub
Dropbox -. 참고 .- WO
Hub --> RSC
Hub --> Typesense
BT --> RSC
SSoT
외부 원본 시스템
사이드카 (참고/복사)
점선: book_id FK · 실선: 데이터 흐름
| 단계 | SSoT (단일 진실의 원천) | 사이드카 (참고/복사) | 핵심 데이터 |
|---|---|---|---|
| 계약 | Supabase contracts (메타)+ Firebase Storage (PDF 원본) |
모두싸인 사이트 (audit 필요 시 역추적) |
인세율 (종이/전자/오디오), 선인세, 계약일, 원고/출판 기한, 존속기간 |
| 편집 | Supabase editorial_projects |
— | 편집 진행 상태, 담당자, 마일스톤 |
| 도서 메타 | Supabase books |
Typesense (검색 인덱스) 자동 동기화 |
제목, 저자, ISBN, 카테고리, 가격, 표지, 출간일, 상태 |
| 제작·발주 | Supabase work_orders +work_order_files (1:N) |
Dropbox 4.제작/작업의뢰서/사람용 운영 미러 |
발주일, 인쇄소, 부수, 용지 공급처, 담당자 |
| 인쇄 이력 | Supabase print_runs |
— | 쇄별 부수, 입고일, 정가 |
| 다국어 | Supabase book_translations |
— | ko/en/zh/ja 별 제목·소개·목차·저자소개 |
| 정산 | Supabase settlements= contracts × print_runs (계산값) |
— | 저자별 인세 지급액, 정산 주기 |
| 청구 | Supabase invoice_items +invoice_item_allocations |
— | 거래처별 청구 라인 + 쇄별 분배 (DB 트리거 자동 계산) |
| 게시 | Next.js RSC (books + book_translations 직조회) | — | 공개 도서 페이지, 검색, 카테고리 |
book_id NOT NULL + ON DELETE RESTRICT로 books를 참조한다.
새 도서는 라이프사이클의 가장 먼저 만들어지고, 가장 나중에 삭제될 수 있다 (참조가 다 사라진 후).
settlements는 contracts × print_runs의 파생 계산값.
invoice_item_allocations는 invoice_items + print_runs의 트리거 자동 계산.
계산값은 SSoT가 아니라, 원본값이 바뀌면 다시 계산해야 한다.
contracts.royalty_rate 같은 컬럼이 정산 로직의 SSoT.
contracts.parsed_data JSONB는 AI 파싱 원본 보존 + 자유 추가 필드 (참조 안정성 보장 X).
정산 코드는 컬럼만 읽고 JSON은 보지 않는다.
4.제작/작업의뢰서/는 회사 운영자가 보는 GUI 미러일 뿐, ERP의 SSoT는 work_orders.
사이드카가 없거나 늦어도 ERP는 동작한다.
sequenceDiagram
actor User as 사용자
participant ERP as ERP UI
participant FS as Firebase Storage
participant DB as Supabase
participant Claude as Claude Code 스킬
Note over User,Claude: ① 업로드 (즉시)
User->>ERP: PDF 드래그앤드롭
(+선택: modusign URL)
ERP->>FS: PDF 업로드
FS-->>ERP: firebase_path
ERP->>DB: contracts INSERT
(parse_status='pending')
DB-->>User: 업로드 완료 ✓
Note over User,Claude: ② 파싱 (사용자가 트리거)
User->>Claude: /parse-contracts
Claude->>DB: pending row 조회
Claude->>FS: PDF 다운로드 + Read tool
Claude->>Claude: 9개 필드 추출
+ books fuzzy 매칭
Claude->>User: 추출 결과 + book 후보 표시
Note over User,Claude: ③ 검증·확정
User->>Claude: confirm + book_id 선택
Claude->>DB: contracts UPDATE
(parse_status='active')
DB-->>User: 라이프사이클 시작 ✓
| 연도 | contracts 등록 | firebase_path | modusign_id | 상태 |
|---|---|---|---|---|
| 2020 | 40 | 40 (100%) | 40 (100%) | ✓ 완전 |
| 2021 | 134 | 134 (100%) | 134 (100%) | ✓ 완전 |
| 2022 | 74 | 74 (100%) | 74 (100%) | ✓ 완전 |
| 2023 | 119 | 119 (100%) | 119 (100%) | ✓ 완전 |
| 2024 | 157 | 157 (100%) | 157 (100%) | ✓ 완전 |
| 2025 | 156 | 156 (100%) | 156 (100%) | ✓ 완전 |
| 2026 | 55 | 53 | 53 | 2건 진행 중 (가제) |
총 685건 · SSoT 결손 0건 · ERP가 회사 마스터 데이터의 실질 진실의 원천.