dev #46
|
|
@ -0,0 +1,74 @@
|
|||
const { PrismaClient } = require("@prisma/client");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function createCommonCodeTables() {
|
||||
try {
|
||||
console.log("=== 공통코드 테이블 생성 시작 ===");
|
||||
|
||||
// 1. code_category 테이블 생성
|
||||
await prisma.$executeRaw`
|
||||
CREATE TABLE IF NOT EXISTS code_category (
|
||||
category_code VARCHAR(50) PRIMARY KEY,
|
||||
category_name VARCHAR(100) NOT NULL,
|
||||
category_name_eng VARCHAR(100),
|
||||
description TEXT,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
is_active CHAR(1) DEFAULT 'Y',
|
||||
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(50),
|
||||
updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_by VARCHAR(50)
|
||||
)
|
||||
`;
|
||||
console.log("✅ code_category 테이블 생성 완료");
|
||||
|
||||
// 2. code_info 테이블 생성
|
||||
await prisma.$executeRaw`
|
||||
CREATE TABLE IF NOT EXISTS code_info (
|
||||
code_category VARCHAR(50) NOT NULL,
|
||||
code_value VARCHAR(50) NOT NULL,
|
||||
code_name VARCHAR(100) NOT NULL,
|
||||
code_name_eng VARCHAR(100),
|
||||
description TEXT,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
is_active CHAR(1) DEFAULT 'Y',
|
||||
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(50),
|
||||
updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_by VARCHAR(50),
|
||||
PRIMARY KEY (code_category, code_value),
|
||||
CONSTRAINT fk_code_info_category
|
||||
FOREIGN KEY (code_category) REFERENCES code_category(category_code)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
)
|
||||
`;
|
||||
console.log("✅ code_info 테이블 생성 완료");
|
||||
|
||||
// 3. 인덱스 생성
|
||||
await prisma.$executeRaw`
|
||||
CREATE INDEX IF NOT EXISTS idx_code_category_active ON code_category(is_active)
|
||||
`;
|
||||
await prisma.$executeRaw`
|
||||
CREATE INDEX IF NOT EXISTS idx_code_category_sort ON code_category(sort_order)
|
||||
`;
|
||||
await prisma.$executeRaw`
|
||||
CREATE INDEX IF NOT EXISTS idx_code_info_category ON code_info(code_category)
|
||||
`;
|
||||
await prisma.$executeRaw`
|
||||
CREATE INDEX IF NOT EXISTS idx_code_info_active ON code_info(is_active)
|
||||
`;
|
||||
await prisma.$executeRaw`
|
||||
CREATE INDEX IF NOT EXISTS idx_code_info_sort ON code_info(code_category, sort_order)
|
||||
`;
|
||||
console.log("✅ 인덱스 생성 완료");
|
||||
|
||||
console.log("🎉 공통코드 테이블 생성 완료!");
|
||||
} catch (error) {
|
||||
console.error("❌ 오류 발생:", error);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
createCommonCodeTables();
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
const { PrismaClient } = require("@prisma/client");
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function insertCommonCodeData() {
|
||||
try {
|
||||
console.log("=== 공통코드 기본 데이터 삽입 시작 ===");
|
||||
|
||||
// 기존 데이터 삭제 (재실행 시 중복 방지)
|
||||
await prisma.$executeRaw`DELETE FROM code_info WHERE code_category IN ('USER_STATUS', 'USER_TYPE', 'DEPT_TYPE', 'LANGUAGE', 'CURRENCY')`;
|
||||
await prisma.$executeRaw`DELETE FROM code_category WHERE category_code IN ('USER_STATUS', 'USER_TYPE', 'DEPT_TYPE', 'LANGUAGE', 'CURRENCY')`;
|
||||
console.log("✅ 기존 데이터 정리 완료");
|
||||
|
||||
// 1. 카테고리 삽입
|
||||
const categories = [
|
||||
[
|
||||
"USER_STATUS",
|
||||
"사용자 상태",
|
||||
"User Status",
|
||||
"사용자의 활성화 상태를 나타내는 코드",
|
||||
1,
|
||||
],
|
||||
[
|
||||
"USER_TYPE",
|
||||
"사용자 타입",
|
||||
"User Type",
|
||||
"사용자의 권한 타입을 구분하는 코드",
|
||||
2,
|
||||
],
|
||||
[
|
||||
"DEPT_TYPE",
|
||||
"부서 타입",
|
||||
"Department Type",
|
||||
"부서의 분류를 나타내는 코드",
|
||||
3,
|
||||
],
|
||||
[
|
||||
"LANGUAGE",
|
||||
"언어 코드",
|
||||
"Language Code",
|
||||
"시스템에서 지원하는 언어 코드",
|
||||
4,
|
||||
],
|
||||
[
|
||||
"CURRENCY",
|
||||
"통화 코드",
|
||||
"Currency Code",
|
||||
"시스템에서 지원하는 통화 코드",
|
||||
5,
|
||||
],
|
||||
];
|
||||
|
||||
for (const [code, name, nameEng, desc, order] of categories) {
|
||||
await prisma.$executeRaw`
|
||||
INSERT INTO code_category (category_code, category_name, category_name_eng, description, sort_order, is_active, created_date, created_by, updated_date, updated_by)
|
||||
VALUES (${code}, ${name}, ${nameEng}, ${desc}, ${order}, 'Y', now(), 'SYSTEM', now(), 'SYSTEM')
|
||||
`;
|
||||
}
|
||||
console.log("✅ 카테고리 데이터 삽입 완료");
|
||||
|
||||
// 2. 코드 정보 삽입
|
||||
const codes = [
|
||||
// 사용자 상태
|
||||
["USER_STATUS", "A", "활성", "Active", "정상적으로 활동 중인 사용자", 1],
|
||||
[
|
||||
"USER_STATUS",
|
||||
"I",
|
||||
"비활성",
|
||||
"Inactive",
|
||||
"일시적으로 비활성화된 사용자",
|
||||
2,
|
||||
],
|
||||
[
|
||||
"USER_STATUS",
|
||||
"S",
|
||||
"휴면",
|
||||
"Sleep",
|
||||
"장기간 미접속으로 휴면 상태인 사용자",
|
||||
3,
|
||||
],
|
||||
["USER_STATUS", "D", "삭제", "Deleted", "삭제 처리된 사용자", 4],
|
||||
|
||||
// 사용자 타입
|
||||
[
|
||||
"USER_TYPE",
|
||||
"ADMIN",
|
||||
"관리자",
|
||||
"Administrator",
|
||||
"시스템 전체를 관리할 수 있는 최고 권한",
|
||||
1,
|
||||
],
|
||||
[
|
||||
"USER_TYPE",
|
||||
"USER",
|
||||
"일반사용자",
|
||||
"User",
|
||||
"일반적인 업무 기능을 사용할 수 있는 사용자",
|
||||
2,
|
||||
],
|
||||
[
|
||||
"USER_TYPE",
|
||||
"GUEST",
|
||||
"게스트",
|
||||
"Guest",
|
||||
"제한적인 기능만 사용할 수 있는 게스트 사용자",
|
||||
3,
|
||||
],
|
||||
[
|
||||
"USER_TYPE",
|
||||
"PARTNER",
|
||||
"협력업체",
|
||||
"Partner",
|
||||
"협력업체 직원으로 특정 기능만 사용 가능",
|
||||
4,
|
||||
],
|
||||
|
||||
// 부서 타입
|
||||
[
|
||||
"DEPT_TYPE",
|
||||
"SALES",
|
||||
"영업부",
|
||||
"Sales Department",
|
||||
"영업 관련 업무를 담당하는 부서",
|
||||
1,
|
||||
],
|
||||
[
|
||||
"DEPT_TYPE",
|
||||
"DEV",
|
||||
"개발부",
|
||||
"Development Department",
|
||||
"시스템 개발을 담당하는 부서",
|
||||
2,
|
||||
],
|
||||
[
|
||||
"DEPT_TYPE",
|
||||
"HR",
|
||||
"인사부",
|
||||
"Human Resources Department",
|
||||
"인사 관리를 담당하는 부서",
|
||||
3,
|
||||
],
|
||||
[
|
||||
"DEPT_TYPE",
|
||||
"FINANCE",
|
||||
"재무부",
|
||||
"Finance Department",
|
||||
"재무 관리를 담당하는 부서",
|
||||
4,
|
||||
],
|
||||
[
|
||||
"DEPT_TYPE",
|
||||
"ADMIN",
|
||||
"관리부",
|
||||
"Administration Department",
|
||||
"일반 관리 업무를 담당하는 부서",
|
||||
5,
|
||||
],
|
||||
|
||||
// 언어 코드
|
||||
["LANGUAGE", "KR", "한국어", "Korean", "한국어 언어 설정", 1],
|
||||
["LANGUAGE", "US", "영어", "English", "영어 언어 설정", 2],
|
||||
["LANGUAGE", "CN", "중국어", "Chinese", "중국어 언어 설정", 3],
|
||||
["LANGUAGE", "JP", "일본어", "Japanese", "일본어 언어 설정", 4],
|
||||
|
||||
// 통화 코드
|
||||
["CURRENCY", "KRW", "원", "Korean Won", "대한민국 원화", 1],
|
||||
["CURRENCY", "USD", "달러", "US Dollar", "미국 달러", 2],
|
||||
["CURRENCY", "EUR", "유로", "Euro", "유럽 유로", 3],
|
||||
["CURRENCY", "JPY", "엔", "Japanese Yen", "일본 엔화", 4],
|
||||
["CURRENCY", "CNY", "위안", "Chinese Yuan", "중국 위안화", 5],
|
||||
];
|
||||
|
||||
for (const [category, value, name, nameEng, desc, order] of codes) {
|
||||
await prisma.$executeRaw`
|
||||
INSERT INTO code_info (code_category, code_value, code_name, code_name_eng, description, sort_order, is_active, created_date, created_by, updated_date, updated_by)
|
||||
VALUES (${category}, ${value}, ${name}, ${nameEng}, ${desc}, ${order}, 'Y', now(), 'SYSTEM', now(), 'SYSTEM')
|
||||
`;
|
||||
}
|
||||
console.log("✅ 코드 정보 데이터 삽입 완료");
|
||||
|
||||
// 3. 삽입 결과 확인
|
||||
const categoryCount =
|
||||
await prisma.$queryRaw`SELECT COUNT(*) as count FROM code_category WHERE created_by = 'SYSTEM'`;
|
||||
const codeCount =
|
||||
await prisma.$queryRaw`SELECT COUNT(*) as count FROM code_info WHERE created_by = 'SYSTEM'`;
|
||||
|
||||
console.log(`📊 삽입된 카테고리 수: ${categoryCount[0].count}`);
|
||||
console.log(`📊 삽입된 코드 수: ${codeCount[0].count}`);
|
||||
console.log("🎉 공통코드 기본 데이터 삽입 완료!");
|
||||
} catch (error) {
|
||||
console.error("❌ 오류 발생:", error);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
insertCommonCodeData();
|
||||
|
|
@ -5145,3 +5145,50 @@ model screen_menu_assignments {
|
|||
@@unique([screen_id, menu_objid, company_code])
|
||||
@@index([company_code])
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 공통코드 관리 시스템 모델
|
||||
// =====================================================
|
||||
|
||||
/// 공통코드 카테고리 테이블
|
||||
model code_category {
|
||||
category_code String @id @db.VarChar(50)
|
||||
category_name String @db.VarChar(100)
|
||||
category_name_eng String? @db.VarChar(100)
|
||||
description String? @db.Text
|
||||
sort_order Int @default(0)
|
||||
is_active String @default("Y") @db.Char(1)
|
||||
created_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
updated_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
updated_by String? @db.VarChar(50)
|
||||
|
||||
// 관계 - 코드 상세 정보
|
||||
codes code_info[]
|
||||
|
||||
@@index([is_active])
|
||||
@@index([sort_order])
|
||||
}
|
||||
|
||||
/// 공통코드 상세 정보 테이블
|
||||
model code_info {
|
||||
code_category String @db.VarChar(50)
|
||||
code_value String @db.VarChar(50)
|
||||
code_name String @db.VarChar(100)
|
||||
code_name_eng String? @db.VarChar(100)
|
||||
description String? @db.Text
|
||||
sort_order Int @default(0)
|
||||
is_active String @default("Y") @db.Char(1)
|
||||
created_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
updated_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
updated_by String? @db.VarChar(50)
|
||||
|
||||
// 관계 - 코드 카테고리
|
||||
category code_category @relation(fields: [code_category], references: [category_code], onDelete: Cascade, onUpdate: Cascade)
|
||||
|
||||
@@id([code_category, code_value])
|
||||
@@index([code_category])
|
||||
@@index([is_active])
|
||||
@@index([code_category, sort_order])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,745 @@
|
|||
# 공통코드 관리 시스템 설계 문서
|
||||
|
||||
## 📋 목차
|
||||
|
||||
1. [시스템 개요](#🎯-시스템-개요)
|
||||
2. [아키텍처 구조](#🏗️-아키텍처-구조)
|
||||
3. [핵심 기능](#🚀-핵심-기능)
|
||||
4. [데이터베이스 설계](#🗄️-데이터베이스-설계)
|
||||
5. [화면 구성 요소](#🎨-화면-구성-요소)
|
||||
6. [API 설계](#🌐-api-설계)
|
||||
7. [프론트엔드 구현](#🎭-프론트엔드-구현)
|
||||
8. [백엔드 구현](#⚙️-백엔드-구현)
|
||||
9. [사용 시나리오](#🎬-사용-시나리오)
|
||||
10. [개발 계획](#📅-개발-계획-및-진행상황)
|
||||
|
||||
## 🎯 시스템 개요
|
||||
|
||||
### 공통코드 관리 시스템이란?
|
||||
|
||||
공통코드 관리 시스템은 **시스템에서 사용하는 공통적인 코드값들을 중앙에서 관리하는 기능**입니다. 드롭다운, 선택박스 등에서 반복적으로 사용되는 코드-값 쌍을 체계적으로 관리하여 데이터 일관성을 보장하고 개발 효율성을 높입니다.
|
||||
|
||||
### 주요 특징
|
||||
|
||||
- **중앙 집중 관리**: 모든 공통코드를 한 곳에서 통합 관리
|
||||
- **카테고리 기반 분류**: 코드를 카테고리별로 체계적 분류
|
||||
- **다국어 지원**: 한국어/영어 코드명 지원
|
||||
- **화면관리 시스템 연계**: 웹 타입 'code'와 완벽 연동
|
||||
- **실시간 반영**: 코드 변경사항 즉시 시스템 전체 반영
|
||||
- **관리자 전용**: 관리자 메뉴에서만 접근 가능
|
||||
|
||||
### 🎯 **필수 요구사항**
|
||||
|
||||
- ✅ **관리자 메뉴 접근**: 관리자 메뉴에서만 접근 가능
|
||||
- ✅ **코드 카테고리 관리**: 카테고리 생성/수정/삭제
|
||||
- ✅ **코드 상세 관리**: 코드값과 코드명 매핑 관리
|
||||
- ✅ **정렬 순서 관리**: 코드 표시 순서 조정
|
||||
- ✅ **활성/비활성 관리**: 코드 사용 여부 제어
|
||||
- ✅ **검색 및 필터링**: 대량 코드 효율적 관리
|
||||
- ✅ **화면관리 연계**: column_labels.code_category와 연동
|
||||
|
||||
## 🏗️ 아키텍처 구조
|
||||
|
||||
### 전체 구조도
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Frontend │ │ Backend │ │ Database │
|
||||
│ │ │ │ │ │
|
||||
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
|
||||
│ │ CodeCategory│ │ │ │ CommonCode │ │ │ │ code_ │ │
|
||||
│ │ Management │ │ │ │ Controller │ │ │ │ category │ │
|
||||
│ │ (React) │ │ │ │ │ │ │ │ Table │ │
|
||||
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
|
||||
│ │ CodeDetail │ │ │ │ CommonCode │ │ │ │ code_info │ │
|
||||
│ │ Management │ │ │ │ Service │ │ │ │ Table │ │
|
||||
│ │ (React) │ │ │ │ │ │ │ │ │ │
|
||||
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
### 화면관리 시스템 연계
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ 화면관리 시스템 │ │ 공통코드 관리 │ │ 실제 화면 │
|
||||
│ │ │ │ │ │
|
||||
│ column_labels │───▶│ code_category │───▶│ Select Widget │
|
||||
│ web_type='code' │ │ code_info │ │ <option>값 │
|
||||
│ code_category │ │ │ │ │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## 🚀 핵심 기능
|
||||
|
||||
### 1. 코드 카테고리 관리
|
||||
|
||||
- **카테고리 생성**: 새로운 코드 카테고리 추가
|
||||
- **카테고리 수정**: 카테고리명, 설명 수정
|
||||
- **카테고리 삭제**: 사용하지 않는 카테고리 삭제 (CASCADE)
|
||||
- **카테고리 활성화**: 카테고리 사용 여부 제어
|
||||
- **카테고리 검색**: 카테고리명으로 빠른 검색
|
||||
|
||||
### 2. 코드 상세 관리
|
||||
|
||||
- **코드 생성**: 카테고리 내 새로운 코드 추가
|
||||
- **코드 수정**: 코드값, 코드명, 영문명 수정
|
||||
- **코드 삭제**: 불필요한 코드 삭제
|
||||
- **정렬 순서**: 드래그앤드롭으로 순서 변경
|
||||
- **일괄 업로드**: CSV/Excel 파일로 대량 등록
|
||||
- **일괄 다운로드**: 현재 코드 데이터 내보내기
|
||||
|
||||
### 3. 화면관리 연계
|
||||
|
||||
- **자동 옵션 생성**: code_category 기반 select 옵션 자동 생성
|
||||
- **실시간 반영**: 코드 변경 시 관련 화면 즉시 업데이트
|
||||
- **참조 관계 표시**: 어떤 화면에서 사용 중인지 표시
|
||||
|
||||
### 4. 검색 및 필터링
|
||||
|
||||
- **통합 검색**: 카테고리명, 코드값, 코드명 통합 검색
|
||||
- **상태 필터**: 활성/비활성 코드 필터링
|
||||
- **카테고리 필터**: 특정 카테고리만 표시
|
||||
- **페이징**: 대량 데이터 효율적 표시
|
||||
|
||||
## 🗄️ 데이터베이스 설계
|
||||
|
||||
### 1. 코드 카테고리 테이블
|
||||
|
||||
```sql
|
||||
-- 공통코드 카테고리 테이블
|
||||
CREATE TABLE code_category (
|
||||
category_code VARCHAR(50) PRIMARY KEY,
|
||||
category_name VARCHAR(100) NOT NULL,
|
||||
category_name_eng VARCHAR(100),
|
||||
description TEXT,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
is_active CHAR(1) DEFAULT 'Y',
|
||||
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(50),
|
||||
updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_by VARCHAR(50)
|
||||
);
|
||||
|
||||
-- 인덱스 추가
|
||||
CREATE INDEX idx_code_category_active ON code_category(is_active);
|
||||
CREATE INDEX idx_code_category_sort ON code_category(sort_order);
|
||||
```
|
||||
|
||||
### 2. 코드 상세 정보 테이블
|
||||
|
||||
```sql
|
||||
-- 공통코드 상세 정보 테이블
|
||||
CREATE TABLE code_info (
|
||||
code_category VARCHAR(50) NOT NULL,
|
||||
code_value VARCHAR(50) NOT NULL,
|
||||
code_name VARCHAR(100) NOT NULL,
|
||||
code_name_eng VARCHAR(100),
|
||||
description TEXT,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
is_active CHAR(1) DEFAULT 'Y',
|
||||
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by VARCHAR(50),
|
||||
updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_by VARCHAR(50),
|
||||
|
||||
-- 복합 기본키
|
||||
PRIMARY KEY (code_category, code_value),
|
||||
|
||||
-- 외래키 제약조건
|
||||
CONSTRAINT fk_code_info_category
|
||||
FOREIGN KEY (code_category) REFERENCES code_category(category_code)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- 성능을 위한 인덱스
|
||||
CREATE INDEX idx_code_info_category ON code_info(code_category);
|
||||
CREATE INDEX idx_code_info_active ON code_info(is_active);
|
||||
CREATE INDEX idx_code_info_sort ON code_info(code_category, sort_order);
|
||||
```
|
||||
|
||||
### 3. 기본 데이터 삽입
|
||||
|
||||
```sql
|
||||
-- 시스템 필수 공통코드 카테고리
|
||||
INSERT INTO code_category VALUES
|
||||
('USER_STATUS', '사용자 상태', 'User Status', '사용자의 활성화 상태', 1, 'Y', now(), 'SYSTEM', now(), 'SYSTEM'),
|
||||
('USER_TYPE', '사용자 타입', 'User Type', '사용자 권한 타입', 2, 'Y', now(), 'SYSTEM', now(), 'SYSTEM'),
|
||||
('DEPT_TYPE', '부서 타입', 'Department Type', '부서 분류', 3, 'Y', now(), 'SYSTEM', now(), 'SYSTEM'),
|
||||
('LANGUAGE', '언어 코드', 'Language Code', '시스템 지원 언어', 4, 'Y', now(), 'SYSTEM', now(), 'SYSTEM'),
|
||||
('CURRENCY', '통화 코드', 'Currency Code', '시스템 지원 통화', 5, 'Y', now(), 'SYSTEM', now(), 'SYSTEM');
|
||||
|
||||
-- 사용자 상태 코드
|
||||
INSERT INTO code_info VALUES
|
||||
('USER_STATUS', 'A', '활성', 'Active', '활성 사용자', 1, 'Y', now(), 'SYSTEM', now(), 'SYSTEM'),
|
||||
('USER_STATUS', 'I', '비활성', 'Inactive', '비활성 사용자', 2, 'Y', now(), 'SYSTEM', now(), 'SYSTEM'),
|
||||
('USER_STATUS', 'S', '휴면', 'Sleep', '휴면 사용자', 3, 'Y', now(), 'SYSTEM', now(), 'SYSTEM'),
|
||||
('USER_STATUS', 'D', '삭제', 'Deleted', '삭제된 사용자', 4, 'Y', now(), 'SYSTEM', now(), 'SYSTEM');
|
||||
|
||||
-- 사용자 타입 코드
|
||||
INSERT INTO code_info VALUES
|
||||
('USER_TYPE', 'ADMIN', '관리자', 'Administrator', '시스템 관리자', 1, 'Y', now(), 'SYSTEM', now(), 'SYSTEM'),
|
||||
('USER_TYPE', 'USER', '일반사용자', 'User', '일반 사용자', 2, 'Y', now(), 'SYSTEM', now(), 'SYSTEM'),
|
||||
('USER_TYPE', 'GUEST', '게스트', 'Guest', '게스트 사용자', 3, 'Y', now(), 'SYSTEM', now(), 'SYSTEM'),
|
||||
('USER_TYPE', 'PARTNER', '협력업체', 'Partner', '협력업체 사용자', 4, 'Y', now(), 'SYSTEM', now(), 'SYSTEM');
|
||||
```
|
||||
|
||||
## 🎨 화면 구성 요소
|
||||
|
||||
### 1. 전체 레이아웃
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 공통코드 관리 시스템 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ [새 카테고리] [🔍 검색박스] │
|
||||
├─────────────────┬───────────────────────────────────────────┤
|
||||
│ 코드 카테고리 │ 코드 상세 정보 │
|
||||
│ │ │
|
||||
│ ┌─────────────┐ │ ┌─────────────────────────────────────────┐ │
|
||||
│ │ USER_STATUS │ │ │ 코드값 │ 코드명 │ 영문명 │ 순서 │ 상태 │ │
|
||||
│ │ 사용자 상태 │ │ │ A │ 활성 │ Active │ 1 │ ✓ │ │
|
||||
│ │ │ │ │ I │ 비활성 │ Inactive │ 2 │ ✓ │ │
|
||||
│ │ USER_TYPE │ │ │ S │ 휴면 │ Sleep │ 3 │ ✓ │ │
|
||||
│ │ 사용자 타입 │ │ │ D │ 삭제 │ Deleted │ 4 │ ✓ │ │
|
||||
│ │ │ │ └─────────────────────────────────────────┘ │
|
||||
│ │ DEPT_TYPE │ │ │
|
||||
│ │ 부서 타입 │ │ [+ 새 코드 추가] [정렬] │
|
||||
│ └─────────────┘ │ │
|
||||
└─────────────────┴───────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2. 주요 컴포넌트
|
||||
|
||||
#### CodeCategoryList (좌측 패널)
|
||||
|
||||
```typescript
|
||||
interface CodeCategoryListProps {
|
||||
categories: CodeCategory[];
|
||||
selectedCategory: string | null;
|
||||
onCategorySelect: (categoryCode: string) => void;
|
||||
onCategoryCreate: () => void;
|
||||
onCategoryEdit: (category: CodeCategory) => void;
|
||||
onCategoryDelete: (categoryCode: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
#### CodeDetailTable (우측 패널)
|
||||
|
||||
```typescript
|
||||
interface CodeDetailTableProps {
|
||||
categoryCode: string;
|
||||
codes: CodeInfo[];
|
||||
onCodeCreate: () => void;
|
||||
onCodeEdit: (code: CodeInfo) => void;
|
||||
onCodeDelete: (codeValue: string) => void;
|
||||
onSortOrderChange: (codes: CodeInfo[]) => void;
|
||||
}
|
||||
```
|
||||
|
||||
#### CodeEditModal (편집 모달)
|
||||
|
||||
```typescript
|
||||
interface CodeEditModalProps {
|
||||
isOpen: boolean;
|
||||
mode: "create" | "edit";
|
||||
categoryCode?: string;
|
||||
codeData?: CodeInfo;
|
||||
onSave: (data: CodeInfo) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
## 🌐 API 설계
|
||||
|
||||
### 1. 코드 카테고리 API
|
||||
|
||||
#### 카테고리 목록 조회
|
||||
|
||||
```typescript
|
||||
GET /api/common-codes/categories
|
||||
Query: {
|
||||
search?: string;
|
||||
isActive?: boolean;
|
||||
page?: number;
|
||||
size?: number;
|
||||
}
|
||||
Response: {
|
||||
success: boolean;
|
||||
data: CodeCategory[];
|
||||
total: number;
|
||||
}
|
||||
```
|
||||
|
||||
#### 카테고리 생성
|
||||
|
||||
```typescript
|
||||
POST /api/common-codes/categories
|
||||
Body: {
|
||||
categoryCode: string;
|
||||
categoryName: string;
|
||||
categoryNameEng?: string;
|
||||
description?: string;
|
||||
sortOrder?: number;
|
||||
}
|
||||
```
|
||||
|
||||
#### 카테고리 수정
|
||||
|
||||
```typescript
|
||||
PUT /api/common-codes/categories/:categoryCode
|
||||
Body: {
|
||||
categoryName?: string;
|
||||
categoryNameEng?: string;
|
||||
description?: string;
|
||||
sortOrder?: number;
|
||||
isActive?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
#### 카테고리 삭제
|
||||
|
||||
```typescript
|
||||
DELETE /api/common-codes/categories/:categoryCode
|
||||
```
|
||||
|
||||
### 2. 코드 상세 API
|
||||
|
||||
#### 카테고리별 코드 목록 조회
|
||||
|
||||
```typescript
|
||||
GET /api/common-codes/categories/:categoryCode/codes
|
||||
Query: {
|
||||
search?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
Response: {
|
||||
success: boolean;
|
||||
data: CodeInfo[];
|
||||
}
|
||||
```
|
||||
|
||||
#### 코드 생성
|
||||
|
||||
```typescript
|
||||
POST /api/common-codes/categories/:categoryCode/codes
|
||||
Body: {
|
||||
codeValue: string;
|
||||
codeName: string;
|
||||
codeNameEng?: string;
|
||||
description?: string;
|
||||
sortOrder?: number;
|
||||
}
|
||||
```
|
||||
|
||||
#### 코드 수정
|
||||
|
||||
```typescript
|
||||
PUT /api/common-codes/categories/:categoryCode/codes/:codeValue
|
||||
Body: {
|
||||
codeName?: string;
|
||||
codeNameEng?: string;
|
||||
description?: string;
|
||||
sortOrder?: number;
|
||||
isActive?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
#### 코드 삭제
|
||||
|
||||
```typescript
|
||||
DELETE /api/common-codes/categories/:categoryCode/codes/:codeValue
|
||||
```
|
||||
|
||||
#### 코드 순서 변경
|
||||
|
||||
```typescript
|
||||
PUT /api/common-codes/categories/:categoryCode/codes/reorder
|
||||
Body: {
|
||||
codes: Array<{
|
||||
codeValue: string;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 화면관리 연계 API
|
||||
|
||||
#### 카테고리별 옵션 조회 (화면관리용)
|
||||
|
||||
```typescript
|
||||
GET /api/common-codes/categories/:categoryCode/options
|
||||
Response: {
|
||||
success: boolean;
|
||||
data: Array<{
|
||||
value: string;
|
||||
label: string;
|
||||
labelEng?: string;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
## 🎭 프론트엔드 구현
|
||||
|
||||
### 1. 페이지 구조
|
||||
|
||||
```typescript
|
||||
// app/(main)/admin/commonCode/page.tsx
|
||||
export default function CommonCodeManagementPage() {
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
const [categories, setCategories] = useState<CodeCategory[]>([]);
|
||||
const [codes, setCodes] = useState<CodeInfo[]>([]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full">
|
||||
<CodeCategoryPanel
|
||||
categories={categories}
|
||||
selectedCategory={selectedCategory}
|
||||
onCategorySelect={setSelectedCategory}
|
||||
/>
|
||||
<CodeDetailPanel categoryCode={selectedCategory} codes={codes} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 주요 컴포넌트
|
||||
|
||||
#### CodeCategoryPanel
|
||||
|
||||
```typescript
|
||||
// components/admin/commonCode/CodeCategoryPanel.tsx
|
||||
export function CodeCategoryPanel({
|
||||
categories,
|
||||
selectedCategory,
|
||||
onCategorySelect,
|
||||
}: CodeCategoryPanelProps) {
|
||||
return (
|
||||
<div className="w-80 border-r bg-white">
|
||||
<div className="p-4 border-b">
|
||||
<h2 className="font-semibold">코드 카테고리</h2>
|
||||
<Button onClick={onCreateCategory}>
|
||||
<Plus className="w-4 h-4 mr-2" />새 카테고리
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="p-2">
|
||||
{categories.map((category) => (
|
||||
<CategoryItem
|
||||
key={category.categoryCode}
|
||||
category={category}
|
||||
isSelected={selectedCategory === category.categoryCode}
|
||||
onClick={() => onCategorySelect(category.categoryCode)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### CodeDetailPanel
|
||||
|
||||
```typescript
|
||||
// components/admin/commonCode/CodeDetailPanel.tsx
|
||||
export function CodeDetailPanel({ categoryCode, codes }: CodeDetailPanelProps) {
|
||||
if (!categoryCode) {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<p className="text-gray-500">카테고리를 선택하세요</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 p-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-semibold">코드 상세 정보</h2>
|
||||
<Button onClick={onCreateCode}>
|
||||
<Plus className="w-4 h-4 mr-2" />새 코드
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<CodeTable
|
||||
codes={codes}
|
||||
onEdit={handleEditCode}
|
||||
onDelete={handleDeleteCode}
|
||||
onReorder={handleReorderCodes}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## ⚙️ 백엔드 구현
|
||||
|
||||
### 1. Controller
|
||||
|
||||
```typescript
|
||||
// backend-node/src/controllers/commonCodeController.ts
|
||||
export class CommonCodeController {
|
||||
// 카테고리 목록 조회
|
||||
async getCategories(req: Request, res: Response) {
|
||||
try {
|
||||
const { search, isActive, page = 1, size = 20 } = req.query;
|
||||
|
||||
const categories = await this.commonCodeService.getCategories({
|
||||
search: search as string,
|
||||
isActive: isActive === "true",
|
||||
page: Number(page),
|
||||
size: Number(size),
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: categories.data,
|
||||
total: categories.total,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 카테고리별 코드 목록 조회
|
||||
async getCodes(req: Request, res: Response) {
|
||||
try {
|
||||
const { categoryCode } = req.params;
|
||||
const { search, isActive } = req.query;
|
||||
|
||||
const codes = await this.commonCodeService.getCodes(categoryCode, {
|
||||
search: search as string,
|
||||
isActive: isActive === "true",
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: codes,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Service
|
||||
|
||||
```typescript
|
||||
// backend-node/src/services/commonCodeService.ts
|
||||
export class CommonCodeService {
|
||||
// 카테고리 목록 조회
|
||||
async getCategories(params: GetCategoriesParams) {
|
||||
const { search, isActive, page, size } = params;
|
||||
|
||||
let whereClause = "";
|
||||
const queryParams: any[] = [];
|
||||
|
||||
if (search) {
|
||||
whereClause +=
|
||||
" AND (category_name ILIKE $" +
|
||||
(queryParams.length + 1) +
|
||||
" OR category_code ILIKE $" +
|
||||
(queryParams.length + 1) +
|
||||
")";
|
||||
queryParams.push(`%${search}%`);
|
||||
}
|
||||
|
||||
if (isActive !== undefined) {
|
||||
whereClause += " AND is_active = $" + (queryParams.length + 1);
|
||||
queryParams.push(isActive ? "Y" : "N");
|
||||
}
|
||||
|
||||
const offset = (page - 1) * size;
|
||||
|
||||
const [categories, total] = await Promise.all([
|
||||
this.db.query(
|
||||
`
|
||||
SELECT * FROM code_category
|
||||
WHERE 1=1 ${whereClause}
|
||||
ORDER BY sort_order, category_code
|
||||
LIMIT $${queryParams.length + 1} OFFSET $${queryParams.length + 2}
|
||||
`,
|
||||
[...queryParams, size, offset]
|
||||
),
|
||||
|
||||
this.db.query(
|
||||
`
|
||||
SELECT COUNT(*) as count FROM code_category
|
||||
WHERE 1=1 ${whereClause}
|
||||
`,
|
||||
queryParams
|
||||
),
|
||||
]);
|
||||
|
||||
return {
|
||||
data: categories.rows,
|
||||
total: parseInt(total.rows[0].count),
|
||||
};
|
||||
}
|
||||
|
||||
// 코드 목록 조회
|
||||
async getCodes(categoryCode: string, params: GetCodesParams) {
|
||||
const { search, isActive } = params;
|
||||
|
||||
let whereClause = "WHERE code_category = $1";
|
||||
const queryParams: any[] = [categoryCode];
|
||||
|
||||
if (search) {
|
||||
whereClause +=
|
||||
" AND (code_name ILIKE $" +
|
||||
(queryParams.length + 1) +
|
||||
" OR code_value ILIKE $" +
|
||||
(queryParams.length + 1) +
|
||||
")";
|
||||
queryParams.push(`%${search}%`);
|
||||
}
|
||||
|
||||
if (isActive !== undefined) {
|
||||
whereClause += " AND is_active = $" + (queryParams.length + 1);
|
||||
queryParams.push(isActive ? "Y" : "N");
|
||||
}
|
||||
|
||||
const result = await this.db.query(
|
||||
`
|
||||
SELECT * FROM code_info
|
||||
${whereClause}
|
||||
ORDER BY sort_order, code_value
|
||||
`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
return result.rows;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎬 사용 시나리오
|
||||
|
||||
### 1. 새로운 공통코드 카테고리 생성
|
||||
|
||||
1. **관리자 메뉴 접근**: 관리자가 공통코드 관리 메뉴 클릭
|
||||
2. **카테고리 생성**: "새 카테고리" 버튼 클릭
|
||||
3. **정보 입력**: 카테고리 코드, 이름, 설명 입력
|
||||
4. **저장**: 카테고리 생성 완료
|
||||
5. **코드 추가**: 생성된 카테고리에 코드 상세 정보 추가
|
||||
|
||||
### 2. 기존 공통코드 수정
|
||||
|
||||
1. **카테고리 선택**: 좌측 패널에서 수정할 카테고리 선택
|
||||
2. **코드 선택**: 우측 테이블에서 수정할 코드 클릭
|
||||
3. **정보 수정**: 코드명, 영문명, 설명 등 수정
|
||||
4. **순서 변경**: 드래그앤드롭으로 표시 순서 조정
|
||||
5. **저장**: 변경사항 저장
|
||||
|
||||
### 3. 화면관리 시스템 연계
|
||||
|
||||
1. **테이블 타입 관리**: column_labels에서 web_type을 'code'로 설정
|
||||
2. **카테고리 선택**: code_category에 공통코드 카테고리 지정
|
||||
3. **화면 설계**: 화면관리에서 해당 컬럼을 드래그하여 위젯 생성
|
||||
4. **자동 옵션**: select 위젯에 공통코드 옵션 자동 생성
|
||||
5. **실시간 반영**: 공통코드 변경 시 화면에 즉시 반영
|
||||
|
||||
## 📅 개발 계획 및 진행상황
|
||||
|
||||
### ✅ Phase 1: 기본 구조 및 데이터베이스 (완료)
|
||||
|
||||
- [x] 데이터베이스 스키마 설계 및 생성
|
||||
- [x] Prisma 스키마에 공통코드 모델 추가
|
||||
- [x] code_category, code_info 테이블 생성
|
||||
- [x] 기본 데이터 삽입 스크립트 작성 및 실행
|
||||
|
||||
**완료 내용:**
|
||||
|
||||
- Prisma schema.prisma에 공통코드 모델 추가
|
||||
- Docker 컨테이너에서 테이블 생성 스크립트 실행
|
||||
- 5개 카테고리, 22개 기본 코드 데이터 삽입 완료
|
||||
|
||||
### ⏳ Phase 2: 백엔드 API 구현 (예정)
|
||||
|
||||
- [ ] CommonCodeController 구현
|
||||
- [ ] CommonCodeService 구현
|
||||
- [ ] 카테고리 CRUD API 구현
|
||||
- [ ] 코드 상세 CRUD API 구현
|
||||
- [ ] 정렬 순서 변경 API 구현
|
||||
|
||||
**목표 기간**: 2일
|
||||
|
||||
### ⏳ Phase 3: 프론트엔드 기본 구현 (예정)
|
||||
|
||||
- [ ] 공통코드 관리 페이지 생성
|
||||
- [ ] CodeCategoryPanel 컴포넌트 구현
|
||||
- [ ] CodeDetailPanel 컴포넌트 구현
|
||||
- [ ] 기본 CRUD 기능 구현
|
||||
|
||||
**목표 기간**: 2일
|
||||
|
||||
### ⏳ Phase 4: 고급 기능 구현 (예정)
|
||||
|
||||
- [ ] 드래그앤드롭 정렬 기능
|
||||
- [ ] 검색 및 필터링 기능
|
||||
- [ ] 일괄 업로드/다운로드 기능
|
||||
- [ ] 코드 편집 모달 구현
|
||||
|
||||
**목표 기간**: 2일
|
||||
|
||||
### ⏳ Phase 5: 화면관리 연계 (예정)
|
||||
|
||||
- [ ] column_labels와 연동 확인
|
||||
- [ ] 화면관리에서 code 타입 위젯 테스트
|
||||
- [ ] 공통코드 변경 시 화면 반영 테스트
|
||||
- [ ] 옵션 조회 API 구현
|
||||
|
||||
**목표 기간**: 1일
|
||||
|
||||
### ⏳ Phase 6: 테스트 및 최적화 (예정)
|
||||
|
||||
- [ ] 전체 기능 통합 테스트
|
||||
- [ ] 성능 최적화
|
||||
- [ ] 사용자 경험 개선
|
||||
- [ ] 문서화 및 사용자 가이드
|
||||
|
||||
**목표 기간**: 1일
|
||||
|
||||
## 🎯 현재 구현 상태
|
||||
|
||||
### 📊 **전체 진행률: 0%**
|
||||
|
||||
- ⏳ **Phase 1**: 기본 구조 및 데이터베이스 (0%)
|
||||
- ⏳ **Phase 2**: 백엔드 API 구현 (0%)
|
||||
- ⏳ **Phase 3**: 프론트엔드 기본 구현 (0%)
|
||||
- ⏳ **Phase 4**: 고급 기능 구현 (0%)
|
||||
- ⏳ **Phase 5**: 화면관리 연계 (0%)
|
||||
- ⏳ **Phase 6**: 테스트 및 최적화 (0%)
|
||||
|
||||
## 🚀 기대 효과
|
||||
|
||||
### 1. 개발 효율성 향상
|
||||
|
||||
- 공통코드 중앙 관리로 중복 작업 제거
|
||||
- 화면관리 시스템과 연계로 자동 위젯 생성
|
||||
|
||||
### 2. 데이터 일관성 보장
|
||||
|
||||
- 모든 시스템에서 동일한 코드값 사용
|
||||
- 코드 변경 시 전체 시스템 일괄 반영
|
||||
|
||||
### 3. 유지보수성 향상
|
||||
|
||||
- 코드 변경이 필요할 때 한 곳에서만 수정
|
||||
- 체계적인 코드 분류 및 관리
|
||||
|
||||
### 4. 사용자 경험 개선
|
||||
|
||||
- 일관된 선택 옵션 제공
|
||||
- 다국어 지원으로 글로벌 대응
|
||||
|
||||
**공통코드 관리 시스템을 통해 전체 ERP 시스템의 품질과 효율성을 크게 향상시킬 수 있습니다!** 🎉
|
||||
Loading…
Reference in New Issue