feat: 엑셀 업로드 템플릿 시스템 (V19)
Agent Teams 3명 병렬 작업 (dba / api / frontend) 결과물.
DB (V19__업로드템플릿.sql):
- upload_template / upload_template_column / upload_history 3 테이블
- 인덱스 5종 (category / company_code / template_id / started_at)
- SYSTEM_UPLOAD_TEMPLATE 메뉴 + READ/CREATE/UPDATE/DELETE 권한
- 모든 역할에 권한 부여 (MANAGER 는 READ 만)
Backend:
- ga-core: VO 7개 (Template/Column/History의 VO/Resp/SaveReq/SearchParam) +
Mapper 3개 + XML 3개 (UploadTemplate/Column/History)
- ga-api: TransformEngine (12 변환 규칙: TRIM/UPPER/LOWER/DATE/DIVIDE/MULTIPLY/
LOOKUP/CODE_MAP/FIXED/REPLACE/SUBSTRING/CONCAT/DEFAULT) +
LookupCache (O(1) HashMap 캐시) + UploadService (processUpload/preview) +
LookupMapper / DynamicInsertMapper (whitelist 기반 동적 SQL)
- ga-api Controller 2개:
· UploadTemplateController: /api/upload-templates CRUD + columns + history
· UploadController: POST /api/upload/{templateCode}, /preview
Frontend:
- api/uploadTemplate.ts (10 endpoints)
- pages/system/UploadTemplateManager.tsx — 좌(템플릿 목록) 우(컬럼 매핑 편집기)
+ ModalForm 등록/수정 + 미리보기 Modal + 이력 Drawer
- components/common/TemplateUpload.tsx — 범용 컴포넌트 (Select 템플릿 + Dragger
+ 진행률 + 결과 Modal + 에러 파일 다운로드)
- App.tsx 라우트 추가, MenuIcon.tsx 매핑 추가
검증:
- ./gradlew :ga-api:bootJar 통과
- Flyway V19 운영 DB 적용 성공 (200ms)
- ga-api 부팅 7.8s, /api/upload-templates 200 응답
- 메뉴 트리에 SYSTEM_UPLOAD_TEMPLATE 노출 확인
- 프론트 tsc -b / vite build 통과
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
-- V19: 엑셀 업로드 매핑 템플릿 시스템
|
||||
|
||||
-- 1. upload_template (마스터)
|
||||
CREATE TABLE upload_template (
|
||||
template_id BIGSERIAL PRIMARY KEY,
|
||||
template_code VARCHAR(50) NOT NULL UNIQUE,
|
||||
template_name VARCHAR(100) NOT NULL,
|
||||
category VARCHAR(30) NOT NULL, -- RECRUIT/MAINTAIN/CONTRACT/AGENT/CUSTOM
|
||||
target_table VARCHAR(50) NOT NULL,
|
||||
company_code VARCHAR(20), -- NULL이면 범용
|
||||
header_row INT NOT NULL DEFAULT 1,
|
||||
data_start_row INT NOT NULL DEFAULT 2,
|
||||
sheet_index INT NOT NULL DEFAULT 0,
|
||||
sheet_name VARCHAR(50),
|
||||
file_encoding VARCHAR(20) DEFAULT 'UTF-8',
|
||||
delimiter VARCHAR(5),
|
||||
skip_empty_row CHAR(1) DEFAULT 'Y',
|
||||
max_error_count INT DEFAULT 1000,
|
||||
batch_size INT DEFAULT 1000,
|
||||
description VARCHAR(200),
|
||||
is_active CHAR(1) DEFAULT 'Y',
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
created_by BIGINT,
|
||||
updated_at TIMESTAMP,
|
||||
updated_by BIGINT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_upload_tpl_category ON upload_template(category);
|
||||
CREATE INDEX idx_upload_tpl_company ON upload_template(company_code) WHERE company_code IS NOT NULL;
|
||||
|
||||
-- 2. upload_template_column (컬럼 매핑 규칙)
|
||||
CREATE TABLE upload_template_column (
|
||||
column_id BIGSERIAL PRIMARY KEY,
|
||||
template_id BIGINT NOT NULL REFERENCES upload_template(template_id) ON DELETE CASCADE,
|
||||
sort_order INT NOT NULL,
|
||||
source_type VARCHAR(10) NOT NULL DEFAULT 'COLUMN', -- COLUMN/FIXED/EXPRESSION
|
||||
source_column VARCHAR(10), -- 엑셀 컬럼 (A,B,C 또는 0,1,2)
|
||||
source_header VARCHAR(100),
|
||||
target_field VARCHAR(50) NOT NULL,
|
||||
target_type VARCHAR(20) NOT NULL DEFAULT 'STRING', -- STRING/NUMBER/DECIMAL/DATE/INTEGER
|
||||
transform_rule VARCHAR(30), -- TRIM/UPPER/LOWER/DATE/DIVIDE/MULTIPLY/LOOKUP/CODE_MAP/FIXED/REPLACE/SUBSTRING/CONCAT/DEFAULT/REQUIRED
|
||||
transform_param VARCHAR(200), -- JSON 또는 단순값
|
||||
date_format VARCHAR(30),
|
||||
number_divide INT,
|
||||
number_multiply INT,
|
||||
fixed_value VARCHAR(200),
|
||||
default_value VARCHAR(200),
|
||||
lookup_table VARCHAR(50),
|
||||
lookup_source_field VARCHAR(50),
|
||||
lookup_target_field VARCHAR(50),
|
||||
code_map_group VARCHAR(50),
|
||||
is_required CHAR(1) DEFAULT 'N',
|
||||
validation_regex VARCHAR(200),
|
||||
validation_message VARCHAR(200),
|
||||
description VARCHAR(200),
|
||||
UNIQUE (template_id, sort_order)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_upload_tpl_col ON upload_template_column(template_id);
|
||||
|
||||
-- 3. upload_history (업로드 이력)
|
||||
CREATE TABLE upload_history (
|
||||
history_id BIGSERIAL PRIMARY KEY,
|
||||
template_id BIGINT NOT NULL REFERENCES upload_template(template_id),
|
||||
template_code VARCHAR(50) NOT NULL,
|
||||
file_name VARCHAR(300) NOT NULL,
|
||||
file_size BIGINT,
|
||||
total_count INT NOT NULL DEFAULT 0,
|
||||
success_count INT NOT NULL DEFAULT 0,
|
||||
error_count INT NOT NULL DEFAULT 0,
|
||||
skip_count INT NOT NULL DEFAULT 0,
|
||||
error_file_id BIGINT, -- file_storage 느슨한 참조 (제약 없음)
|
||||
status VARCHAR(10) NOT NULL DEFAULT 'PROCESSING', -- PROCESSING/COMPLETED/FAILED/CANCELLED
|
||||
error_message TEXT,
|
||||
started_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
finished_at TIMESTAMP,
|
||||
duration_sec INT,
|
||||
created_by BIGINT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_upload_history_tpl ON upload_history(template_id, started_at DESC);
|
||||
CREATE INDEX idx_upload_history_started ON upload_history(started_at DESC);
|
||||
|
||||
-- 4. 메뉴 등록 (GRP_SYSTEM 하위, sort_order 7)
|
||||
INSERT INTO menu (menu_code, menu_name, menu_type, menu_path, component_path, menu_icon,
|
||||
menu_level, sort_order, is_visible, is_active, parent_menu_id, description)
|
||||
VALUES ('SYSTEM_UPLOAD_TEMPLATE', '엑셀 업로드 템플릿', 'PAGE', '/system/upload-templates',
|
||||
'system/UploadTemplateManager', 'file-spreadsheet', 2, 7, 'Y', 'Y',
|
||||
(SELECT menu_id FROM menu WHERE menu_code = 'GRP_SYSTEM'),
|
||||
'보험사별 엑셀 업로드 양식 매핑 관리')
|
||||
ON CONFLICT (menu_code) DO NOTHING;
|
||||
|
||||
-- 5. 메뉴 권한 4건 정의
|
||||
INSERT INTO menu_permission (menu_id, perm_code, perm_name, sort_order)
|
||||
SELECT menu_id, 'READ', '조회', 1 FROM menu WHERE menu_code = 'SYSTEM_UPLOAD_TEMPLATE'
|
||||
ON CONFLICT (menu_id, perm_code) DO NOTHING;
|
||||
|
||||
INSERT INTO menu_permission (menu_id, perm_code, perm_name, sort_order)
|
||||
SELECT menu_id, 'CREATE', '등록', 2 FROM menu WHERE menu_code = 'SYSTEM_UPLOAD_TEMPLATE'
|
||||
ON CONFLICT (menu_id, perm_code) DO NOTHING;
|
||||
|
||||
INSERT INTO menu_permission (menu_id, perm_code, perm_name, sort_order)
|
||||
SELECT menu_id, 'UPDATE', '수정', 3 FROM menu WHERE menu_code = 'SYSTEM_UPLOAD_TEMPLATE'
|
||||
ON CONFLICT (menu_id, perm_code) DO NOTHING;
|
||||
|
||||
INSERT INTO menu_permission (menu_id, perm_code, perm_name, sort_order)
|
||||
SELECT menu_id, 'DELETE', '삭제', 4 FROM menu WHERE menu_code = 'SYSTEM_UPLOAD_TEMPLATE'
|
||||
ON CONFLICT (menu_id, perm_code) DO NOTHING;
|
||||
|
||||
-- 6. SUPER_ADMIN / ADMIN : READ + CREATE + UPDATE + DELETE
|
||||
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
|
||||
SELECT r.role_id, m.menu_id, p.perm_code, 'Y'
|
||||
FROM role r
|
||||
CROSS JOIN menu m
|
||||
CROSS JOIN (VALUES ('READ'), ('CREATE'), ('UPDATE'), ('DELETE')) AS p(perm_code)
|
||||
WHERE r.role_code IN ('SUPER_ADMIN', 'ADMIN')
|
||||
AND m.menu_code = 'SYSTEM_UPLOAD_TEMPLATE'
|
||||
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
|
||||
|
||||
-- 7. MANAGER : READ 만
|
||||
INSERT INTO role_menu_permission (role_id, menu_id, perm_code, is_granted)
|
||||
SELECT r.role_id, m.menu_id, 'READ', 'Y'
|
||||
FROM role r
|
||||
CROSS JOIN menu m
|
||||
WHERE r.role_code = 'MANAGER'
|
||||
AND m.menu_code = 'SYSTEM_UPLOAD_TEMPLATE'
|
||||
ON CONFLICT (role_id, menu_id, perm_code) DO NOTHING;
|
||||
Reference in New Issue
Block a user