From 8efdb93a1c42d381a351d63a3d5c666743305c0f Mon Sep 17 00:00:00 2001 From: dohyeons Date: Wed, 8 Oct 2025 10:32:24 +0900 Subject: [PATCH] =?UTF-8?q?md=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/리포트_문서번호_채번_시스템_설계.md | 371 +++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 docs/리포트_문서번호_채번_시스템_설계.md diff --git a/docs/리포트_문서번호_채번_시스템_설계.md b/docs/리포트_문서번호_채번_시스템_설계.md new file mode 100644 index 00000000..7222f653 --- /dev/null +++ b/docs/리포트_문서번호_채번_시스템_설계.md @@ -0,0 +1,371 @@ +# 리포트 문서 번호 자동 채번 시스템 설계 + +## 1. 개요 + +리포트 관리 시스템에 체계적인 문서 번호 자동 채번 시스템을 추가하여, 기업 환경에서 문서를 추적하고 관리할 수 있도록 합니다. + +## 2. 문서 번호 형식 + +### 2.1 기본 형식 + +``` +{PREFIX}-{YEAR}-{SEQUENCE} +예: RPT-2024-0001, INV-2024-0123 +``` + +### 2.2 확장 형식 (선택 사항) + +``` +{PREFIX}-{DEPT_CODE}-{YEAR}-{SEQUENCE} +예: RPT-SALES-2024-0001, INV-FIN-2024-0123 +``` + +### 2.3 구성 요소 + +- **PREFIX**: 문서 유형 접두사 (예: RPT, INV, PO, QT) +- **DEPT_CODE**: 부서 코드 (선택 사항) +- **YEAR**: 연도 (4자리) +- **SEQUENCE**: 순차 번호 (0001부터 시작, 자릿수 설정 가능) + +## 3. 데이터베이스 스키마 + +### 3.1 문서 번호 규칙 테이블 + +```sql +-- 문서 번호 규칙 정의 +CREATE TABLE report_number_rules ( + rule_id SERIAL PRIMARY KEY, + rule_name VARCHAR(100) NOT NULL, -- 규칙 이름 + prefix VARCHAR(20) NOT NULL, -- 접두사 (RPT, INV 등) + use_dept_code BOOLEAN DEFAULT FALSE, -- 부서 코드 사용 여부 + use_year BOOLEAN DEFAULT TRUE, -- 연도 사용 여부 + sequence_length INTEGER DEFAULT 4, -- 순차 번호 자릿수 + reset_period VARCHAR(20) DEFAULT 'YEARLY', -- 초기화 주기 (YEARLY, MONTHLY, NEVER) + separator VARCHAR(5) DEFAULT '-', -- 구분자 + description TEXT, -- 설명 + is_active BOOLEAN DEFAULT TRUE, -- 활성화 여부 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by VARCHAR(50), + updated_by VARCHAR(50) +); + +-- 기본 데이터 삽입 +INSERT INTO report_number_rules (rule_name, prefix, description) +VALUES ('리포트 문서 번호', 'RPT', '일반 리포트 문서 번호 규칙'); +``` + +### 3.2 문서 번호 시퀀스 테이블 + +```sql +-- 문서 번호 시퀀스 관리 (연도/부서별 현재 번호) +CREATE TABLE report_number_sequences ( + sequence_id SERIAL PRIMARY KEY, + rule_id INTEGER NOT NULL REFERENCES report_number_rules(rule_id), + dept_code VARCHAR(20), -- 부서 코드 (NULL 가능) + year INTEGER NOT NULL, -- 연도 + current_number INTEGER DEFAULT 0, -- 현재 번호 + last_generated_at TIMESTAMP, -- 마지막 생성 시각 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE (rule_id, dept_code, year) -- 규칙+부서+연도 조합 유니크 +); +``` + +### 3.3 리포트 테이블 수정 + +```sql +-- 기존 report_layout 테이블에 컬럼 추가 +ALTER TABLE report_layout +ADD COLUMN document_number VARCHAR(100), -- 생성된 문서 번호 +ADD COLUMN number_rule_id INTEGER REFERENCES report_number_rules(rule_id), -- 사용된 규칙 +ADD COLUMN number_generated_at TIMESTAMP; -- 번호 생성 시각 + +-- 문서 번호 인덱스 (검색 성능) +CREATE INDEX idx_report_layout_document_number ON report_layout(document_number); +``` + +### 3.4 문서 번호 이력 테이블 (감사용) + +```sql +-- 문서 번호 생성 이력 +CREATE TABLE report_number_history ( + history_id SERIAL PRIMARY KEY, + report_id INTEGER REFERENCES report_layout(id), + document_number VARCHAR(100) NOT NULL, + rule_id INTEGER REFERENCES report_number_rules(rule_id), + generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + generated_by VARCHAR(50), + is_voided BOOLEAN DEFAULT FALSE, -- 번호 무효화 여부 + void_reason TEXT, -- 무효화 사유 + voided_at TIMESTAMP, + voided_by VARCHAR(50) +); + +-- 문서 번호로 검색 인덱스 +CREATE INDEX idx_report_number_history_doc_number ON report_number_history(document_number); +``` + +## 4. 백엔드 구현 + +### 4.1 서비스 레이어 (`reportNumberService.ts`) + +```typescript +export class ReportNumberService { + // 문서 번호 생성 + static async generateNumber( + ruleId: number, + deptCode?: string + ): Promise; + + // 문서 번호 형식 검증 + static async validateNumber(documentNumber: string): Promise; + + // 문서 번호 중복 체크 + static async isDuplicate(documentNumber: string): Promise; + + // 문서 번호 무효화 + static async voidNumber( + documentNumber: string, + reason: string, + userId: string + ): Promise; + + // 특정 규칙의 다음 번호 미리보기 + static async previewNextNumber( + ruleId: number, + deptCode?: string + ): Promise; +} +``` + +### 4.2 컨트롤러 (`reportNumberController.ts`) + +```typescript +// GET /api/report/number-rules - 규칙 목록 +// GET /api/report/number-rules/:id - 규칙 상세 +// POST /api/report/number-rules - 규칙 생성 +// PUT /api/report/number-rules/:id - 규칙 수정 +// DELETE /api/report/number-rules/:id - 규칙 삭제 + +// POST /api/report/:reportId/generate-number - 문서 번호 생성 +// POST /api/report/number/preview - 다음 번호 미리보기 +// POST /api/report/number/void - 문서 번호 무효화 +// GET /api/report/number/history/:documentNumber - 문서 번호 이력 +``` + +### 4.3 핵심 로직 (번호 생성) + +```typescript +async generateNumber(ruleId: number, deptCode?: string): Promise { + // 1. 트랜잭션 시작 + const client = await pool.connect(); + try { + await client.query('BEGIN'); + + // 2. 규칙 조회 + const rule = await this.getRule(ruleId); + + // 3. 현재 연도/월 + const now = new Date(); + const year = now.getFullYear(); + + // 4. 시퀀스 조회 또는 생성 (FOR UPDATE로 락) + let sequence = await this.getSequence(ruleId, deptCode, year, true); + + if (!sequence) { + sequence = await this.createSequence(ruleId, deptCode, year); + } + + // 5. 다음 번호 계산 + const nextNumber = sequence.current_number + 1; + + // 6. 문서 번호 생성 + const documentNumber = this.formatNumber(rule, deptCode, year, nextNumber); + + // 7. 시퀀스 업데이트 + await this.updateSequence(sequence.sequence_id, nextNumber); + + // 8. 커밋 + await client.query('COMMIT'); + + return documentNumber; + + } catch (error) { + await client.query('ROLLBACK'); + throw error; + } finally { + client.release(); + } +} + +// 번호 포맷팅 +private formatNumber( + rule: NumberRule, + deptCode: string | undefined, + year: number, + sequence: number +): string { + const parts = [rule.prefix]; + + if (rule.use_dept_code && deptCode) { + parts.push(deptCode); + } + + if (rule.use_year) { + parts.push(year.toString()); + } + + // 0 패딩 + const paddedSequence = sequence.toString().padStart(rule.sequence_length, '0'); + parts.push(paddedSequence); + + return parts.join(rule.separator); +} +``` + +## 5. 프론트엔드 구현 + +### 5.1 문서 번호 규칙 관리 화면 + +**경로**: `/admin/report/number-rules` + +**기능**: + +- 규칙 목록 조회 +- 규칙 생성/수정/삭제 +- 규칙 미리보기 (다음 번호 확인) +- 규칙 활성화/비활성화 + +### 5.2 리포트 목록 화면 수정 + +**변경 사항**: + +- 문서 번호 컬럼 추가 +- 문서 번호로 검색 기능 + +### 5.3 리포트 저장 시 번호 생성 + +**위치**: `ReportDesignerContext.tsx` - `saveLayout` 함수 + +```typescript +const saveLayout = async () => { + // 1. 새 리포트인 경우 문서 번호 자동 생성 + if (reportId === "new" && !documentNumber) { + const response = await fetch(`/api/report/generate-number`, { + method: "POST", + body: JSON.stringify({ ruleId: 1 }), // 기본 규칙 + }); + const { documentNumber: newNumber } = await response.json(); + setDocumentNumber(newNumber); + } + + // 2. 리포트 저장 (문서 번호 포함) + await saveReport({ ...reportData, documentNumber }); +}; +``` + +### 5.4 문서 번호 표시 UI + +**위치**: 디자이너 헤더 + +```tsx +
+ + {documentNumber || "저장 시 자동 생성"} +
+``` + +## 6. 동시성 제어 + +### 6.1 문제점 + +여러 사용자가 동시에 문서 번호를 생성할 때 중복 발생 가능성 + +### 6.2 해결 방법 + +**PostgreSQL의 `FOR UPDATE` 사용** + +```sql +-- 시퀀스 조회 시 행 락 걸기 +SELECT * FROM report_number_sequences +WHERE rule_id = $1 AND year = $2 +FOR UPDATE; +``` + +**트랜잭션 격리 수준** + +```typescript +await client.query("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"); +``` + +## 7. 테스트 시나리오 + +### 7.1 기본 기능 테스트 + +- [ ] 규칙 생성 → 문서 번호 생성 → 포맷 확인 +- [ ] 연속 생성 시 순차 번호 증가 확인 +- [ ] 연도 변경 시 시퀀스 초기화 확인 + +### 7.2 동시성 테스트 + +- [ ] 10명이 동시에 문서 번호 생성 → 중복 없음 확인 +- [ ] 동일 규칙으로 100개 생성 → 순차 번호 연속성 확인 + +### 7.3 에러 처리 + +- [ ] 존재하지 않는 규칙 ID → 에러 메시지 +- [ ] 비활성화된 규칙 사용 → 경고 메시지 +- [ ] 시퀀스 최대값 초과 → 관리자 알림 + +## 8. 구현 순서 + +### Phase 1: 데이터베이스 (1단계) + +1. 테이블 생성 SQL 작성 +2. 마이그레이션 실행 +3. 기본 데이터 삽입 + +### Phase 2: 백엔드 (2단계) + +1. `reportNumberService.ts` 구현 +2. `reportNumberController.ts` 구현 +3. 라우트 추가 +4. 단위 테스트 + +### Phase 3: 프론트엔드 (3단계) + +1. 문서 번호 규칙 관리 화면 +2. 리포트 목록 화면 수정 +3. 디자이너 문서 번호 표시 +4. 저장 시 자동 생성 연동 + +### Phase 4: 테스트 및 최적화 (4단계) + +1. 통합 테스트 +2. 동시성 테스트 +3. 성능 최적화 +4. 사용자 가이드 작성 + +## 9. 향후 확장 + +### 9.1 고급 기능 + +- 문서 번호 예약 기능 +- 번호 건너뛰기 허용 설정 +- 커스텀 포맷 지원 (정규식 기반) +- 연/월/일 단위 초기화 선택 + +### 9.2 통합 + +- 승인 완료 시점에 최종 번호 확정 +- 외부 시스템과 번호 동기화 +- 바코드/QR 코드 자동 생성 + +## 10. 보안 고려사항 + +- 문서 번호 생성 권한 제한 +- 번호 무효화 감사 로그 +- 시퀀스 직접 수정 방지 +- API 호출 횟수 제한 (Rate Limiting) +