공통코드 원복
This commit is contained in:
parent
b01efd293c
commit
6e9cbccf47
|
|
@ -275,22 +275,8 @@ export class CommonCodeController {
|
||||||
const { categoryCode } = req.params;
|
const { categoryCode } = req.params;
|
||||||
const codeData: CreateCodeData = req.body;
|
const codeData: CreateCodeData = req.body;
|
||||||
const userId = req.user?.userId || "SYSTEM";
|
const userId = req.user?.userId || "SYSTEM";
|
||||||
// 사용자의 회사 코드 사용 (회사별 코드 관리)
|
|
||||||
const companyCode = req.user?.companyCode || "*";
|
const companyCode = req.user?.companyCode || "*";
|
||||||
|
const menuObjid = req.body.menuObjid;
|
||||||
// 카테고리 조회 - 존재 여부 확인 및 menuObjid 가져오기
|
|
||||||
const category = await this.commonCodeService.getCategoryByCode(categoryCode);
|
|
||||||
if (!category) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: `카테고리를 찾을 수 없습니다: ${categoryCode}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 카테고리의 menuObjid 사용 (카테고리는 공통, 코드는 회사별)
|
|
||||||
const menuObjid = category.menu_objid;
|
|
||||||
|
|
||||||
logger.info(`코드 생성 - 사용자 회사로 저장: categoryCode=${categoryCode}, companyCode=${companyCode}, menuObjid=${menuObjid}`);
|
|
||||||
|
|
||||||
// 입력값 검증
|
// 입력값 검증
|
||||||
if (!codeData.codeValue || !codeData.codeName) {
|
if (!codeData.codeValue || !codeData.codeName) {
|
||||||
|
|
@ -300,6 +286,13 @@ export class CommonCodeController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!menuObjid) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: "메뉴 OBJID는 필수입니다.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const code = await this.commonCodeService.createCode(
|
const code = await this.commonCodeService.createCode(
|
||||||
categoryCode,
|
categoryCode,
|
||||||
codeData,
|
codeData,
|
||||||
|
|
@ -595,100 +588,4 @@ export class CommonCodeController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 테이블.컬럼 기반 카테고리 옵션 조회 (레거시 호환용)
|
|
||||||
* GET /api/common-codes/category-options/:tableName/:columnName
|
|
||||||
*
|
|
||||||
* 기존 카테고리 타입이 공통코드로 마이그레이션된 후에도
|
|
||||||
* 기존 API 호출이 동작하도록 호환성 제공
|
|
||||||
*/
|
|
||||||
async getCategoryOptionsAsCode(req: AuthenticatedRequest, res: Response) {
|
|
||||||
try {
|
|
||||||
const { tableName, columnName } = req.params;
|
|
||||||
const userCompanyCode = req.user?.companyCode;
|
|
||||||
|
|
||||||
logger.info(`카테고리 → 공통코드 호환 조회: ${tableName}.${columnName}`);
|
|
||||||
|
|
||||||
const options = await this.commonCodeService.getCategoryOptionsAsCode(
|
|
||||||
tableName,
|
|
||||||
columnName,
|
|
||||||
userCompanyCode
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.json({
|
|
||||||
success: true,
|
|
||||||
data: options,
|
|
||||||
message: `카테고리 옵션 조회 성공 (${tableName}.${columnName})`,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`카테고리 옵션 조회 실패 (${req.params.tableName}.${req.params.columnName}):`, error);
|
|
||||||
return res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: "카테고리 옵션 조회 중 오류가 발생했습니다.",
|
|
||||||
error: error instanceof Error ? error.message : "Unknown error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 계층 구조 코드 조회 (트리 형태)
|
|
||||||
* GET /api/common-codes/categories/:categoryCode/hierarchy
|
|
||||||
*/
|
|
||||||
async getCodesHierarchy(req: AuthenticatedRequest, res: Response) {
|
|
||||||
try {
|
|
||||||
const { categoryCode } = req.params;
|
|
||||||
const userCompanyCode = req.user?.companyCode;
|
|
||||||
|
|
||||||
const hierarchy = await this.commonCodeService.getCodesHierarchy(
|
|
||||||
categoryCode,
|
|
||||||
userCompanyCode
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.json({
|
|
||||||
success: true,
|
|
||||||
data: hierarchy,
|
|
||||||
message: `계층 코드 조회 성공 (${categoryCode})`,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`계층 코드 조회 실패 (${req.params.categoryCode}):`, error);
|
|
||||||
return res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: "계층 코드 조회 중 오류가 발생했습니다.",
|
|
||||||
error: error instanceof Error ? error.message : "Unknown error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 자식 코드 조회 (연쇄 선택용)
|
|
||||||
* GET /api/common-codes/categories/:categoryCode/children
|
|
||||||
* Query: parentCodeValue (optional)
|
|
||||||
*/
|
|
||||||
async getChildCodes(req: AuthenticatedRequest, res: Response) {
|
|
||||||
try {
|
|
||||||
const { categoryCode } = req.params;
|
|
||||||
const { parentCodeValue } = req.query;
|
|
||||||
const userCompanyCode = req.user?.companyCode;
|
|
||||||
|
|
||||||
const children = await this.commonCodeService.getChildCodes(
|
|
||||||
categoryCode,
|
|
||||||
parentCodeValue as string | null,
|
|
||||||
userCompanyCode
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.json({
|
|
||||||
success: true,
|
|
||||||
data: children,
|
|
||||||
message: `자식 코드 조회 성공 (${categoryCode}, 부모: ${parentCodeValue || '최상위'})`,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`자식 코드 조회 실패 (${req.params.categoryCode}):`, error);
|
|
||||||
return res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: "자식 코드 조회 중 오류가 발생했습니다.",
|
|
||||||
error: error instanceof Error ? error.message : "Unknown error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,6 @@ export interface CodeInfo {
|
||||||
is_active: string;
|
is_active: string;
|
||||||
company_code: string;
|
company_code: string;
|
||||||
menu_objid?: number | null; // 메뉴 기반 코드 관리용
|
menu_objid?: number | null; // 메뉴 기반 코드 관리용
|
||||||
// 계층 구조 지원
|
|
||||||
parent_code_value?: string | null; // 부모 코드 값
|
|
||||||
depth?: number; // 계층 깊이 (1: 최상위)
|
|
||||||
created_date?: Date | null;
|
created_date?: Date | null;
|
||||||
created_by?: string | null;
|
created_by?: string | null;
|
||||||
updated_date?: Date | null;
|
updated_date?: Date | null;
|
||||||
|
|
@ -64,9 +61,6 @@ export interface CreateCodeData {
|
||||||
description?: string;
|
description?: string;
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
isActive?: string;
|
isActive?: string;
|
||||||
// 계층 구조 지원
|
|
||||||
parentCodeValue?: string;
|
|
||||||
depth?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommonCodeService {
|
export class CommonCodeService {
|
||||||
|
|
@ -154,22 +148,6 @@ export class CommonCodeService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 특정 카테고리 조회 (코드로)
|
|
||||||
*/
|
|
||||||
async getCategoryByCode(categoryCode: string): Promise<CodeCategory | null> {
|
|
||||||
try {
|
|
||||||
const category = await queryOne<CodeCategory>(
|
|
||||||
`SELECT * FROM code_category WHERE category_code = $1`,
|
|
||||||
[categoryCode]
|
|
||||||
);
|
|
||||||
return category || null;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`카테고리 조회 실패 (${categoryCode}):`, error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 카테고리별 코드 목록 조회
|
* 카테고리별 코드 목록 조회
|
||||||
*/
|
*/
|
||||||
|
|
@ -214,12 +192,11 @@ export class CommonCodeService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 회사별 필터링 (최고 관리자가 아닌 경우)
|
// 회사별 필터링 (최고 관리자가 아닌 경우)
|
||||||
// company_code = '*'인 공통 데이터도 함께 조회
|
|
||||||
if (userCompanyCode && userCompanyCode !== "*") {
|
if (userCompanyCode && userCompanyCode !== "*") {
|
||||||
whereConditions.push(`(company_code = $${paramIndex} OR company_code = '*')`);
|
whereConditions.push(`company_code = $${paramIndex}`);
|
||||||
values.push(userCompanyCode);
|
values.push(userCompanyCode);
|
||||||
paramIndex++;
|
paramIndex++;
|
||||||
logger.info(`회사별 코드 필터링: ${userCompanyCode} (공통 데이터 포함)`);
|
logger.info(`회사별 코드 필터링: ${userCompanyCode}`);
|
||||||
} else if (userCompanyCode === "*") {
|
} else if (userCompanyCode === "*") {
|
||||||
logger.info(`최고 관리자: 모든 코드 조회`);
|
logger.info(`최고 관리자: 모든 코드 조회`);
|
||||||
}
|
}
|
||||||
|
|
@ -428,22 +405,11 @@ export class CommonCodeService {
|
||||||
menuObjid: number
|
menuObjid: number
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
// 계층 구조 깊이 계산
|
|
||||||
let depth = data.depth || 1;
|
|
||||||
if (data.parentCodeValue && !data.depth) {
|
|
||||||
// 부모 코드의 depth를 조회하여 +1
|
|
||||||
const parentCode = await queryOne<{ depth: number }>(
|
|
||||||
`SELECT depth FROM code_info WHERE code_category = $1 AND code_value = $2`,
|
|
||||||
[categoryCode, data.parentCodeValue]
|
|
||||||
);
|
|
||||||
depth = (parentCode?.depth || 0) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const code = await queryOne<CodeInfo>(
|
const code = await queryOne<CodeInfo>(
|
||||||
`INSERT INTO code_info
|
`INSERT INTO code_info
|
||||||
(code_category, code_value, code_name, code_name_eng, description, sort_order,
|
(code_category, code_value, code_name, code_name_eng, description, sort_order,
|
||||||
is_active, menu_objid, company_code, parent_code_value, depth, created_by, updated_by, created_date, updated_date)
|
is_active, menu_objid, company_code, created_by, updated_by, created_date, updated_date)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, 'Y', $7, $8, $9, $10, $11, $12, NOW(), NOW())
|
VALUES ($1, $2, $3, $4, $5, $6, 'Y', $7, $8, $9, $10, NOW(), NOW())
|
||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[
|
[
|
||||||
categoryCode,
|
categoryCode,
|
||||||
|
|
@ -454,15 +420,13 @@ export class CommonCodeService {
|
||||||
data.sortOrder || 0,
|
data.sortOrder || 0,
|
||||||
menuObjid,
|
menuObjid,
|
||||||
companyCode,
|
companyCode,
|
||||||
data.parentCodeValue || null,
|
|
||||||
depth,
|
|
||||||
createdBy,
|
createdBy,
|
||||||
createdBy,
|
createdBy,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`코드 생성 완료: ${categoryCode}.${data.codeValue} (메뉴: ${menuObjid}, 회사: ${companyCode}, 부모: ${data.parentCodeValue || '없음'}, depth: ${depth})`
|
`코드 생성 완료: ${categoryCode}.${data.codeValue} (메뉴: ${menuObjid}, 회사: ${companyCode})`
|
||||||
);
|
);
|
||||||
return code;
|
return code;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -527,25 +491,6 @@ export class CommonCodeService {
|
||||||
updateFields.push(`is_active = $${paramIndex++}`);
|
updateFields.push(`is_active = $${paramIndex++}`);
|
||||||
values.push(activeValue);
|
values.push(activeValue);
|
||||||
}
|
}
|
||||||
// 계층 구조 필드
|
|
||||||
if (data.parentCodeValue !== undefined) {
|
|
||||||
updateFields.push(`parent_code_value = $${paramIndex++}`);
|
|
||||||
values.push(data.parentCodeValue || null);
|
|
||||||
|
|
||||||
// depth 자동 계산
|
|
||||||
if (data.parentCodeValue) {
|
|
||||||
const parentCode = await queryOne<{ depth: number }>(
|
|
||||||
`SELECT depth FROM code_info WHERE code_category = $1 AND code_value = $2`,
|
|
||||||
[categoryCode, data.parentCodeValue]
|
|
||||||
);
|
|
||||||
const newDepth = (parentCode?.depth || 0) + 1;
|
|
||||||
updateFields.push(`depth = $${paramIndex++}`);
|
|
||||||
values.push(newDepth);
|
|
||||||
} else {
|
|
||||||
updateFields.push(`depth = $${paramIndex++}`);
|
|
||||||
values.push(1); // 부모 없으면 최상위
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WHERE 절 구성
|
// WHERE 절 구성
|
||||||
let whereClause = `WHERE code_category = $${paramIndex++} AND code_value = $${paramIndex}`;
|
let whereClause = `WHERE code_category = $${paramIndex++} AND code_value = $${paramIndex}`;
|
||||||
|
|
@ -649,164 +594,6 @@ export class CommonCodeService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 계층 구조로 코드 조회 (트리 형태)
|
|
||||||
*/
|
|
||||||
async getCodesHierarchy(
|
|
||||||
categoryCode: string,
|
|
||||||
userCompanyCode?: string
|
|
||||||
): Promise<any[]> {
|
|
||||||
try {
|
|
||||||
let sql = `
|
|
||||||
SELECT code_value, code_name, code_name_eng, description, sort_order,
|
|
||||||
is_active, parent_code_value, depth
|
|
||||||
FROM code_info
|
|
||||||
WHERE code_category = $1 AND is_active = 'Y'
|
|
||||||
`;
|
|
||||||
const values: any[] = [categoryCode];
|
|
||||||
|
|
||||||
// 회사별 필터링
|
|
||||||
if (userCompanyCode && userCompanyCode !== "*") {
|
|
||||||
sql += ` AND company_code = $2`;
|
|
||||||
values.push(userCompanyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
sql += ` ORDER BY depth ASC, sort_order ASC, code_value ASC`;
|
|
||||||
|
|
||||||
const codes = await query<{
|
|
||||||
code_value: string;
|
|
||||||
code_name: string;
|
|
||||||
code_name_eng: string | null;
|
|
||||||
description: string | null;
|
|
||||||
sort_order: number;
|
|
||||||
is_active: string;
|
|
||||||
parent_code_value: string | null;
|
|
||||||
depth: number;
|
|
||||||
}>(sql, values);
|
|
||||||
|
|
||||||
// 트리 구조로 변환
|
|
||||||
const codeMap = new Map<string, any>();
|
|
||||||
const roots: any[] = [];
|
|
||||||
|
|
||||||
// 모든 코드를 맵에 저장
|
|
||||||
for (const code of codes) {
|
|
||||||
codeMap.set(code.code_value, {
|
|
||||||
value: code.code_value,
|
|
||||||
label: code.code_name,
|
|
||||||
labelEng: code.code_name_eng,
|
|
||||||
description: code.description,
|
|
||||||
depth: code.depth || 1,
|
|
||||||
parentValue: code.parent_code_value,
|
|
||||||
children: [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 부모-자식 관계 구성
|
|
||||||
for (const code of codes) {
|
|
||||||
const node = codeMap.get(code.code_value);
|
|
||||||
if (code.parent_code_value && codeMap.has(code.parent_code_value)) {
|
|
||||||
const parent = codeMap.get(code.parent_code_value);
|
|
||||||
parent.children.push(node);
|
|
||||||
} else {
|
|
||||||
roots.push(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
`계층 코드 조회 완료: ${categoryCode} - ${roots.length}개 루트, 전체 ${codes.length}개`
|
|
||||||
);
|
|
||||||
return roots;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`계층 코드 조회 중 오류 (${categoryCode}):`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 특정 부모 코드의 자식 코드 목록 조회 (연쇄 선택용)
|
|
||||||
*/
|
|
||||||
async getChildCodes(
|
|
||||||
categoryCode: string,
|
|
||||||
parentCodeValue: string | null,
|
|
||||||
userCompanyCode?: string
|
|
||||||
): Promise<Array<{ value: string; label: string; hasChildren: boolean }>> {
|
|
||||||
try {
|
|
||||||
let sql = `
|
|
||||||
SELECT
|
|
||||||
c.code_value,
|
|
||||||
c.code_name,
|
|
||||||
c.code_name_eng,
|
|
||||||
EXISTS(SELECT 1 FROM code_info c2 WHERE c2.parent_code_value = c.code_value AND c2.code_category = c.code_category) as has_children
|
|
||||||
FROM code_info c
|
|
||||||
WHERE c.code_category = $1
|
|
||||||
AND c.is_active = 'Y'
|
|
||||||
`;
|
|
||||||
const values: any[] = [categoryCode];
|
|
||||||
let paramIndex = 2;
|
|
||||||
|
|
||||||
// 부모 코드 필터
|
|
||||||
if (parentCodeValue) {
|
|
||||||
sql += ` AND c.parent_code_value = $${paramIndex++}`;
|
|
||||||
values.push(parentCodeValue);
|
|
||||||
} else {
|
|
||||||
sql += ` AND (c.parent_code_value IS NULL OR c.depth = 1)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 회사별 필터링
|
|
||||||
if (userCompanyCode && userCompanyCode !== "*") {
|
|
||||||
sql += ` AND c.company_code = $${paramIndex++}`;
|
|
||||||
values.push(userCompanyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
sql += ` ORDER BY c.sort_order ASC, c.code_value ASC`;
|
|
||||||
|
|
||||||
const codes = await query<{
|
|
||||||
code_value: string;
|
|
||||||
code_name: string;
|
|
||||||
code_name_eng: string | null;
|
|
||||||
has_children: boolean;
|
|
||||||
}>(sql, values);
|
|
||||||
|
|
||||||
const result = codes.map((code) => ({
|
|
||||||
value: code.code_value,
|
|
||||||
label: code.code_name,
|
|
||||||
hasChildren: code.has_children,
|
|
||||||
}));
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
`자식 코드 조회 완료: ${categoryCode} (부모: ${parentCodeValue || '최상위'}) - ${result.length}개`
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`자식 코드 조회 중 오류 (${categoryCode}, ${parentCodeValue}):`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 테이블.컬럼 기반 카테고리 옵션 조회 (레거시 카테고리 호환용)
|
|
||||||
*
|
|
||||||
* 마이그레이션 후 category 타입이 code로 변환되었을 때
|
|
||||||
* 기존 `tableName_columnName` 형식의 codeGroup으로 조회
|
|
||||||
*
|
|
||||||
* @param tableName 테이블명
|
|
||||||
* @param columnName 컬럼명
|
|
||||||
* @param userCompanyCode 회사 코드
|
|
||||||
*/
|
|
||||||
async getCategoryOptionsAsCode(tableName: string, columnName: string, userCompanyCode?: string) {
|
|
||||||
try {
|
|
||||||
// 카테고리 코드 그룹명 생성: TABLENAME_COLUMNNAME
|
|
||||||
const categoryCode = `${tableName.toUpperCase()}_${columnName.toUpperCase()}`;
|
|
||||||
|
|
||||||
logger.info(`카테고리 → 코드 호환 조회: ${tableName}.${columnName} → ${categoryCode}`);
|
|
||||||
|
|
||||||
return await this.getCodeOptions(categoryCode, userCompanyCode);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`카테고리 호환 조회 중 오류 (${tableName}.${columnName}):`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 코드 순서 변경
|
* 코드 순서 변경
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
||||||
|
|
||||||
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
|
||||||
import { ValidationMessage } from "@/components/common/ValidationMessage";
|
import { ValidationMessage } from "@/components/common/ValidationMessage";
|
||||||
|
|
@ -84,9 +83,6 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
|
||||||
// 폼 스키마 선택 (생성/수정에 따라)
|
// 폼 스키마 선택 (생성/수정에 따라)
|
||||||
const schema = isEditing ? updateCodeSchema : createCodeSchema;
|
const schema = isEditing ? updateCodeSchema : createCodeSchema;
|
||||||
|
|
||||||
// 부모 코드 선택 상태
|
|
||||||
const [parentCodeValue, setParentCodeValue] = useState<string>("");
|
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
mode: "onChange", // 실시간 검증 활성화
|
mode: "onChange", // 실시간 검증 활성화
|
||||||
|
|
@ -115,9 +111,6 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
|
||||||
|
|
||||||
// codeValue는 별도로 설정 (표시용)
|
// codeValue는 별도로 설정 (표시용)
|
||||||
form.setValue("codeValue" as any, editingCode.codeValue || editingCode.code_value);
|
form.setValue("codeValue" as any, editingCode.codeValue || editingCode.code_value);
|
||||||
|
|
||||||
// 부모 코드 설정
|
|
||||||
setParentCodeValue(editingCode.parentCodeValue || editingCode.parent_code_value || "");
|
|
||||||
} else {
|
} else {
|
||||||
// 새 코드 모드: 자동 순서 계산
|
// 새 코드 모드: 자동 순서 계산
|
||||||
const maxSortOrder = codes.length > 0 ? Math.max(...codes.map((c) => c.sortOrder || c.sort_order)) : 0;
|
const maxSortOrder = codes.length > 0 ? Math.max(...codes.map((c) => c.sortOrder || c.sort_order)) : 0;
|
||||||
|
|
@ -129,7 +122,6 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
|
||||||
description: "",
|
description: "",
|
||||||
sortOrder: maxSortOrder + 1,
|
sortOrder: maxSortOrder + 1,
|
||||||
});
|
});
|
||||||
setParentCodeValue("");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isOpen, isEditing, editingCode, codes]);
|
}, [isOpen, isEditing, editingCode, codes]);
|
||||||
|
|
@ -137,29 +129,22 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
try {
|
try {
|
||||||
if (isEditing && editingCode) {
|
if (isEditing && editingCode) {
|
||||||
// 수정 - 부모 코드 포함
|
// 수정
|
||||||
await updateCodeMutation.mutateAsync({
|
await updateCodeMutation.mutateAsync({
|
||||||
categoryCode,
|
categoryCode,
|
||||||
codeValue: editingCode.codeValue || editingCode.code_value,
|
codeValue: editingCode.codeValue || editingCode.code_value,
|
||||||
data: {
|
data: data as UpdateCodeData,
|
||||||
...data,
|
|
||||||
parentCodeValue: parentCodeValue || undefined,
|
|
||||||
} as UpdateCodeData,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 생성 - 부모 코드 포함
|
// 생성
|
||||||
await createCodeMutation.mutateAsync({
|
await createCodeMutation.mutateAsync({
|
||||||
categoryCode,
|
categoryCode,
|
||||||
data: {
|
data: data as CreateCodeData,
|
||||||
...data,
|
|
||||||
parentCodeValue: parentCodeValue || undefined,
|
|
||||||
} as CreateCodeData,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
form.reset();
|
form.reset();
|
||||||
setParentCodeValue("");
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("코드 저장 실패:", error);
|
console.error("코드 저장 실패:", error);
|
||||||
}
|
}
|
||||||
|
|
@ -284,43 +269,6 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 부모 코드 (계층 구조) */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="parentCodeValue" className="text-xs sm:text-sm">부모 코드 (선택)</Label>
|
|
||||||
<Select
|
|
||||||
value={parentCodeValue || "_none_"}
|
|
||||||
onValueChange={(val) => setParentCodeValue(val === "_none_" ? "" : val)}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
|
|
||||||
<SelectValue placeholder="부모 코드 선택 (최상위면 비워두세요)" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="_none_">없음 (최상위)</SelectItem>
|
|
||||||
{codes
|
|
||||||
.filter((c) => {
|
|
||||||
// 자기 자신은 제외
|
|
||||||
const currentCodeValue = editingCode?.codeValue || editingCode?.code_value;
|
|
||||||
const codeValue = c.codeValue || c.code_value;
|
|
||||||
return codeValue !== currentCodeValue;
|
|
||||||
})
|
|
||||||
.map((c) => {
|
|
||||||
const codeValue = c.codeValue || c.code_value;
|
|
||||||
if (!codeValue) return null;
|
|
||||||
return (
|
|
||||||
<SelectItem key={codeValue} value={codeValue}>
|
|
||||||
{c.depth && c.depth > 1 ? "└".repeat(c.depth - 1) + " " : ""}
|
|
||||||
{c.codeName || c.code_name} ({codeValue})
|
|
||||||
</SelectItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<p className="text-[10px] sm:text-xs text-muted-foreground">
|
|
||||||
계층 구조가 필요한 경우 부모 코드를 선택하세요.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 정렬 순서 */}
|
{/* 정렬 순서 */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="sortOrder" className="text-xs sm:text-sm">정렬 순서</Label>
|
<Label htmlFor="sortOrder" className="text-xs sm:text-sm">정렬 순서</Label>
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ import {
|
||||||
CodeCategory,
|
CodeCategory,
|
||||||
CodeInfo,
|
CodeInfo,
|
||||||
CodeOption,
|
CodeOption,
|
||||||
CodeHierarchyNode,
|
|
||||||
CodeChildOption,
|
|
||||||
CreateCategoryRequest,
|
CreateCategoryRequest,
|
||||||
UpdateCategoryRequest,
|
UpdateCategoryRequest,
|
||||||
CreateCodeRequest,
|
CreateCodeRequest,
|
||||||
|
|
@ -168,34 +166,4 @@ export const commonCodeApi = {
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// 계층 구조 API
|
|
||||||
hierarchy: {
|
|
||||||
/**
|
|
||||||
* 계층 구조 코드 조회 (트리 형태)
|
|
||||||
*/
|
|
||||||
async getTree(categoryCode: string): Promise<ApiResponse<CodeHierarchyNode[]>> {
|
|
||||||
const response = await apiClient.get(`/common-codes/categories/${categoryCode}/hierarchy`);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 자식 코드 조회 (연쇄 선택용)
|
|
||||||
* @param categoryCode 카테고리 코드
|
|
||||||
* @param parentCodeValue 부모 코드 값 (null이면 최상위)
|
|
||||||
*/
|
|
||||||
async getChildren(
|
|
||||||
categoryCode: string,
|
|
||||||
parentCodeValue?: string | null
|
|
||||||
): Promise<ApiResponse<CodeChildOption[]>> {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
if (parentCodeValue) {
|
|
||||||
params.append("parentCodeValue", parentCodeValue);
|
|
||||||
}
|
|
||||||
const queryString = params.toString();
|
|
||||||
const url = `/common-codes/categories/${categoryCode}/children${queryString ? `?${queryString}` : ""}`;
|
|
||||||
const response = await apiClient.get(url);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,6 @@ export interface CodeInfo {
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
isActive?: string | boolean;
|
isActive?: string | boolean;
|
||||||
useYn?: string;
|
useYn?: string;
|
||||||
// 계층 구조 필드
|
|
||||||
parentCodeValue?: string | null;
|
|
||||||
depth?: number;
|
|
||||||
|
|
||||||
// 기존 필드 (하위 호환성을 위해 유지)
|
// 기존 필드 (하위 호환성을 위해 유지)
|
||||||
code_category?: string;
|
code_category?: string;
|
||||||
|
|
@ -36,7 +33,6 @@ export interface CodeInfo {
|
||||||
code_name_eng?: string | null;
|
code_name_eng?: string | null;
|
||||||
sort_order?: number;
|
sort_order?: number;
|
||||||
is_active?: string;
|
is_active?: string;
|
||||||
parent_code_value?: string | null;
|
|
||||||
created_date?: string | null;
|
created_date?: string | null;
|
||||||
created_by?: string | null;
|
created_by?: string | null;
|
||||||
updated_date?: string | null;
|
updated_date?: string | null;
|
||||||
|
|
@ -65,9 +61,6 @@ export interface CreateCodeRequest {
|
||||||
codeNameEng?: string;
|
codeNameEng?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
// 계층 구조 필드
|
|
||||||
parentCodeValue?: string;
|
|
||||||
depth?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateCodeRequest {
|
export interface UpdateCodeRequest {
|
||||||
|
|
@ -76,8 +69,6 @@ export interface UpdateCodeRequest {
|
||||||
description?: string;
|
description?: string;
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
isActive?: "Y" | "N"; // 백엔드에서 기대하는 문자열 타입
|
isActive?: "Y" | "N"; // 백엔드에서 기대하는 문자열 타입
|
||||||
// 계층 구조 필드
|
|
||||||
parentCodeValue?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CodeOption {
|
export interface CodeOption {
|
||||||
|
|
@ -86,24 +77,6 @@ export interface CodeOption {
|
||||||
labelEng?: string | null;
|
labelEng?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 계층 구조 트리 노드
|
|
||||||
export interface CodeHierarchyNode {
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
labelEng?: string | null;
|
|
||||||
description?: string | null;
|
|
||||||
depth: number;
|
|
||||||
parentValue?: string | null;
|
|
||||||
children: CodeHierarchyNode[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 자식 코드 (연쇄 선택용)
|
|
||||||
export interface CodeChildOption {
|
|
||||||
value: string;
|
|
||||||
label: string;
|
|
||||||
hasChildren: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReorderCodesRequest {
|
export interface ReorderCodesRequest {
|
||||||
codes: Array<{
|
codes: Array<{
|
||||||
codeValue: string;
|
codeValue: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue