ERP-node/backend-node/src/controllers/commonCodeController.ts

719 lines
22 KiB
TypeScript

import { Request, Response } from "express";
import {
CommonCodeService,
CreateCategoryData,
CreateCodeData,
} from "../services/commonCodeService";
import { AuthenticatedRequest } from "../types/auth";
import { logger } from "../utils/logger";
export class CommonCodeController {
private commonCodeService: CommonCodeService;
constructor() {
this.commonCodeService = new CommonCodeService();
}
/**
* 카테고리 목록 조회
* GET /api/common-codes/categories
*/
async getCategories(req: AuthenticatedRequest, res: Response) {
try {
const { search, isActive, page = "1", size = "20", menuObjid } = req.query;
const userCompanyCode = req.user?.companyCode;
const menuObjidNum = menuObjid ? Number(menuObjid) : undefined;
const categories = await this.commonCodeService.getCategories(
{
search: search as string,
isActive:
isActive === "true"
? true
: isActive === "false"
? false
: undefined,
page: parseInt(page as string),
size: parseInt(size as string),
},
userCompanyCode,
menuObjidNum
);
return res.json({
success: true,
data: categories.data,
total: categories.total,
message: "카테고리 목록 조회 성공",
});
} catch (error) {
logger.error("카테고리 목록 조회 실패:", error);
return res.status(500).json({
success: false,
message: "카테고리 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
/**
* 카테고리별 코드 목록 조회
* GET /api/common-codes/categories/:categoryCode/codes
*/
async getCodes(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode } = req.params;
const { search, isActive, page, size, menuObjid } = req.query;
const userCompanyCode = req.user?.companyCode;
const menuObjidNum = menuObjid ? Number(menuObjid) : undefined;
const result = await this.commonCodeService.getCodes(
categoryCode,
{
search: search as string,
isActive:
isActive === "true"
? true
: isActive === "false"
? false
: undefined,
page: page ? parseInt(page as string) : undefined,
size: size ? parseInt(size as string) : undefined,
},
userCompanyCode,
menuObjidNum
);
// 프론트엔드가 기대하는 형식으로 데이터 변환
const transformedData = result.data.map((code: any) => ({
// 새로운 필드명 (카멜케이스)
codeValue: code.code_value,
codeName: code.code_name,
codeNameEng: code.code_name_eng,
description: code.description,
sortOrder: code.sort_order,
isActive: code.is_active,
useYn: code.is_active,
companyCode: code.company_code,
parentCodeValue: code.parent_code_value, // 계층구조: 부모 코드값
depth: code.depth, // 계층구조: 깊이
// 기존 필드명도 유지 (하위 호환성)
code_category: code.code_category,
code_value: code.code_value,
code_name: code.code_name,
code_name_eng: code.code_name_eng,
sort_order: code.sort_order,
is_active: code.is_active,
company_code: code.company_code,
parent_code_value: code.parent_code_value, // 계층구조: 부모 코드값
// depth는 위에서 이미 정의됨 (snake_case와 camelCase 동일)
created_date: code.created_date,
created_by: code.created_by,
updated_date: code.updated_date,
updated_by: code.updated_by,
}));
return res.json({
success: true,
data: transformedData,
total: result.total,
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",
});
}
}
/**
* 카테고리 생성
* POST /api/common-codes/categories
*/
async createCategory(req: AuthenticatedRequest, res: Response) {
try {
const categoryData: CreateCategoryData = req.body;
const userId = req.user?.userId || "SYSTEM";
const companyCode = req.user?.companyCode || "*";
const menuObjid = req.body.menuObjid;
// 입력값 검증
if (!categoryData.categoryCode || !categoryData.categoryName) {
return res.status(400).json({
success: false,
message: "카테고리 코드와 이름은 필수입니다.",
});
}
if (!menuObjid) {
return res.status(400).json({
success: false,
message: "메뉴 OBJID는 필수입니다.",
});
}
const category = await this.commonCodeService.createCategory(
categoryData,
userId,
companyCode,
Number(menuObjid)
);
return res.status(201).json({
success: true,
data: category,
message: "카테고리 생성 성공",
});
} catch (error) {
logger.error("카테고리 생성 실패:", error);
// PostgreSQL 에러 처리
if (
(error as any)?.code === "23505" || // PostgreSQL unique_violation
(error instanceof Error && error.message.includes("Unique constraint"))
) {
return res.status(409).json({
success: false,
message: "이미 존재하는 카테고리 코드입니다.",
});
}
return res.status(500).json({
success: false,
message: "카테고리 생성 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
/**
* 카테고리 수정
* PUT /api/common-codes/categories/:categoryCode
*/
async updateCategory(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode } = req.params;
const categoryData: Partial<CreateCategoryData> = req.body;
const userId = req.user?.userId || "SYSTEM";
const companyCode = req.user?.companyCode;
const category = await this.commonCodeService.updateCategory(
categoryCode,
categoryData,
userId,
companyCode
);
return res.json({
success: true,
data: category,
message: "카테고리 수정 성공",
});
} catch (error) {
logger.error(`카테고리 수정 실패 (${req.params.categoryCode}):`, error);
if (
error instanceof Error &&
error.message.includes("Record to update not found")
) {
return res.status(404).json({
success: false,
message: "존재하지 않는 카테고리입니다.",
});
}
return res.status(500).json({
success: false,
message: "카테고리 수정 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
/**
* 카테고리 삭제
* DELETE /api/common-codes/categories/:categoryCode
*/
async deleteCategory(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode } = req.params;
const companyCode = req.user?.companyCode;
await this.commonCodeService.deleteCategory(categoryCode, companyCode);
return res.json({
success: true,
message: "카테고리 삭제 성공",
});
} catch (error) {
logger.error(`카테고리 삭제 실패 (${req.params.categoryCode}):`, error);
if (
error instanceof Error &&
error.message.includes("Record to delete does not exist")
) {
return res.status(404).json({
success: false,
message: "존재하지 않는 카테고리입니다.",
});
}
return res.status(500).json({
success: false,
message: "카테고리 삭제 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
/**
* 코드 생성
* POST /api/common-codes/categories/:categoryCode/codes
*/
async createCode(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode } = req.params;
const codeData: CreateCodeData = req.body;
const userId = req.user?.userId || "SYSTEM";
const companyCode = req.user?.companyCode || "*";
const menuObjid = req.body.menuObjid;
// 입력값 검증
if (!codeData.codeValue || !codeData.codeName) {
return res.status(400).json({
success: false,
message: "코드값과 코드명은 필수입니다.",
});
}
// menuObjid가 없으면 공통코드관리 메뉴의 기본 OBJID 사용 (전역 코드)
// 공통코드관리 메뉴 OBJID: 1757401858940
const DEFAULT_CODE_MANAGEMENT_MENU_OBJID = 1757401858940;
const effectiveMenuObjid = menuObjid ? Number(menuObjid) : DEFAULT_CODE_MANAGEMENT_MENU_OBJID;
const code = await this.commonCodeService.createCode(
categoryCode,
codeData,
userId,
companyCode,
effectiveMenuObjid
);
return res.status(201).json({
success: true,
data: code,
message: "코드 생성 성공",
});
} catch (error) {
logger.error(`코드 생성 실패 (${req.params.categoryCode}):`, error);
if (
error instanceof Error &&
error.message.includes("Unique constraint")
) {
return res.status(409).json({
success: false,
message: "이미 존재하는 코드값입니다.",
});
}
return res.status(500).json({
success: false,
message: "코드 생성 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
/**
* 코드 수정
* PUT /api/common-codes/categories/:categoryCode/codes/:codeValue
*/
async updateCode(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode, codeValue } = req.params;
const codeData: Partial<CreateCodeData> = req.body;
const userId = req.user?.userId || "SYSTEM";
const companyCode = req.user?.companyCode;
const code = await this.commonCodeService.updateCode(
categoryCode,
codeValue,
codeData,
userId,
companyCode
);
return res.json({
success: true,
data: code,
message: "코드 수정 성공",
});
} catch (error) {
logger.error(
`코드 수정 실패 (${req.params.categoryCode}.${req.params.codeValue}):`,
error
);
if (
error instanceof Error &&
error.message.includes("Record to update not found")
) {
return res.status(404).json({
success: false,
message: "존재하지 않는 코드입니다.",
});
}
return res.status(500).json({
success: false,
message: "코드 수정 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
/**
* 코드 삭제
* DELETE /api/common-codes/categories/:categoryCode/codes/:codeValue
*/
async deleteCode(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode, codeValue } = req.params;
const companyCode = req.user?.companyCode;
await this.commonCodeService.deleteCode(
categoryCode,
codeValue,
companyCode
);
return res.json({
success: true,
message: "코드 삭제 성공",
});
} catch (error) {
logger.error(
`코드 삭제 실패 (${req.params.categoryCode}.${req.params.codeValue}):`,
error
);
if (
error instanceof Error &&
error.message.includes("Record to delete does not exist")
) {
return res.status(404).json({
success: false,
message: "존재하지 않는 코드입니다.",
});
}
return res.status(500).json({
success: false,
message: "코드 삭제 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
/**
* 카테고리별 옵션 조회 (화면관리용)
* GET /api/common-codes/categories/:categoryCode/options
*/
async getCodeOptions(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode } = req.params;
const userCompanyCode = req.user?.companyCode;
const options = await this.commonCodeService.getCodeOptions(
categoryCode,
userCompanyCode
);
return res.json({
success: true,
data: options,
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",
});
}
}
/**
* 코드 순서 변경
* PUT /api/common-codes/categories/:categoryCode/codes/reorder
*/
async reorderCodes(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode } = req.params;
const { codes } = req.body as {
codes: Array<{ codeValue: string; sortOrder: number }>;
};
const userId = req.user?.userId || "SYSTEM";
if (!codes || !Array.isArray(codes)) {
return res.status(400).json({
success: false,
message: "코드 순서 정보가 올바르지 않습니다.",
});
}
await this.commonCodeService.reorderCodes(categoryCode, codes, userId);
return res.json({
success: true,
message: "코드 순서 변경 성공",
});
} 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/check-duplicate?field=categoryCode&value=USER_STATUS&excludeCode=OLD_CODE
*/
async checkCategoryDuplicate(req: AuthenticatedRequest, res: Response) {
try {
const { field, value, excludeCode } = req.query;
const userCompanyCode = req.user?.companyCode;
// 입력값 검증
if (!field || !value) {
return res.status(400).json({
success: false,
message: "field와 value 파라미터가 필요합니다.",
});
}
const validFields = ["categoryCode", "categoryName", "categoryNameEng"];
if (!validFields.includes(field as string)) {
return res.status(400).json({
success: false,
message:
"field는 categoryCode, categoryName, categoryNameEng 중 하나여야 합니다.",
});
}
const result = await this.commonCodeService.checkCategoryDuplicate(
field as "categoryCode" | "categoryName" | "categoryNameEng",
value as string,
excludeCode as string,
userCompanyCode
);
return res.json({
success: true,
data: {
...result,
field,
value,
},
message: "카테고리 중복 검사 완료",
});
} catch (error) {
logger.error("카테고리 중복 검사 실패:", error);
return res.status(500).json({
success: false,
message: "카테고리 중복 검사 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
/**
* 코드 중복 검사 (회사별)
* GET /api/common-codes/categories/:categoryCode/codes/check-duplicate?field=codeValue&value=ACTIVE&excludeCode=OLD_CODE
*/
async checkCodeDuplicate(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode } = req.params;
const { field, value, excludeCode } = req.query;
const userCompanyCode = req.user?.companyCode;
// 입력값 검증
if (!field || !value) {
return res.status(400).json({
success: false,
message: "field와 value 파라미터가 필요합니다.",
});
}
const validFields = ["codeValue", "codeName", "codeNameEng"];
if (!validFields.includes(field as string)) {
return res.status(400).json({
success: false,
message:
"field는 codeValue, codeName, codeNameEng 중 하나여야 합니다.",
});
}
const result = await this.commonCodeService.checkCodeDuplicate(
categoryCode,
field as "codeValue" | "codeName" | "codeNameEng",
value as string,
excludeCode as string,
userCompanyCode
);
return res.json({
success: true,
data: {
...result,
categoryCode,
field,
value,
},
message: "코드 중복 검사 완료",
});
} 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/hierarchy
* Query: parentCodeValue (optional), depth (optional), menuObjid (optional)
*/
async getHierarchicalCodes(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode } = req.params;
const { parentCodeValue, depth, menuObjid } = req.query;
const userCompanyCode = req.user?.companyCode;
const menuObjidNum = menuObjid ? Number(menuObjid) : undefined;
// parentCodeValue가 빈 문자열이면 최상위 코드 조회
const parentValue = parentCodeValue === '' || parentCodeValue === undefined
? null
: parentCodeValue as string;
const codes = await this.commonCodeService.getHierarchicalCodes(
categoryCode,
parentValue,
depth ? parseInt(depth as string) : undefined,
userCompanyCode,
menuObjidNum
);
// 프론트엔드 형식으로 변환
const transformedData = codes.map((code: any) => ({
codeValue: code.code_value,
codeName: code.code_name,
codeNameEng: code.code_name_eng,
description: code.description,
sortOrder: code.sort_order,
isActive: code.is_active,
parentCodeValue: code.parent_code_value,
depth: code.depth,
// 기존 필드도 유지
code_category: code.code_category,
code_value: code.code_value,
code_name: code.code_name,
code_name_eng: code.code_name_eng,
sort_order: code.sort_order,
is_active: code.is_active,
parent_code_value: code.parent_code_value,
}));
return res.json({
success: true,
data: transformedData,
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/tree
*/
async getCodeTree(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode } = req.params;
const { menuObjid } = req.query;
const userCompanyCode = req.user?.companyCode;
const menuObjidNum = menuObjid ? Number(menuObjid) : undefined;
const result = await this.commonCodeService.getCodeTree(
categoryCode,
userCompanyCode,
menuObjidNum
);
return res.json({
success: true,
data: result,
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/codes/:codeValue/has-children
*/
async hasChildren(req: AuthenticatedRequest, res: Response) {
try {
const { categoryCode, codeValue } = req.params;
const companyCode = req.user?.companyCode;
const hasChildren = await this.commonCodeService.hasChildren(
categoryCode,
codeValue,
companyCode
);
return res.json({
success: true,
data: { hasChildren },
message: "자식 코드 확인 완료",
});
} catch (error) {
logger.error(
`자식 코드 확인 실패 (${req.params.categoryCode}.${req.params.codeValue}):`,
error
);
return res.status(500).json({
success: false,
message: "자식 코드 확인 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
}