Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node
This commit is contained in:
@@ -108,6 +108,46 @@ export async function getUserMenus(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POP 메뉴 목록 조회
|
||||
* [POP] 태그가 있는 L1 메뉴의 하위 active 메뉴를 반환
|
||||
*/
|
||||
export async function getPopMenus(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
try {
|
||||
const userCompanyCode = req.user?.companyCode || "ILSHIN";
|
||||
const userType = req.user?.userType;
|
||||
|
||||
const result = await AdminService.getPopMenuList({
|
||||
userCompanyCode,
|
||||
userType,
|
||||
});
|
||||
|
||||
const response: ApiResponse<any> = {
|
||||
success: true,
|
||||
message: "POP 메뉴 목록 조회 성공",
|
||||
data: result,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("POP 메뉴 목록 조회 중 오류 발생:", error);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: false,
|
||||
message: "POP 메뉴 목록 조회 중 오류가 발생했습니다.",
|
||||
error: {
|
||||
code: "POP_MENU_LIST_ERROR",
|
||||
details: error instanceof Error ? error.message : "Unknown error",
|
||||
},
|
||||
};
|
||||
|
||||
res.status(500).json(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 정보 조회
|
||||
*/
|
||||
|
||||
@@ -51,29 +51,24 @@ export class AuthController {
|
||||
|
||||
logger.debug(`로그인 사용자 정보: ${userInfo.userId} (${userInfo.companyCode})`);
|
||||
|
||||
// 메뉴 조회를 위한 공통 파라미터
|
||||
const { AdminService } = await import("../services/adminService");
|
||||
const paramMap = {
|
||||
userId: loginResult.userInfo.userId,
|
||||
userCompanyCode: loginResult.userInfo.companyCode || "ILSHIN",
|
||||
userType: loginResult.userInfo.userType,
|
||||
userLang: "ko",
|
||||
};
|
||||
|
||||
// 사용자의 첫 번째 접근 가능한 메뉴 조회
|
||||
let firstMenuPath: string | null = null;
|
||||
try {
|
||||
const { AdminService } = await import("../services/adminService");
|
||||
const paramMap = {
|
||||
userId: loginResult.userInfo.userId,
|
||||
userCompanyCode: loginResult.userInfo.companyCode || "ILSHIN",
|
||||
userType: loginResult.userInfo.userType,
|
||||
userLang: "ko",
|
||||
};
|
||||
|
||||
const menuList = await AdminService.getUserMenuList(paramMap);
|
||||
logger.debug(`로그인 후 메뉴 조회: 총 ${menuList.length}개 메뉴`);
|
||||
|
||||
// 접근 가능한 첫 번째 메뉴 찾기
|
||||
// 조건:
|
||||
// 1. LEV (레벨)이 2 이상 (최상위 폴더 제외)
|
||||
// 2. MENU_URL이 있고 비어있지 않음
|
||||
// 3. 이미 PATH, SEQ로 정렬되어 있으므로 첫 번째로 찾은 것이 첫 번째 메뉴
|
||||
const firstMenu = menuList.find((menu: any) => {
|
||||
const level = menu.lev || menu.level;
|
||||
const url = menu.menu_url || menu.url;
|
||||
|
||||
return level >= 2 && url && url.trim() !== "" && url !== "#";
|
||||
});
|
||||
|
||||
@@ -94,6 +89,22 @@ export class AuthController {
|
||||
useType: "접속",
|
||||
}).catch(() => {});
|
||||
|
||||
// POP 랜딩 경로 조회
|
||||
let popLandingPath: string | null = null;
|
||||
try {
|
||||
const popResult = await AdminService.getPopMenuList(paramMap);
|
||||
if (popResult.landingMenu?.menu_url) {
|
||||
popLandingPath = popResult.landingMenu.menu_url;
|
||||
} else if (popResult.childMenus.length === 1) {
|
||||
popLandingPath = popResult.childMenus[0].menu_url;
|
||||
} else if (popResult.childMenus.length > 1) {
|
||||
popLandingPath = "/pop";
|
||||
}
|
||||
logger.debug(`POP 랜딩 경로: ${popLandingPath}`);
|
||||
} catch (popError) {
|
||||
logger.warn("POP 메뉴 조회 중 오류 (무시):", popError);
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: "로그인 성공",
|
||||
@@ -101,6 +112,7 @@ export class AuthController {
|
||||
userInfo,
|
||||
token: loginResult.token,
|
||||
firstMenuPath,
|
||||
popLandingPath,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Router } from "express";
|
||||
import {
|
||||
getAdminMenus,
|
||||
getUserMenus,
|
||||
getPopMenus,
|
||||
getMenuInfo,
|
||||
saveMenu, // 메뉴 추가
|
||||
updateMenu, // 메뉴 수정
|
||||
@@ -40,6 +41,7 @@ router.use(authenticateToken);
|
||||
// 메뉴 관련 API
|
||||
router.get("/menus", getAdminMenus);
|
||||
router.get("/user-menus", getUserMenus);
|
||||
router.get("/pop-menus", getPopMenus);
|
||||
router.get("/menus/:menuId", getMenuInfo);
|
||||
router.post("/menus", saveMenu); // 메뉴 추가
|
||||
router.post("/menus/:menuObjid/copy", copyMenu); // 메뉴 복사 (NEW!)
|
||||
|
||||
@@ -17,6 +17,7 @@ interface AutoGenMappingInfo {
|
||||
numberingRuleId: string;
|
||||
targetColumn: string;
|
||||
showResultModal?: boolean;
|
||||
shareAcrossItems?: boolean;
|
||||
}
|
||||
|
||||
interface HiddenMappingInfo {
|
||||
@@ -182,6 +183,31 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
||||
throw new Error(`유효하지 않은 테이블명: ${cardMapping.targetTable}`);
|
||||
}
|
||||
|
||||
const allAutoGen = [
|
||||
...(fieldMapping?.autoGenMappings ?? []),
|
||||
...(cardMapping?.autoGenMappings ?? []),
|
||||
];
|
||||
|
||||
// 일괄 채번: shareAcrossItems=true인 매핑은 루프 전 1회만 채번
|
||||
const sharedCodes: Record<string, string> = {};
|
||||
for (const ag of allAutoGen) {
|
||||
if (!ag.shareAcrossItems) continue;
|
||||
if (!ag.numberingRuleId || !ag.targetColumn) continue;
|
||||
if (!isSafeIdentifier(ag.targetColumn)) continue;
|
||||
try {
|
||||
const code = await numberingRuleService.allocateCode(
|
||||
ag.numberingRuleId, companyCode, { ...fieldValues, ...(items[0] ?? {}) },
|
||||
);
|
||||
sharedCodes[ag.targetColumn] = code;
|
||||
generatedCodes.push({ targetColumn: ag.targetColumn, code, showResultModal: ag.showResultModal ?? false });
|
||||
logger.info("[pop/execute-action] 일괄 채번 완료", {
|
||||
ruleId: ag.numberingRuleId, targetColumn: ag.targetColumn, code,
|
||||
});
|
||||
} catch (err: any) {
|
||||
logger.error("[pop/execute-action] 일괄 채번 실패", { ruleId: ag.numberingRuleId, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
const columns: string[] = ["company_code"];
|
||||
const values: unknown[] = [companyCode];
|
||||
@@ -225,26 +251,41 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
const allAutoGen = [
|
||||
...(fieldMapping?.autoGenMappings ?? []),
|
||||
...(cardMapping?.autoGenMappings ?? []),
|
||||
];
|
||||
for (const ag of allAutoGen) {
|
||||
if (!ag.numberingRuleId || !ag.targetColumn) continue;
|
||||
if (!isSafeIdentifier(ag.targetColumn)) continue;
|
||||
if (columns.includes(`"${ag.targetColumn}"`)) continue;
|
||||
try {
|
||||
const generatedCode = await numberingRuleService.allocateCode(
|
||||
ag.numberingRuleId, companyCode, { ...fieldValues, ...item },
|
||||
);
|
||||
|
||||
if (ag.shareAcrossItems && sharedCodes[ag.targetColumn]) {
|
||||
columns.push(`"${ag.targetColumn}"`);
|
||||
values.push(generatedCode);
|
||||
generatedCodes.push({ targetColumn: ag.targetColumn, code: generatedCode, showResultModal: ag.showResultModal ?? false });
|
||||
} catch (err: any) {
|
||||
logger.error("[pop/execute-action] 채번 실패", { ruleId: ag.numberingRuleId, error: err.message });
|
||||
values.push(sharedCodes[ag.targetColumn]);
|
||||
} else if (!ag.shareAcrossItems) {
|
||||
try {
|
||||
const generatedCode = await numberingRuleService.allocateCode(
|
||||
ag.numberingRuleId, companyCode, { ...fieldValues, ...item },
|
||||
);
|
||||
columns.push(`"${ag.targetColumn}"`);
|
||||
values.push(generatedCode);
|
||||
generatedCodes.push({ targetColumn: ag.targetColumn, code: generatedCode, showResultModal: ag.showResultModal ?? false });
|
||||
} catch (err: any) {
|
||||
logger.error("[pop/execute-action] 채번 실패", { ruleId: ag.numberingRuleId, error: err.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!columns.includes('"created_date"')) {
|
||||
columns.push('"created_date"');
|
||||
values.push(new Date().toISOString());
|
||||
}
|
||||
if (!columns.includes('"updated_date"')) {
|
||||
columns.push('"updated_date"');
|
||||
values.push(new Date().toISOString());
|
||||
}
|
||||
if (!columns.includes('"writer"') && userId) {
|
||||
columns.push('"writer"');
|
||||
values.push(userId);
|
||||
}
|
||||
|
||||
if (columns.length > 1) {
|
||||
const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
|
||||
await client.query(
|
||||
@@ -292,8 +333,9 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
||||
for (let i = 0; i < lookupValues.length; i++) {
|
||||
const item = items[i] ?? {};
|
||||
const resolved = resolveStatusValue("conditional", task.fixedValue ?? "", task.conditionalValue, item);
|
||||
const autoUpdated = task.targetColumn !== "updated_date" ? `, "updated_date" = NOW()` : "";
|
||||
await client.query(
|
||||
`UPDATE "${task.targetTable}" SET "${task.targetColumn}" = $1 WHERE company_code = $2 AND "${pkColumn}" = $3`,
|
||||
`UPDATE "${task.targetTable}" SET "${task.targetColumn}" = $1${autoUpdated} WHERE company_code = $2 AND "${pkColumn}" = $3`,
|
||||
[resolved, companyCode, lookupValues[i]],
|
||||
);
|
||||
processedCount++;
|
||||
@@ -311,9 +353,10 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
||||
|
||||
const caseSql = `CASE WHEN COALESCE("${task.compareColumn}"::numeric, 0) ${op} COALESCE("${task.compareWith}"::numeric, 0) THEN $1 ELSE $2 END`;
|
||||
|
||||
const autoUpdatedDb = task.targetColumn !== "updated_date" ? `, "updated_date" = NOW()` : "";
|
||||
const placeholders = lookupValues.map((_, i) => `$${i + 4}`).join(", ");
|
||||
await client.query(
|
||||
`UPDATE "${task.targetTable}" SET "${task.targetColumn}" = ${caseSql} WHERE company_code = $3 AND "${pkColumn}" IN (${placeholders})`,
|
||||
`UPDATE "${task.targetTable}" SET "${task.targetColumn}" = ${caseSql}${autoUpdatedDb} WHERE company_code = $3 AND "${pkColumn}" IN (${placeholders})`,
|
||||
[thenVal, elseVal, companyCode, ...lookupValues],
|
||||
);
|
||||
processedCount += lookupValues.length;
|
||||
@@ -325,7 +368,14 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
||||
if (valSource === "linked") {
|
||||
value = item[task.sourceField ?? ""] ?? null;
|
||||
} else {
|
||||
value = task.fixedValue ?? "";
|
||||
const raw = task.fixedValue ?? "";
|
||||
if (raw === "__CURRENT_USER__") {
|
||||
value = userId;
|
||||
} else if (raw === "__CURRENT_TIME__") {
|
||||
value = new Date().toISOString();
|
||||
} else {
|
||||
value = raw;
|
||||
}
|
||||
}
|
||||
|
||||
let setSql: string;
|
||||
@@ -341,8 +391,9 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
||||
setSql = `"${task.targetColumn}" = $1`;
|
||||
}
|
||||
|
||||
const autoUpdatedDate = task.targetColumn !== "updated_date" ? `, "updated_date" = NOW()` : "";
|
||||
await client.query(
|
||||
`UPDATE "${task.targetTable}" SET ${setSql} WHERE company_code = $2 AND "${pkColumn}" = $3`,
|
||||
`UPDATE "${task.targetTable}" SET ${setSql}${autoUpdatedDate} WHERE company_code = $2 AND "${pkColumn}" = $3`,
|
||||
[value, companyCode, lookupValues[i]],
|
||||
);
|
||||
processedCount++;
|
||||
@@ -448,6 +499,31 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
||||
throw new Error(`유효하지 않은 테이블명: ${cardMapping.targetTable}`);
|
||||
}
|
||||
|
||||
const allAutoGen = [
|
||||
...(fieldMapping?.autoGenMappings ?? []),
|
||||
...(cardMapping?.autoGenMappings ?? []),
|
||||
];
|
||||
|
||||
// 일괄 채번: shareAcrossItems=true인 매핑은 루프 전 1회만 채번
|
||||
const sharedCodes: Record<string, string> = {};
|
||||
for (const ag of allAutoGen) {
|
||||
if (!ag.shareAcrossItems) continue;
|
||||
if (!ag.numberingRuleId || !ag.targetColumn) continue;
|
||||
if (!isSafeIdentifier(ag.targetColumn)) continue;
|
||||
try {
|
||||
const code = await numberingRuleService.allocateCode(
|
||||
ag.numberingRuleId, companyCode, { ...fieldValues, ...(items[0] ?? {}) },
|
||||
);
|
||||
sharedCodes[ag.targetColumn] = code;
|
||||
generatedCodes.push({ targetColumn: ag.targetColumn, code, showResultModal: ag.showResultModal ?? false });
|
||||
logger.info("[pop/execute-action] 일괄 채번 완료", {
|
||||
ruleId: ag.numberingRuleId, targetColumn: ag.targetColumn, code,
|
||||
});
|
||||
} catch (err: any) {
|
||||
logger.error("[pop/execute-action] 일괄 채번 실패", { ruleId: ag.numberingRuleId, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
const columns: string[] = ["company_code"];
|
||||
const values: unknown[] = [companyCode];
|
||||
@@ -467,7 +543,6 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
||||
}
|
||||
}
|
||||
|
||||
// 숨은 필드 매핑 처리 (고정값 / JSON추출 / DB컬럼)
|
||||
const allHidden = [
|
||||
...(fieldMapping?.hiddenMappings ?? []),
|
||||
...(cardMapping?.hiddenMappings ?? []),
|
||||
@@ -494,37 +569,44 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
// 채번 규칙 실행: field + cardList의 autoGenMappings에서 코드 발급
|
||||
const allAutoGen = [
|
||||
...(fieldMapping?.autoGenMappings ?? []),
|
||||
...(cardMapping?.autoGenMappings ?? []),
|
||||
];
|
||||
for (const ag of allAutoGen) {
|
||||
if (!ag.numberingRuleId || !ag.targetColumn) continue;
|
||||
if (!isSafeIdentifier(ag.targetColumn)) continue;
|
||||
if (columns.includes(`"${ag.targetColumn}"`)) continue;
|
||||
try {
|
||||
const generatedCode = await numberingRuleService.allocateCode(
|
||||
ag.numberingRuleId,
|
||||
companyCode,
|
||||
{ ...fieldValues, ...item },
|
||||
);
|
||||
|
||||
if (ag.shareAcrossItems && sharedCodes[ag.targetColumn]) {
|
||||
columns.push(`"${ag.targetColumn}"`);
|
||||
values.push(generatedCode);
|
||||
generatedCodes.push({ targetColumn: ag.targetColumn, code: generatedCode, showResultModal: ag.showResultModal ?? false });
|
||||
logger.info("[pop/execute-action] 채번 완료", {
|
||||
ruleId: ag.numberingRuleId,
|
||||
targetColumn: ag.targetColumn,
|
||||
generatedCode,
|
||||
});
|
||||
} catch (err: any) {
|
||||
logger.error("[pop/execute-action] 채번 실패", {
|
||||
ruleId: ag.numberingRuleId,
|
||||
error: err.message,
|
||||
});
|
||||
values.push(sharedCodes[ag.targetColumn]);
|
||||
} else if (!ag.shareAcrossItems) {
|
||||
try {
|
||||
const generatedCode = await numberingRuleService.allocateCode(
|
||||
ag.numberingRuleId, companyCode, { ...fieldValues, ...item },
|
||||
);
|
||||
columns.push(`"${ag.targetColumn}"`);
|
||||
values.push(generatedCode);
|
||||
generatedCodes.push({ targetColumn: ag.targetColumn, code: generatedCode, showResultModal: ag.showResultModal ?? false });
|
||||
logger.info("[pop/execute-action] 채번 완료", {
|
||||
ruleId: ag.numberingRuleId, targetColumn: ag.targetColumn, generatedCode,
|
||||
});
|
||||
} catch (err: any) {
|
||||
logger.error("[pop/execute-action] 채번 실패", { ruleId: ag.numberingRuleId, error: err.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!columns.includes('"created_date"')) {
|
||||
columns.push('"created_date"');
|
||||
values.push(new Date().toISOString());
|
||||
}
|
||||
if (!columns.includes('"updated_date"')) {
|
||||
columns.push('"updated_date"');
|
||||
values.push(new Date().toISOString());
|
||||
}
|
||||
if (!columns.includes('"writer"') && userId) {
|
||||
columns.push('"writer"');
|
||||
values.push(userId);
|
||||
}
|
||||
|
||||
if (columns.length > 1) {
|
||||
const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
|
||||
const sql = `INSERT INTO "${cardMapping.targetTable}" (${columns.join(", ")}) VALUES (${placeholders})`;
|
||||
@@ -558,6 +640,19 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
||||
values.push(fieldValues[sourceField] ?? null);
|
||||
}
|
||||
|
||||
if (!columns.includes('"created_date"')) {
|
||||
columns.push('"created_date"');
|
||||
values.push(new Date().toISOString());
|
||||
}
|
||||
if (!columns.includes('"updated_date"')) {
|
||||
columns.push('"updated_date"');
|
||||
values.push(new Date().toISOString());
|
||||
}
|
||||
if (!columns.includes('"writer"') && userId) {
|
||||
columns.push('"writer"');
|
||||
values.push(userId);
|
||||
}
|
||||
|
||||
if (columns.length > 1) {
|
||||
const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
|
||||
const sql = `INSERT INTO "${fieldMapping.targetTable}" (${columns.join(", ")}) VALUES (${placeholders})`;
|
||||
@@ -609,16 +704,18 @@ router.post("/execute-action", authenticateToken, async (req: Request, res: Resp
|
||||
}
|
||||
|
||||
if (valueType === "fixed") {
|
||||
const autoUpd = rule.targetColumn !== "updated_date" ? `, "updated_date" = NOW()` : "";
|
||||
const placeholders = lookupValues.map((_, i) => `$${i + 3}`).join(", ");
|
||||
const sql = `UPDATE "${rule.targetTable}" SET "${rule.targetColumn}" = $1 WHERE company_code = $2 AND "${pkColumn}" IN (${placeholders})`;
|
||||
const sql = `UPDATE "${rule.targetTable}" SET "${rule.targetColumn}" = $1${autoUpd} WHERE company_code = $2 AND "${pkColumn}" IN (${placeholders})`;
|
||||
await client.query(sql, [fixedValue, companyCode, ...lookupValues]);
|
||||
processedCount += lookupValues.length;
|
||||
} else {
|
||||
for (let i = 0; i < lookupValues.length; i++) {
|
||||
const item = items[i] ?? {};
|
||||
const resolvedValue = resolveStatusValue(valueType, fixedValue, rule.conditionalValue, item);
|
||||
const autoUpd2 = rule.targetColumn !== "updated_date" ? `, "updated_date" = NOW()` : "";
|
||||
await client.query(
|
||||
`UPDATE "${rule.targetTable}" SET "${rule.targetColumn}" = $1 WHERE company_code = $2 AND "${pkColumn}" = $3`,
|
||||
`UPDATE "${rule.targetTable}" SET "${rule.targetColumn}" = $1${autoUpd2} WHERE company_code = $2 AND "${pkColumn}" = $3`,
|
||||
[resolvedValue, companyCode, lookupValues[i]]
|
||||
);
|
||||
processedCount++;
|
||||
|
||||
@@ -621,6 +621,74 @@ export class AdminService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POP 메뉴 목록 조회
|
||||
* menu_name_kor에 'POP'이 포함되거나 menu_desc에 [POP] 태그가 있는 L1 메뉴의 하위 active 메뉴를 반환
|
||||
* [POP_LANDING] 태그가 있는 하위 메뉴를 landingMenu로 별도 반환
|
||||
*/
|
||||
static async getPopMenuList(paramMap: any): Promise<{ parentMenu: any | null; childMenus: any[]; landingMenu: any | null }> {
|
||||
try {
|
||||
const { userCompanyCode, userType } = paramMap;
|
||||
logger.info("AdminService.getPopMenuList 시작", { userCompanyCode, userType });
|
||||
|
||||
let queryParams: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
let companyFilter = "";
|
||||
if (userType === "SUPER_ADMIN" && userCompanyCode === "*") {
|
||||
companyFilter = `AND COMPANY_CODE = '*'`;
|
||||
} else {
|
||||
companyFilter = `AND COMPANY_CODE = $${paramIndex}`;
|
||||
queryParams.push(userCompanyCode);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// POP L1 메뉴 조회
|
||||
const parentMenus = await query<any>(
|
||||
`SELECT OBJID, MENU_NAME_KOR, MENU_URL, MENU_DESC, SEQ, COMPANY_CODE, STATUS
|
||||
FROM MENU_INFO
|
||||
WHERE PARENT_OBJ_ID = 0
|
||||
AND MENU_TYPE = 1
|
||||
AND (
|
||||
MENU_DESC LIKE '%[POP]%'
|
||||
OR UPPER(MENU_NAME_KOR) LIKE '%POP%'
|
||||
)
|
||||
${companyFilter}
|
||||
ORDER BY SEQ
|
||||
LIMIT 1`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
if (parentMenus.length === 0) {
|
||||
logger.info("POP 메뉴 없음 (L1 POP 메뉴 미발견)");
|
||||
return { parentMenu: null, childMenus: [], landingMenu: null };
|
||||
}
|
||||
|
||||
const parentMenu = parentMenus[0];
|
||||
|
||||
// 하위 active 메뉴 조회 (부모와 같은 company_code로 필터링)
|
||||
const childMenus = await query<any>(
|
||||
`SELECT OBJID, MENU_NAME_KOR, MENU_URL, MENU_DESC, SEQ, COMPANY_CODE, STATUS
|
||||
FROM MENU_INFO
|
||||
WHERE PARENT_OBJ_ID = $1
|
||||
AND STATUS = 'active'
|
||||
AND COMPANY_CODE = $2
|
||||
ORDER BY SEQ`,
|
||||
[parentMenu.objid, parentMenu.company_code]
|
||||
);
|
||||
|
||||
// [POP_LANDING] 태그가 있는 메뉴를 랜딩 화면으로 지정
|
||||
const landingMenu = childMenus.find((m: any) => m.menu_desc?.includes("[POP_LANDING]")) || null;
|
||||
|
||||
logger.info(`POP 메뉴 조회 완료: 부모=${parentMenu.menu_name_kor}, 하위=${childMenus.length}개, 랜딩=${landingMenu?.menu_name_kor || '없음'}`);
|
||||
|
||||
return { parentMenu, childMenus, landingMenu };
|
||||
} catch (error) {
|
||||
logger.error("AdminService.getPopMenuList 오류:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 정보 조회
|
||||
*/
|
||||
|
||||
@@ -3367,22 +3367,26 @@ export class TableManagementService {
|
||||
`${safeColumn} != '${String(value).replace(/'/g, "''")}'`
|
||||
);
|
||||
break;
|
||||
case "in":
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
const values = value
|
||||
case "in": {
|
||||
const inArr = Array.isArray(value) ? value : value != null && value !== "" ? [String(value)] : [];
|
||||
if (inArr.length > 0) {
|
||||
const values = inArr
|
||||
.map((v) => `'${String(v).replace(/'/g, "''")}'`)
|
||||
.join(", ");
|
||||
filterConditions.push(`${safeColumn} IN (${values})`);
|
||||
}
|
||||
break;
|
||||
case "not_in":
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
const values = value
|
||||
}
|
||||
case "not_in": {
|
||||
const notInArr = Array.isArray(value) ? value : value != null && value !== "" ? [String(value)] : [];
|
||||
if (notInArr.length > 0) {
|
||||
const values = notInArr
|
||||
.map((v) => `'${String(v).replace(/'/g, "''")}'`)
|
||||
.join(", ");
|
||||
filterConditions.push(`${safeColumn} NOT IN (${values})`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "contains":
|
||||
filterConditions.push(
|
||||
`${safeColumn} LIKE '%${String(value).replace(/'/g, "''")}%'`
|
||||
@@ -4500,26 +4504,30 @@ export class TableManagementService {
|
||||
|
||||
const rawColumns = await query<any>(
|
||||
`SELECT
|
||||
column_name as "columnName",
|
||||
column_name as "displayName",
|
||||
data_type as "dataType",
|
||||
udt_name as "dbType",
|
||||
is_nullable as "isNullable",
|
||||
column_default as "defaultValue",
|
||||
character_maximum_length as "maxLength",
|
||||
numeric_precision as "numericPrecision",
|
||||
numeric_scale as "numericScale",
|
||||
c.column_name as "columnName",
|
||||
c.column_name as "displayName",
|
||||
c.data_type as "dataType",
|
||||
c.udt_name as "dbType",
|
||||
c.is_nullable as "isNullable",
|
||||
c.column_default as "defaultValue",
|
||||
c.character_maximum_length as "maxLength",
|
||||
c.numeric_precision as "numericPrecision",
|
||||
c.numeric_scale as "numericScale",
|
||||
CASE
|
||||
WHEN column_name IN (
|
||||
SELECT column_name FROM information_schema.key_column_usage
|
||||
WHERE table_name = $1 AND constraint_name LIKE '%_pkey'
|
||||
WHEN c.column_name IN (
|
||||
SELECT kcu.column_name FROM information_schema.key_column_usage kcu
|
||||
WHERE kcu.table_name = $1 AND kcu.constraint_name LIKE '%_pkey'
|
||||
) THEN true
|
||||
ELSE false
|
||||
END as "isPrimaryKey"
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = $1
|
||||
AND table_schema = 'public'
|
||||
ORDER BY ordinal_position`,
|
||||
END as "isPrimaryKey",
|
||||
col_description(
|
||||
(SELECT oid FROM pg_class WHERE relname = $1 AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')),
|
||||
c.ordinal_position
|
||||
) as "columnComment"
|
||||
FROM information_schema.columns c
|
||||
WHERE c.table_name = $1
|
||||
AND c.table_schema = 'public'
|
||||
ORDER BY c.ordinal_position`,
|
||||
[tableName]
|
||||
);
|
||||
|
||||
@@ -4529,10 +4537,10 @@ export class TableManagementService {
|
||||
displayName: col.displayName,
|
||||
dataType: col.dataType,
|
||||
dbType: col.dbType,
|
||||
webType: "text", // 기본값
|
||||
webType: "text",
|
||||
inputType: "direct",
|
||||
detailSettings: "{}",
|
||||
description: "", // 필수 필드 추가
|
||||
description: col.columnComment || "",
|
||||
isNullable: col.isNullable,
|
||||
isPrimaryKey: col.isPrimaryKey,
|
||||
defaultValue: col.defaultValue,
|
||||
@@ -4543,6 +4551,7 @@ export class TableManagementService {
|
||||
numericScale: col.numericScale ? Number(col.numericScale) : undefined,
|
||||
displayOrder: 0,
|
||||
isVisible: true,
|
||||
columnComment: col.columnComment || "",
|
||||
}));
|
||||
|
||||
logger.info(
|
||||
|
||||
@@ -98,23 +98,27 @@ export function buildDataFilterWhereClause(
|
||||
paramIndex++;
|
||||
break;
|
||||
|
||||
case "in":
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
const placeholders = value.map((_, idx) => `$${paramIndex + idx}`).join(", ");
|
||||
case "in": {
|
||||
const inArr = Array.isArray(value) ? value : value != null && value !== "" ? [String(value)] : [];
|
||||
if (inArr.length > 0) {
|
||||
const placeholders = inArr.map((_, idx) => `$${paramIndex + idx}`).join(", ");
|
||||
conditions.push(`${columnRef} IN (${placeholders})`);
|
||||
params.push(...value);
|
||||
paramIndex += value.length;
|
||||
params.push(...inArr);
|
||||
paramIndex += inArr.length;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "not_in":
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
const placeholders = value.map((_, idx) => `$${paramIndex + idx}`).join(", ");
|
||||
case "not_in": {
|
||||
const notInArr = Array.isArray(value) ? value : value != null && value !== "" ? [String(value)] : [];
|
||||
if (notInArr.length > 0) {
|
||||
const placeholders = notInArr.map((_, idx) => `$${paramIndex + idx}`).join(", ");
|
||||
conditions.push(`${columnRef} NOT IN (${placeholders})`);
|
||||
params.push(...value);
|
||||
paramIndex += value.length;
|
||||
params.push(...notInArr);
|
||||
paramIndex += notInArr.length;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "contains":
|
||||
conditions.push(`${columnRef} LIKE $${paramIndex}`);
|
||||
|
||||
Reference in New Issue
Block a user