# 리포트 문서 번호 자동 채번 시스템 설계 ## 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)