ERP-node/backend-node/src/services/commonCodeService.ts

565 lines
15 KiB
TypeScript
Raw Normal View History

2025-09-02 11:30:19 +09:00
import { PrismaClient } from "@prisma/client";
2025-09-29 15:21:14 +09:00
import prisma from "../config/database";
2025-09-02 11:30:19 +09:00
import { logger } from "../utils/logger";
export interface CodeCategory {
category_code: string;
category_name: string;
category_name_eng?: string | null;
description?: string | null;
sort_order: number;
is_active: string;
created_date?: Date | null;
created_by?: string | null;
updated_date?: Date | null;
updated_by?: string | null;
}
export interface CodeInfo {
code_category: string;
code_value: string;
code_name: string;
code_name_eng?: string | null;
description?: string | null;
sort_order: number;
is_active: string;
created_date?: Date | null;
created_by?: string | null;
updated_date?: Date | null;
updated_by?: string | null;
}
export interface GetCategoriesParams {
search?: string;
isActive?: boolean;
page?: number;
size?: number;
}
export interface GetCodesParams {
search?: string;
isActive?: boolean;
2025-09-03 18:23:23 +09:00
page?: number;
size?: number;
2025-09-02 11:30:19 +09:00
}
export interface CreateCategoryData {
categoryCode: string;
categoryName: string;
categoryNameEng?: string;
description?: string;
sortOrder?: number;
isActive?: string;
2025-09-02 11:30:19 +09:00
}
export interface CreateCodeData {
codeValue: string;
codeName: string;
codeNameEng?: string;
description?: string;
sortOrder?: number;
isActive?: string;
2025-09-02 11:30:19 +09:00
}
export class CommonCodeService {
/**
*
*/
async getCategories(params: GetCategoriesParams) {
try {
const { search, isActive, page = 1, size = 20 } = params;
let whereClause: any = {};
if (search) {
whereClause.OR = [
{ category_name: { contains: search, mode: "insensitive" } },
{ category_code: { contains: search, mode: "insensitive" } },
];
}
if (isActive !== undefined) {
whereClause.is_active = isActive ? "Y" : "N";
}
const offset = (page - 1) * size;
const [categories, total] = await Promise.all([
prisma.code_category.findMany({
where: whereClause,
orderBy: [{ sort_order: "asc" }, { category_code: "asc" }],
skip: offset,
take: size,
}),
prisma.code_category.count({ where: whereClause }),
]);
logger.info(
`카테고리 조회 완료: ${categories.length}개, 전체: ${total}`
);
return {
data: categories,
total,
};
} catch (error) {
logger.error("카테고리 조회 중 오류:", error);
throw error;
}
}
/**
*
*/
async getCodes(categoryCode: string, params: GetCodesParams) {
try {
2025-09-03 18:23:23 +09:00
const { search, isActive, page = 1, size = 20 } = params;
2025-09-02 11:30:19 +09:00
let whereClause: any = {
code_category: categoryCode,
};
if (search) {
whereClause.OR = [
{ code_name: { contains: search, mode: "insensitive" } },
{ code_value: { contains: search, mode: "insensitive" } },
];
}
if (isActive !== undefined) {
whereClause.is_active = isActive ? "Y" : "N";
}
2025-09-03 18:23:23 +09:00
const offset = (page - 1) * size;
const [codes, total] = await Promise.all([
prisma.code_info.findMany({
where: whereClause,
orderBy: [{ sort_order: "asc" }, { code_value: "asc" }],
skip: offset,
take: size,
}),
prisma.code_info.count({ where: whereClause }),
]);
2025-09-02 11:30:19 +09:00
2025-09-03 18:23:23 +09:00
logger.info(
`코드 조회 완료: ${categoryCode} - ${codes.length}개, 전체: ${total}`
);
2025-09-02 11:30:19 +09:00
2025-09-03 18:23:23 +09:00
return { data: codes, total };
2025-09-02 11:30:19 +09:00
} catch (error) {
logger.error(`코드 조회 중 오류 (${categoryCode}):`, error);
throw error;
}
}
/**
*
*/
async createCategory(data: CreateCategoryData, createdBy: string) {
try {
const category = await prisma.code_category.create({
data: {
category_code: data.categoryCode,
category_name: data.categoryName,
category_name_eng: data.categoryNameEng,
description: data.description,
sort_order: data.sortOrder || 0,
is_active: "Y",
created_by: createdBy,
updated_by: createdBy,
},
});
logger.info(`카테고리 생성 완료: ${data.categoryCode}`);
return category;
} catch (error) {
logger.error("카테고리 생성 중 오류:", error);
throw error;
}
}
/**
*
*/
async updateCategory(
categoryCode: string,
data: Partial<CreateCategoryData>,
updatedBy: string
) {
try {
// 디버깅: 받은 데이터 로그
logger.info(`카테고리 수정 데이터:`, { categoryCode, data });
2025-09-02 11:30:19 +09:00
const category = await prisma.code_category.update({
where: { category_code: categoryCode },
data: {
category_name: data.categoryName,
category_name_eng: data.categoryNameEng,
description: data.description,
sort_order: data.sortOrder,
is_active:
typeof data.isActive === "boolean"
? data.isActive
? "Y"
: "N"
: data.isActive, // boolean이면 "Y"/"N"으로 변환
2025-09-02 11:30:19 +09:00
updated_by: updatedBy,
updated_date: new Date(),
},
});
logger.info(`카테고리 수정 완료: ${categoryCode}`);
return category;
} catch (error) {
logger.error(`카테고리 수정 중 오류 (${categoryCode}):`, error);
throw error;
}
}
/**
*
*/
async deleteCategory(categoryCode: string) {
try {
await prisma.code_category.delete({
where: { category_code: categoryCode },
});
logger.info(`카테고리 삭제 완료: ${categoryCode}`);
} catch (error) {
logger.error(`카테고리 삭제 중 오류 (${categoryCode}):`, error);
throw error;
}
}
/**
*
*/
async createCode(
categoryCode: string,
data: CreateCodeData,
createdBy: string
) {
try {
const code = await prisma.code_info.create({
data: {
code_category: categoryCode,
code_value: data.codeValue,
code_name: data.codeName,
code_name_eng: data.codeNameEng,
description: data.description,
sort_order: data.sortOrder || 0,
is_active: "Y",
created_by: createdBy,
updated_by: createdBy,
},
});
logger.info(`코드 생성 완료: ${categoryCode}.${data.codeValue}`);
return code;
} catch (error) {
logger.error(
`코드 생성 중 오류 (${categoryCode}.${data.codeValue}):`,
error
);
throw error;
}
}
/**
*
*/
async updateCode(
categoryCode: string,
codeValue: string,
data: Partial<CreateCodeData>,
updatedBy: string
) {
try {
// 디버깅: 받은 데이터 로그
logger.info(`코드 수정 데이터:`, { categoryCode, codeValue, data });
2025-09-02 11:30:19 +09:00
const code = await prisma.code_info.update({
where: {
code_category_code_value: {
code_category: categoryCode,
code_value: codeValue,
},
},
data: {
code_name: data.codeName,
code_name_eng: data.codeNameEng,
description: data.description,
sort_order: data.sortOrder,
is_active:
typeof data.isActive === "boolean"
? data.isActive
? "Y"
: "N"
: data.isActive, // boolean이면 "Y"/"N"으로 변환
2025-09-02 11:30:19 +09:00
updated_by: updatedBy,
updated_date: new Date(),
},
});
logger.info(`코드 수정 완료: ${categoryCode}.${codeValue}`);
return code;
} catch (error) {
logger.error(`코드 수정 중 오류 (${categoryCode}.${codeValue}):`, error);
throw error;
}
}
/**
*
*/
async deleteCode(categoryCode: string, codeValue: string) {
try {
await prisma.code_info.delete({
where: {
code_category_code_value: {
code_category: categoryCode,
code_value: codeValue,
},
},
});
logger.info(`코드 삭제 완료: ${categoryCode}.${codeValue}`);
} catch (error) {
logger.error(`코드 삭제 중 오류 (${categoryCode}.${codeValue}):`, error);
throw error;
}
}
/**
* ()
*/
async getCodeOptions(categoryCode: string) {
try {
const codes = await prisma.code_info.findMany({
where: {
code_category: categoryCode,
is_active: "Y",
},
select: {
code_value: true,
code_name: true,
code_name_eng: true,
sort_order: true,
},
orderBy: [{ sort_order: "asc" }, { code_value: "asc" }],
});
const options = codes.map((code) => ({
value: code.code_value,
label: code.code_name,
labelEng: code.code_name_eng,
}));
logger.info(`코드 옵션 조회 완료: ${categoryCode} - ${options.length}`);
return options;
} catch (error) {
logger.error(`코드 옵션 조회 중 오류 (${categoryCode}):`, error);
throw error;
}
}
/**
*
*/
async reorderCodes(
categoryCode: string,
codes: Array<{ codeValue: string; sortOrder: number }>,
updatedBy: string
) {
try {
// 먼저 존재하는 코드들을 확인
const existingCodes = await prisma.code_info.findMany({
where: {
code_category: categoryCode,
code_value: { in: codes.map((c) => c.codeValue) },
},
select: { code_value: true },
});
const existingCodeValues = existingCodes.map((c) => c.code_value);
const validCodes = codes.filter((c) =>
existingCodeValues.includes(c.codeValue)
);
if (validCodes.length === 0) {
throw new Error(
`카테고리 ${categoryCode}에 순서를 변경할 유효한 코드가 없습니다.`
);
}
const updatePromises = validCodes.map(({ codeValue, sortOrder }) =>
2025-09-02 11:30:19 +09:00
prisma.code_info.update({
where: {
code_category_code_value: {
code_category: categoryCode,
code_value: codeValue,
},
},
data: {
sort_order: sortOrder,
updated_by: updatedBy,
updated_date: new Date(),
},
})
);
await Promise.all(updatePromises);
const skippedCodes = codes.filter(
(c) => !existingCodeValues.includes(c.codeValue)
);
if (skippedCodes.length > 0) {
logger.warn(
`코드 순서 변경 시 존재하지 않는 코드들을 건너뜀: ${skippedCodes.map((c) => c.codeValue).join(", ")}`
);
}
logger.info(
`코드 순서 변경 완료: ${categoryCode} - ${validCodes.length}개 (전체 ${codes.length}개 중)`
);
2025-09-02 11:30:19 +09:00
} catch (error) {
logger.error(`코드 순서 변경 중 오류 (${categoryCode}):`, error);
throw error;
}
}
/**
*
*/
async checkCategoryDuplicate(
2025-09-03 16:44:36 +09:00
field: "categoryCode" | "categoryName" | "categoryNameEng",
value: string,
excludeCategoryCode?: string
): Promise<{ isDuplicate: boolean; message: string }> {
try {
if (!value || !value.trim()) {
return {
isDuplicate: false,
2025-09-03 16:44:36 +09:00
message: "값을 입력해주세요.",
};
}
const trimmedValue = value.trim();
let whereCondition: any = {};
// 필드별 검색 조건 설정
switch (field) {
2025-09-03 16:44:36 +09:00
case "categoryCode":
whereCondition.category_code = trimmedValue;
break;
2025-09-03 16:44:36 +09:00
case "categoryName":
whereCondition.category_name = trimmedValue;
break;
2025-09-03 16:44:36 +09:00
case "categoryNameEng":
whereCondition.category_name_eng = trimmedValue;
break;
}
// 수정 시 자기 자신 제외
if (excludeCategoryCode) {
whereCondition.category_code = {
...whereCondition.category_code,
2025-09-03 16:44:36 +09:00
not: excludeCategoryCode,
};
}
const existingCategory = await prisma.code_category.findFirst({
where: whereCondition,
2025-09-03 16:44:36 +09:00
select: { category_code: true },
});
const isDuplicate = !!existingCategory;
const fieldNames = {
2025-09-03 16:44:36 +09:00
categoryCode: "카테고리 코드",
categoryName: "카테고리명",
categoryNameEng: "카테고리 영문명",
};
return {
isDuplicate,
2025-09-03 16:44:36 +09:00
message: isDuplicate
? `이미 사용 중인 ${fieldNames[field]}입니다.`
2025-09-03 16:44:36 +09:00
: `사용 가능한 ${fieldNames[field]}입니다.`,
};
} catch (error) {
logger.error(`카테고리 중복 검사 중 오류 (${field}: ${value}):`, error);
throw error;
}
}
/**
*
*/
async checkCodeDuplicate(
categoryCode: string,
2025-09-03 16:44:36 +09:00
field: "codeValue" | "codeName" | "codeNameEng",
value: string,
excludeCodeValue?: string
): Promise<{ isDuplicate: boolean; message: string }> {
try {
if (!value || !value.trim()) {
return {
isDuplicate: false,
2025-09-03 16:44:36 +09:00
message: "값을 입력해주세요.",
};
}
const trimmedValue = value.trim();
let whereCondition: any = {
2025-09-03 16:44:36 +09:00
code_category: categoryCode,
};
// 필드별 검색 조건 설정
switch (field) {
2025-09-03 16:44:36 +09:00
case "codeValue":
whereCondition.code_value = trimmedValue;
break;
2025-09-03 16:44:36 +09:00
case "codeName":
whereCondition.code_name = trimmedValue;
break;
2025-09-03 16:44:36 +09:00
case "codeNameEng":
whereCondition.code_name_eng = trimmedValue;
break;
}
// 수정 시 자기 자신 제외
if (excludeCodeValue) {
whereCondition.code_value = {
...whereCondition.code_value,
2025-09-03 16:44:36 +09:00
not: excludeCodeValue,
};
}
const existingCode = await prisma.code_info.findFirst({
where: whereCondition,
2025-09-03 16:44:36 +09:00
select: { code_value: true },
});
const isDuplicate = !!existingCode;
const fieldNames = {
2025-09-03 16:44:36 +09:00
codeValue: "코드값",
codeName: "코드명",
codeNameEng: "코드 영문명",
};
return {
isDuplicate,
2025-09-03 16:44:36 +09:00
message: isDuplicate
? `이미 사용 중인 ${fieldNames[field]}입니다.`
2025-09-03 16:44:36 +09:00
: `사용 가능한 ${fieldNames[field]}입니다.`,
};
} catch (error) {
2025-09-03 16:44:36 +09:00
logger.error(
`코드 중복 검사 중 오류 (${categoryCode}, ${field}: ${value}):`,
error
);
throw error;
}
}
2025-09-02 11:30:19 +09:00
}