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

792 lines
23 KiB
TypeScript

import { PrismaClient } from "@prisma/client";
import { logger } from "../utils/logger";
import {
Language,
LangKey,
LangText,
CreateLanguageRequest,
UpdateLanguageRequest,
CreateLangKeyRequest,
UpdateLangKeyRequest,
SaveLangTextsRequest,
GetLangKeysParams,
GetUserTextParams,
BatchTranslationRequest,
ApiResponse,
} from "../types/multilang";
const prisma = new PrismaClient();
export class MultiLangService {
constructor() {}
/**
* 언어 목록 조회
*/
async getLanguages(): Promise<Language[]> {
try {
logger.info("언어 목록 조회 시작");
const languages = await prisma.language_master.findMany({
orderBy: [{ sort_order: "asc" }, { lang_code: "asc" }],
select: {
lang_code: true,
lang_name: true,
lang_native: true,
is_active: true,
sort_order: true,
created_date: true,
created_by: true,
updated_date: true,
updated_by: true,
},
});
const mappedLanguages: Language[] = languages.map((lang) => ({
langCode: lang.lang_code,
langName: lang.lang_name,
langNative: lang.lang_native,
isActive: lang.is_active || "N",
sortOrder: lang.sort_order ?? undefined,
createdDate: lang.created_date || undefined,
createdBy: lang.created_by || undefined,
updatedDate: lang.updated_date || undefined,
updatedBy: lang.updated_by || undefined,
}));
logger.info(`언어 목록 조회 완료: ${mappedLanguages.length}`);
return mappedLanguages;
} catch (error) {
logger.error("언어 목록 조회 중 오류 발생:", error);
throw new Error(
`언어 목록 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 언어 생성
*/
async createLanguage(languageData: CreateLanguageRequest): Promise<Language> {
try {
logger.info("언어 생성 시작", { languageData });
// 중복 체크
const existingLanguage = await prisma.language_master.findUnique({
where: { lang_code: languageData.langCode },
});
if (existingLanguage) {
throw new Error(
`이미 존재하는 언어 코드입니다: ${languageData.langCode}`
);
}
// 언어 생성
const createdLanguage = await prisma.language_master.create({
data: {
lang_code: languageData.langCode,
lang_name: languageData.langName,
lang_native: languageData.langNative,
is_active: languageData.isActive || "Y",
sort_order: languageData.sortOrder || 0,
created_by: languageData.createdBy || "system",
updated_by: languageData.updatedBy || "system",
},
});
logger.info("언어 생성 완료", { langCode: createdLanguage.lang_code });
return {
langCode: createdLanguage.lang_code,
langName: createdLanguage.lang_name,
langNative: createdLanguage.lang_native,
isActive: createdLanguage.is_active || "N",
sortOrder: createdLanguage.sort_order ?? undefined,
createdDate: createdLanguage.created_date || undefined,
createdBy: createdLanguage.created_by || undefined,
updatedDate: createdLanguage.updated_date || undefined,
updatedBy: createdLanguage.updated_by || undefined,
};
} catch (error) {
logger.error("언어 생성 중 오류 발생:", error);
throw new Error(
`언어 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 언어 수정
*/
async updateLanguage(
langCode: string,
languageData: UpdateLanguageRequest
): Promise<Language> {
try {
logger.info("언어 수정 시작", { langCode, languageData });
// 기존 언어 확인
const existingLanguage = await prisma.language_master.findUnique({
where: { lang_code: langCode },
});
if (!existingLanguage) {
throw new Error(`언어를 찾을 수 없습니다: ${langCode}`);
}
// 언어 수정
const updatedLanguage = await prisma.language_master.update({
where: { lang_code: langCode },
data: {
...(languageData.langName && { lang_name: languageData.langName }),
...(languageData.langNative && {
lang_native: languageData.langNative,
}),
...(languageData.isActive && { is_active: languageData.isActive }),
...(languageData.sortOrder !== undefined && {
sort_order: languageData.sortOrder,
}),
updated_by: languageData.updatedBy || "system",
},
});
logger.info("언어 수정 완료", { langCode });
return {
langCode: updatedLanguage.lang_code,
langName: updatedLanguage.lang_name,
langNative: updatedLanguage.lang_native,
isActive: updatedLanguage.is_active || "N",
sortOrder: updatedLanguage.sort_order ?? undefined,
createdDate: updatedLanguage.created_date || undefined,
createdBy: updatedLanguage.created_by || undefined,
updatedDate: updatedLanguage.updated_date || undefined,
updatedBy: updatedLanguage.updated_by || undefined,
};
} catch (error) {
logger.error("언어 수정 중 오류 발생:", error);
throw new Error(
`언어 수정 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 언어 상태 토글
*/
async toggleLanguage(langCode: string): Promise<string> {
try {
logger.info("언어 상태 토글 시작", { langCode });
// 현재 언어 조회
const currentLanguage = await prisma.language_master.findUnique({
where: { lang_code: langCode },
select: { is_active: true },
});
if (!currentLanguage) {
throw new Error(`언어를 찾을 수 없습니다: ${langCode}`);
}
const newStatus = currentLanguage.is_active === "Y" ? "N" : "Y";
// 상태 업데이트
await prisma.language_master.update({
where: { lang_code: langCode },
data: {
is_active: newStatus,
updated_by: "system",
},
});
const result = newStatus === "Y" ? "활성화" : "비활성화";
logger.info("언어 상태 토글 완료", { langCode, result });
return result;
} catch (error) {
logger.error("언어 상태 토글 중 오류 발생:", error);
throw new Error(
`언어 상태 토글 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 다국어 키 목록 조회
*/
async getLangKeys(params: GetLangKeysParams): Promise<LangKey[]> {
try {
logger.info("다국어 키 목록 조회 시작", { params });
const whereConditions: any = {};
// 회사 코드 필터
if (params.companyCode) {
whereConditions.company_code = params.companyCode;
}
// 메뉴 코드 필터
if (params.menuCode) {
whereConditions.menu_name = params.menuCode;
}
// 검색 조건
if (params.searchText) {
whereConditions.OR = [
{ lang_key: { contains: params.searchText, mode: "insensitive" } },
{ description: { contains: params.searchText, mode: "insensitive" } },
{ menu_name: { contains: params.searchText, mode: "insensitive" } },
];
}
const langKeys = await prisma.multi_lang_key_master.findMany({
where: whereConditions,
orderBy: [
{ company_code: "asc" },
{ menu_name: "asc" },
{ lang_key: "asc" },
],
select: {
key_id: true,
company_code: true,
menu_name: true,
lang_key: true,
description: true,
is_active: true,
created_date: true,
created_by: true,
updated_date: true,
updated_by: true,
},
});
const mappedKeys: LangKey[] = langKeys.map((key) => ({
keyId: key.key_id,
companyCode: key.company_code,
menuName: key.menu_name || undefined,
langKey: key.lang_key,
description: key.description || undefined,
isActive: key.is_active || "Y",
createdDate: key.created_date || undefined,
createdBy: key.created_by || undefined,
updatedDate: key.updated_date || undefined,
updatedBy: key.updated_by || undefined,
}));
logger.info(`다국어 키 목록 조회 완료: ${mappedKeys.length}`);
return mappedKeys;
} catch (error) {
logger.error("다국어 키 목록 조회 중 오류 발생:", error);
throw new Error(
`다국어 키 목록 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 특정 키의 다국어 텍스트 조회
*/
async getLangTexts(keyId: number): Promise<LangText[]> {
try {
logger.info("다국어 텍스트 조회 시작", { keyId });
const langTexts = await prisma.multi_lang_text.findMany({
where: {
key_id: keyId,
is_active: "Y",
},
orderBy: { lang_code: "asc" },
select: {
text_id: true,
key_id: true,
lang_code: true,
lang_text: true,
is_active: true,
created_date: true,
created_by: true,
updated_date: true,
updated_by: true,
},
});
const mappedTexts: LangText[] = langTexts.map((text) => ({
textId: text.text_id,
keyId: text.key_id,
langCode: text.lang_code,
langText: text.lang_text,
isActive: text.is_active || "Y",
createdDate: text.created_date || undefined,
createdBy: text.created_by || undefined,
updatedDate: text.updated_date || undefined,
updatedBy: text.updated_by || undefined,
}));
logger.info(`다국어 텍스트 조회 완료: ${mappedTexts.length}`);
return mappedTexts;
} catch (error) {
logger.error("다국어 텍스트 조회 중 오류 발생:", error);
throw new Error(
`다국어 텍스트 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 다국어 키 생성
*/
async createLangKey(keyData: CreateLangKeyRequest): Promise<number> {
try {
logger.info("다국어 키 생성 시작", { keyData });
// 중복 체크
const existingKey = await prisma.multi_lang_key_master.findFirst({
where: {
company_code: keyData.companyCode,
lang_key: keyData.langKey,
},
});
if (existingKey) {
throw new Error(
`동일한 회사에 이미 존재하는 언어키입니다: ${keyData.langKey}`
);
}
// 다국어 키 생성
const createdKey = await prisma.multi_lang_key_master.create({
data: {
company_code: keyData.companyCode,
menu_name: keyData.menuName || null,
lang_key: keyData.langKey,
description: keyData.description || null,
is_active: keyData.isActive || "Y",
created_by: keyData.createdBy || "system",
updated_by: keyData.updatedBy || "system",
},
});
logger.info("다국어 키 생성 완료", {
keyId: createdKey.key_id,
langKey: keyData.langKey,
});
return createdKey.key_id;
} catch (error) {
logger.error("다국어 키 생성 중 오류 발생:", error);
throw new Error(
`다국어 키 생성 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 다국어 키 수정
*/
async updateLangKey(
keyId: number,
keyData: UpdateLangKeyRequest
): Promise<void> {
try {
logger.info("다국어 키 수정 시작", { keyId, keyData });
// 기존 키 확인
const existingKey = await prisma.multi_lang_key_master.findUnique({
where: { key_id: keyId },
});
if (!existingKey) {
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
}
// 중복 체크 (자신을 제외하고)
if (keyData.companyCode && keyData.langKey) {
const duplicateKey = await prisma.multi_lang_key_master.findFirst({
where: {
company_code: keyData.companyCode,
lang_key: keyData.langKey,
key_id: { not: keyId },
},
});
if (duplicateKey) {
throw new Error(
`동일한 회사에 이미 존재하는 언어키입니다: ${keyData.langKey}`
);
}
}
// 다국어 키 수정
await prisma.multi_lang_key_master.update({
where: { key_id: keyId },
data: {
...(keyData.companyCode && { company_code: keyData.companyCode }),
...(keyData.menuName !== undefined && {
menu_name: keyData.menuName,
}),
...(keyData.langKey && { lang_key: keyData.langKey }),
...(keyData.description !== undefined && {
description: keyData.description,
}),
updated_by: keyData.updatedBy || "system",
},
});
logger.info("다국어 키 수정 완료", { keyId });
} catch (error) {
logger.error("다국어 키 수정 중 오류 발생:", error);
throw new Error(
`다국어 키 수정 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 다국어 키 삭제
*/
async deleteLangKey(keyId: number): Promise<void> {
try {
logger.info("다국어 키 삭제 시작", { keyId });
// 기존 키 확인
const existingKey = await prisma.multi_lang_key_master.findUnique({
where: { key_id: keyId },
});
if (!existingKey) {
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
}
// 트랜잭션으로 키와 연관된 텍스트 모두 삭제
await prisma.$transaction(async (tx) => {
// 관련된 다국어 텍스트 삭제
await tx.multi_lang_text.deleteMany({
where: { key_id: keyId },
});
// 다국어 키 삭제
await tx.multi_lang_key_master.delete({
where: { key_id: keyId },
});
});
logger.info("다국어 키 삭제 완료", { keyId });
} catch (error) {
logger.error("다국어 키 삭제 중 오류 발생:", error);
throw new Error(
`다국어 키 삭제 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 다국어 키 상태 토글
*/
async toggleLangKey(keyId: number): Promise<string> {
try {
logger.info("다국어 키 상태 토글 시작", { keyId });
// 현재 키 조회
const currentKey = await prisma.multi_lang_key_master.findUnique({
where: { key_id: keyId },
select: { is_active: true },
});
if (!currentKey) {
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
}
const newStatus = currentKey.is_active === "Y" ? "N" : "Y";
// 상태 업데이트
await prisma.multi_lang_key_master.update({
where: { key_id: keyId },
data: {
is_active: newStatus,
updated_by: "system",
},
});
const result = newStatus === "Y" ? "활성화" : "비활성화";
logger.info("다국어 키 상태 토글 완료", { keyId, result });
return result;
} catch (error) {
logger.error("다국어 키 상태 토글 중 오류 발생:", error);
throw new Error(
`다국어 키 상태 토글 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 다국어 텍스트 저장/수정
*/
async saveLangTexts(
keyId: number,
textData: SaveLangTextsRequest
): Promise<void> {
try {
logger.info("다국어 텍스트 저장 시작", {
keyId,
textCount: textData.texts.length,
});
// 기존 키 확인
const existingKey = await prisma.multi_lang_key_master.findUnique({
where: { key_id: keyId },
});
if (!existingKey) {
throw new Error(`다국어 키를 찾을 수 없습니다: ${keyId}`);
}
// 트랜잭션으로 기존 텍스트 삭제 후 새로 생성
await prisma.$transaction(async (tx) => {
// 기존 텍스트 삭제
await tx.multi_lang_text.deleteMany({
where: { key_id: keyId },
});
// 새로운 텍스트 삽입
if (textData.texts.length > 0) {
await tx.multi_lang_text.createMany({
data: textData.texts.map((text) => ({
key_id: keyId,
lang_code: text.langCode,
lang_text: text.langText,
is_active: text.isActive || "Y",
created_by: text.createdBy || "system",
updated_by: text.updatedBy || "system",
})),
});
}
});
logger.info("다국어 텍스트 저장 완료", {
keyId,
savedCount: textData.texts.length,
});
} catch (error) {
logger.error("다국어 텍스트 저장 중 오류 발생:", error);
throw new Error(
`다국어 텍스트 저장 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 사용자별 다국어 텍스트 조회
*/
async getUserText(params: GetUserTextParams): Promise<string> {
try {
logger.info("사용자별 다국어 텍스트 조회 시작", { params });
const result = await prisma.multi_lang_text.findFirst({
where: {
lang_code: params.userLang,
is_active: "Y",
multi_lang_key_master: {
company_code: params.companyCode,
menu_name: params.menuCode,
lang_key: params.langKey,
is_active: "Y",
},
},
select: {
lang_text: true,
},
});
if (!result) {
logger.warn("사용자별 다국어 텍스트를 찾을 수 없음", { params });
return params.langKey; // 기본값으로 키 반환
}
logger.info("사용자별 다국어 텍스트 조회 완료", {
params,
langText: result.lang_text,
});
return result.lang_text;
} catch (error) {
logger.error("사용자별 다국어 텍스트 조회 중 오류 발생:", error);
throw new Error(
`사용자별 다국어 텍스트 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 특정 키의 다국어 텍스트 조회
*/
async getLangText(
companyCode: string,
langKey: string,
langCode: string
): Promise<string> {
try {
logger.info("특정 키의 다국어 텍스트 조회 시작", {
companyCode,
langKey,
langCode,
});
const result = await prisma.multi_lang_text.findFirst({
where: {
lang_code: langCode,
is_active: "Y",
multi_lang_key_master: {
company_code: companyCode,
lang_key: langKey,
is_active: "Y",
},
},
select: {
lang_text: true,
},
});
if (!result) {
logger.warn("특정 키의 다국어 텍스트를 찾을 수 없음", {
companyCode,
langKey,
langCode,
});
return langKey; // 기본값으로 키 반환
}
logger.info("특정 키의 다국어 텍스트 조회 완료", {
companyCode,
langKey,
langCode,
langText: result.lang_text,
});
return result.lang_text;
} catch (error) {
logger.error("특정 키의 다국어 텍스트 조회 중 오류 발생:", error);
throw new Error(
`특정 키의 다국어 텍스트 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 배치 번역 조회
*/
async getBatchTranslations(
params: BatchTranslationRequest
): Promise<Record<string, string>> {
try {
logger.info("배치 번역 조회 시작", {
companyCode: params.companyCode,
menuCode: params.menuCode,
userLang: params.userLang,
keyCount: params.langKeys.length,
});
if (params.langKeys.length === 0) {
return {};
}
// 모든 키에 대한 번역 조회
const translations = await prisma.multi_lang_text.findMany({
where: {
lang_code: params.userLang,
is_active: "Y",
multi_lang_key_master: {
lang_key: { in: params.langKeys },
company_code: { in: [params.companyCode, "*"] },
is_active: "Y",
},
},
select: {
lang_text: true,
multi_lang_key_master: {
select: {
lang_key: true,
company_code: true,
},
},
},
orderBy: {
multi_lang_key_master: {
company_code: "asc", // 회사별 우선, '*' 는 기본값
},
},
});
const result: Record<string, string> = {};
// 기본값으로 모든 키 설정
params.langKeys.forEach((key) => {
result[key] = key;
});
// 실제 번역으로 덮어쓰기 (회사별 우선)
translations.forEach((translation) => {
const langKey = translation.multi_lang_key_master.lang_key;
if (params.langKeys.includes(langKey)) {
result[langKey] = translation.lang_text;
}
});
logger.info("배치 번역 조회 완료", {
totalKeys: params.langKeys.length,
foundTranslations: translations.length,
resultKeys: Object.keys(result).length,
});
return result;
} catch (error) {
logger.error("배치 번역 조회 중 오류 발생:", error);
throw new Error(
`배치 번역 조회 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 언어 삭제
*/
async deleteLanguage(langCode: string): Promise<void> {
try {
logger.info("언어 삭제 시작", { langCode });
// 기존 언어 확인
const existingLanguage = await prisma.language_master.findUnique({
where: { lang_code: langCode },
});
if (!existingLanguage) {
throw new Error(`언어를 찾을 수 없습니다: ${langCode}`);
}
// 트랜잭션으로 언어와 관련 텍스트 삭제
await prisma.$transaction(async (tx) => {
// 해당 언어의 다국어 텍스트 삭제
const deleteResult = await tx.multi_lang_text.deleteMany({
where: { lang_code: langCode },
});
logger.info(`삭제된 다국어 텍스트 수: ${deleteResult.count}`, {
langCode,
});
// 언어 마스터 삭제
await tx.language_master.delete({
where: { lang_code: langCode },
});
});
logger.info("언어 삭제 완료", { langCode });
} catch (error) {
logger.error("언어 삭제 중 오류 발생:", error);
throw new Error(
`언어 삭제 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
}