Files
wace_rps/backend-node/src/services/ecrTableMigration.ts
T
chpark 690b85805c ECR 기능/스키마 wace_plm 일치 + 공통코드·테이블타입 화면 정리
- ECR 관리: wace 의 ecrList/Form/Detail JSP 와 동일하게 5개 필터(연도/기종/요청/작성자/상태),
  변경전/후 2분할 모달, 작성중(0000100)만 삭제·수정 허용, 컬럼 순서/라벨 wace 일치
- ECR 스키마 wace 풀세트 동기화: ecr_mng 컬럼폭 확장, product_mgmt 16컬럼, part_mng 52컬럼,
  user_info 22컬럼, comm_code 보강(id/code_cd/ext_val), code_name(varchar) 함수,
  seq_ecr_no setval(33) 정렬
- wace_plm public.comm_code 733행 시드: src/seed/wace_comm_code.sql 추출 + 부팅 시 자동 적재
  (writer='system-seed' placeholder 자동 정리, 무중단 재적재 엔드포인트 /comm-code-seed)
- wace_plm 데이터 import 풀스키마: PRODUCT(7→16), PART(6→52), USER_INFO·COMM_CODE 신규
- 공통코드 관리 화면: 제목/설명 축소, 카테고리·코드 카드 → 컴팩트 리스트, 활성 토글 점,
  계층 배지 톤다운, hover 시 액션 노출
- 테이블 타입 관리 — 좌측: 한 줄 리스트 + 알파벳 인덱스 sticky 헤더
- 테이블 타입 관리 — 우측: 타입 카드 그리드 → 그룹 셀렉트(기본/참조/자동/첨부/표시변형),
  표시이름 제거 + 코멘트(description) Textarea 신설(화면관리에서 기본 라벨로 활용),
  시스템 자동 생성 컬럼(id/company_code/writer/created_date/updated_date) 잠금,
  표시옵션/고급설정(필수·읽기·기본값·최대길이) 제거 — 화면관리로 이관

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:58:54 +09:00

311 lines
19 KiB
TypeScript

/**
* ECR(Engineering Change Request) 관련 테이블 idempotent 마이그레이션
*
* - wace_plm 의 ECR_MNG / PRODUCT_MGMT / PART_MNG / COMM_CODE / USER_INFO 스키마와
* 1:1 매칭되도록 컬럼 명세를 동기화 (CREATE TABLE IF NOT EXISTS + ADD COLUMN IF NOT EXISTS)
* - wace_plm 의 code_name(varchar) 함수도 그대로 재현 (mapper SQL 에서 사용)
* - 시퀀스 seq_ecr_no 도 동일 보장
* - 부팅 시 1회 실행 (멱등)
*
* 주의: 데이터 자체의 import 는 WacePlmDataImportService (POST /api/wace-import/*) 가 담당.
* 본 마이그레이션은 "동일 스키마" 보장만 한다.
*/
import { getPool } from "../database/db";
import { logger } from "../utils/logger";
const STATEMENTS: string[] = [
// ────────────────────────────────────────────────────────────────────────────
// 1) ECR_MNG (wace_plm dbexport 기준 컬럼 명세와 정확히 일치)
//
// wace_plm DDL:
// objid integer NOT NULL, ecr_no varchar(100), product_objid integer,
// upg_no varchar(100), part_objid integer, request_cd varchar(100),
// title varchar(1000), writer varchar(100), status_cd varchar(100),
// before_contents varchar(4000), after_contents varchar(4000),
// reg_date timestamp, check_user_id varchar(100), check_date timestamp
// ────────────────────────────────────────────────────────────────────────────
`CREATE TABLE IF NOT EXISTS ecr_mng (
objid BIGINT PRIMARY KEY,
ecr_no VARCHAR(100),
product_objid BIGINT,
upg_no VARCHAR(100),
part_objid BIGINT,
request_cd VARCHAR(100),
title VARCHAR(1000),
writer VARCHAR(100),
status_cd VARCHAR(100) DEFAULT '0000100',
before_contents VARCHAR(4000),
after_contents VARCHAR(4000),
reg_date TIMESTAMP DEFAULT NOW(),
check_user_id VARCHAR(100),
check_date TIMESTAMP
)`,
// 기존 환경(이전 마이그레이션으로 짧은 길이로 만들어진 경우)을 위해 컬럼 폭 확장 (멱등)
`ALTER TABLE ecr_mng ALTER COLUMN ecr_no TYPE VARCHAR(100) USING ecr_no::VARCHAR(100)`,
`ALTER TABLE ecr_mng ALTER COLUMN upg_no TYPE VARCHAR(100) USING upg_no::VARCHAR(100)`,
`ALTER TABLE ecr_mng ALTER COLUMN request_cd TYPE VARCHAR(100) USING request_cd::VARCHAR(100)`,
`ALTER TABLE ecr_mng ALTER COLUMN title TYPE VARCHAR(1000) USING title::VARCHAR(1000)`,
`ALTER TABLE ecr_mng ALTER COLUMN writer TYPE VARCHAR(100) USING writer::VARCHAR(100)`,
`ALTER TABLE ecr_mng ALTER COLUMN status_cd TYPE VARCHAR(100) USING status_cd::VARCHAR(100)`,
`ALTER TABLE ecr_mng ALTER COLUMN before_contents TYPE VARCHAR(4000) USING before_contents::VARCHAR(4000)`,
`ALTER TABLE ecr_mng ALTER COLUMN after_contents TYPE VARCHAR(4000) USING after_contents::VARCHAR(4000)`,
`ALTER TABLE ecr_mng ALTER COLUMN check_user_id TYPE VARCHAR(100) USING check_user_id::VARCHAR(100)`,
// 신규로 만든 환경에 company_code 가 떨어졌을 수 있어 제거 시도 없이 그냥 두되, 누락된 컬럼만 보충
`ALTER TABLE ecr_mng ADD COLUMN IF NOT EXISTS upg_no VARCHAR(100)`,
`ALTER TABLE ecr_mng ADD COLUMN IF NOT EXISTS check_user_id VARCHAR(100)`,
`ALTER TABLE ecr_mng ADD COLUMN IF NOT EXISTS check_date TIMESTAMP`,
`CREATE INDEX IF NOT EXISTS idx_ecr_mng_status ON ecr_mng (status_cd)`,
`CREATE INDEX IF NOT EXISTS idx_ecr_mng_writer ON ecr_mng (writer)`,
`CREATE INDEX IF NOT EXISTS idx_ecr_mng_reg_date ON ecr_mng (reg_date DESC)`,
// ECR_NO 시퀀스 — wace_plm 도 동일하게 별도 SEQUENCE 사용
`CREATE SEQUENCE IF NOT EXISTS seq_ecr_no START WITH 1 INCREMENT BY 1`,
// wace_plm 운영본은 33 까지 발번되어 있음. 우리 seq 가 더 작으면 그 값까지 끌어올려서
// 채번 충돌(ON CONFLICT 로 UPDATE 분기 타는 사고)을 방지.
`SELECT setval('seq_ecr_no', GREATEST((SELECT last_value FROM seq_ecr_no), 33))`,
// ────────────────────────────────────────────────────────────────────────────
// 2) PRODUCT_MGMT (wace_plm dbexport 컬럼 풀세트)
// ────────────────────────────────────────────────────────────────────────────
`CREATE TABLE IF NOT EXISTS product_mgmt (
objid BIGINT PRIMARY KEY,
product_category VARCHAR(100),
product_type VARCHAR(100),
product_grade VARCHAR(100),
product_ton VARCHAR(100),
product_boom VARCHAR(100),
product_vehicle VARCHAR(100),
product_code VARCHAR(100),
production_flag VARCHAR(100),
regdate TIMESTAMP,
writer VARCHAR(100),
contents TEXT,
price VARCHAR,
product_name VARCHAR,
product_name_code VARCHAR,
note VARCHAR
)`,
// 기존 좁은 컬럼이면 폭 확장
`ALTER TABLE product_mgmt ALTER COLUMN product_code TYPE VARCHAR(100) USING product_code::VARCHAR(100)`,
`ALTER TABLE product_mgmt ALTER COLUMN product_type TYPE VARCHAR(100) USING product_type::VARCHAR(100)`,
`ALTER TABLE product_mgmt ALTER COLUMN writer TYPE VARCHAR(100) USING writer::VARCHAR(100)`,
// 누락 컬럼 보충 (이전 마이그레이션은 일부만 만들었음)
`ALTER TABLE product_mgmt ADD COLUMN IF NOT EXISTS product_category VARCHAR(100)`,
`ALTER TABLE product_mgmt ADD COLUMN IF NOT EXISTS product_grade VARCHAR(100)`,
`ALTER TABLE product_mgmt ADD COLUMN IF NOT EXISTS product_ton VARCHAR(100)`,
`ALTER TABLE product_mgmt ADD COLUMN IF NOT EXISTS product_boom VARCHAR(100)`,
`ALTER TABLE product_mgmt ADD COLUMN IF NOT EXISTS product_vehicle VARCHAR(100)`,
`ALTER TABLE product_mgmt ADD COLUMN IF NOT EXISTS contents TEXT`,
`ALTER TABLE product_mgmt ADD COLUMN IF NOT EXISTS price VARCHAR`,
`ALTER TABLE product_mgmt ADD COLUMN IF NOT EXISTS product_name_code VARCHAR`,
`ALTER TABLE product_mgmt ADD COLUMN IF NOT EXISTS note VARCHAR`,
`CREATE INDEX IF NOT EXISTS idx_product_mgmt_code ON product_mgmt (product_code)`,
// ────────────────────────────────────────────────────────────────────────────
// 3) PART_MNG (wace_plm dbexport 컬럼 풀세트 — 50+ 컬럼)
// ────────────────────────────────────────────────────────────────────────────
`CREATE TABLE IF NOT EXISTS part_mng (
objid BIGINT PRIMARY KEY,
product_mgmt_objid VARCHAR(100),
upg_no VARCHAR(100),
part_no VARCHAR(100),
part_name VARCHAR(100),
unit VARCHAR(50),
qty VARCHAR(50),
spec VARCHAR(100),
material VARCHAR(100),
weight VARCHAR(50),
part_type VARCHAR(100),
remark VARCHAR(1000),
es_spec VARCHAR(100),
ms_spec VARCHAR(100),
change_option VARCHAR(50),
design_apply_point VARCHAR(50),
management_flag VARCHAR(50),
revision VARCHAR(50),
status VARCHAR(30),
reg_date TIMESTAMP,
edit_date TIMESTAMP,
writer VARCHAR(30),
is_last VARCHAR(5),
eo_no VARCHAR,
eo_temp VARCHAR,
excel_upload_seq INTEGER,
sourcing_code VARCHAR,
sub_material VARCHAR(100),
parent_part_no VARCHAR,
design_date VARCHAR,
eo_date VARCHAR,
deploy_date TIMESTAMP,
thickness VARCHAR,
width VARCHAR,
height VARCHAR,
out_diameter VARCHAR,
in_diameter VARCHAR,
length VARCHAR,
supply_code VARCHAR,
change_type VARCHAR,
contract_objid VARCHAR,
maker VARCHAR,
post_processing VARCHAR,
material_code VARCHAR,
code1 VARCHAR,
code2 VARCHAR,
code3 VARCHAR,
code4 VARCHAR,
code5 VARCHAR,
major_category VARCHAR,
sub_category VARCHAR,
is_new VARCHAR(5),
is_longd VARCHAR(5)
)`,
// 기존 part_mng 가 작은 스키마였다면 누락 컬럼 보충 (멱등 ADD COLUMN IF NOT EXISTS)
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS product_mgmt_objid VARCHAR(100)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS upg_no VARCHAR(100)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS unit VARCHAR(50)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS qty VARCHAR(50)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS spec VARCHAR(100)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS material VARCHAR(100)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS weight VARCHAR(50)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS remark VARCHAR(1000)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS es_spec VARCHAR(100)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS ms_spec VARCHAR(100)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS change_option VARCHAR(50)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS design_apply_point VARCHAR(50)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS management_flag VARCHAR(50)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS revision VARCHAR(50)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS reg_date TIMESTAMP`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS edit_date TIMESTAMP`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS is_last VARCHAR(5)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS eo_no VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS eo_temp VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS excel_upload_seq INTEGER`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS sourcing_code VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS sub_material VARCHAR(100)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS parent_part_no VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS design_date VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS eo_date VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS deploy_date TIMESTAMP`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS thickness VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS width VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS height VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS out_diameter VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS in_diameter VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS length VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS supply_code VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS change_type VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS contract_objid VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS maker VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS post_processing VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS material_code VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS code1 VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS code2 VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS code3 VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS code4 VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS code5 VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS major_category VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS sub_category VARCHAR`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS is_new VARCHAR(5)`,
`ALTER TABLE part_mng ADD COLUMN IF NOT EXISTS is_longd VARCHAR(5)`,
`CREATE INDEX IF NOT EXISTS idx_part_mng_no ON part_mng (part_no)`,
// ────────────────────────────────────────────────────────────────────────────
// 4) COMM_CODE (wace_plm 명세 보충 — 우리 시스템은 이미 일부 컬럼만 가졌을 수 있음)
// ────────────────────────────────────────────────────────────────────────────
`ALTER TABLE comm_code ADD COLUMN IF NOT EXISTS id VARCHAR(100)`,
`ALTER TABLE comm_code ADD COLUMN IF NOT EXISTS code_cd VARCHAR(100)`,
`ALTER TABLE comm_code ADD COLUMN IF NOT EXISTS ext_val VARCHAR(10)`,
// ────────────────────────────────────────────────────────────────────────────
// 5) USER_INFO (wace_plm 의 USER_INFO 와 동일하게 만들지만, 우리 user 테이블과는 별도)
// - mapper SQL 이 USER_INFO 를 직접 참조 (writer_name, check_name 서브쿼리)
// - import 시 wace_plm.USER_INFO → 우리 user_info 에 그대로 적재
// ────────────────────────────────────────────────────────────────────────────
`CREATE TABLE IF NOT EXISTS user_info (
sabun VARCHAR(1024),
user_id VARCHAR(1024) PRIMARY KEY,
user_password VARCHAR(1024),
user_name VARCHAR(1024),
user_name_eng VARCHAR(1024),
user_name_cn VARCHAR(1024),
dept_code VARCHAR(1024),
dept_name VARCHAR(1024),
position_code VARCHAR(1024),
position_name VARCHAR(1024),
email VARCHAR(1024),
tel VARCHAR(1024),
cell_phone VARCHAR(1024),
user_type VARCHAR(1024),
user_type_name VARCHAR(1024),
regdate TIMESTAMP,
data_type VARCHAR(64),
status VARCHAR(32),
end_date TIMESTAMP,
fax_no VARCHAR,
partner_objid VARCHAR,
rank VARCHAR
)`,
// ────────────────────────────────────────────────────────────────────────────
// 6) public.code_name(varchar) 함수
// wace_plm mapper 들이 ECR 등에서 `code_name(STATUS_CD)` 호출 — 동일 함수 정의
// ────────────────────────────────────────────────────────────────────────────
`CREATE OR REPLACE FUNCTION public.code_name(v_code_id varchar) RETURNS varchar
LANGUAGE plpgsql AS $$
DECLARE v_code_name varchar;
BEGIN
SELECT code_name INTO v_code_name FROM comm_code WHERE code_id = v_code_id LIMIT 1;
RETURN v_code_name;
END;
$$`,
// ────────────────────────────────────────────────────────────────────────────
// 7) 공통코드 시드 — 0000090(설변요청) / 0000099(ECR상태)
// ────────────────────────────────────────────────────────────────────────────
`DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM comm_code WHERE code_id='0000090') THEN
INSERT INTO comm_code (objid, code_id, parent_code_id, code_name, sort_order, status, regdate, writer)
VALUES (extract(epoch FROM now())::BIGINT, '0000090', NULL, '설변요청 코드', 0, 'active', NOW(), 'system-seed');
END IF;
IF NOT EXISTS (SELECT 1 FROM comm_code WHERE parent_code_id='0000090') THEN
INSERT INTO comm_code (objid, code_id, parent_code_id, code_name, sort_order, status, regdate, writer) VALUES
(extract(epoch FROM now())::BIGINT + 1, '0090001', '0000090', '설계오류', 1, 'active', NOW(), 'system-seed'),
(extract(epoch FROM now())::BIGINT + 2, '0090002', '0000090', '품질개선', 2, 'active', NOW(), 'system-seed'),
(extract(epoch FROM now())::BIGINT + 3, '0090003', '0000090', '원가절감', 3, 'active', NOW(), 'system-seed'),
(extract(epoch FROM now())::BIGINT + 4, '0090004', '0000090', '고객요청', 4, 'active', NOW(), 'system-seed'),
(extract(epoch FROM now())::BIGINT + 5, '0090005', '0000090', '법규대응', 5, 'active', NOW(), 'system-seed'),
(extract(epoch FROM now())::BIGINT + 6, '0090006', '0000090', '부품단종', 6, 'active', NOW(), 'system-seed');
END IF;
END $$`,
// 0000099 / 자식(0000100=작성중, 0000101=결재중, 0000107=반려, 0000102=적용완료) 는 wace_plm
// 원본에 그대로 존재하므로 별도 시드하지 않는다 (wace_comm_code.sql 시드 로더가 처리).
// wace 가 미보유한 0000090(설변요청) 만 위에서 시드.
// ────────────────────────────────────────────────────────────────────────────
// 8) ECR 메뉴 활성화 (기존 menu_info 100045 정규화)
// ────────────────────────────────────────────────────────────────────────────
`UPDATE menu_info SET menu_url = '/COMPANY_16/ecr/ecr', status = 'active'
WHERE objid = '100045' AND (menu_url IS NULL OR menu_url = '')`,
];
export async function ensureEcrTables(): Promise<void> {
const pool = getPool();
let success = 0;
let failed = 0;
for (const stmt of STATEMENTS) {
try {
await pool.query(stmt);
success++;
} catch (e: any) {
failed++;
logger.warn(`[ecrMigration] 실패 (계속 진행): ${e?.message?.slice(0, 200)}`);
}
}
logger.info(`🌱 ECR 마이그레이션 완료: 성공 ${success}건 / 실패 ${failed}`);
}