ERP-node/docs/리포트_문서번호_채번_시스템_설계.md

372 lines
10 KiB
Markdown
Raw Normal View History

2025-10-08 10:32:24 +09:00
# 리포트 문서 번호 자동 채번 시스템 설계
## 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<string>;
// 문서 번호 형식 검증
static async validateNumber(documentNumber: string): Promise<boolean>;
// 문서 번호 중복 체크
static async isDuplicate(documentNumber: string): Promise<boolean>;
// 문서 번호 무효화
static async voidNumber(
documentNumber: string,
reason: string,
userId: string
): Promise<void>;
// 특정 규칙의 다음 번호 미리보기
static async previewNextNumber(
ruleId: number,
deptCode?: string
): Promise<string>;
}
```
### 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<string> {
// 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
<div className="document-number">
<Label>문서 번호</Label>
<Badge variant="outline">{documentNumber || "저장 시 자동 생성"}</Badge>
</div>
```
## 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)