2025-11-07 10:18:34 +09:00
|
|
|
import { Response } from "express";
|
|
|
|
|
import { AuthenticatedRequest } from "../types/auth";
|
2025-11-05 15:23:57 +09:00
|
|
|
import tableCategoryValueService from "../services/tableCategoryValueService";
|
|
|
|
|
import { logger } from "../utils/logger";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 테이블의 카테고리 컬럼 목록 조회
|
|
|
|
|
*/
|
2025-11-07 10:18:34 +09:00
|
|
|
export const getCategoryColumns = async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-05 15:23:57 +09:00
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const { tableName } = req.params;
|
|
|
|
|
|
|
|
|
|
const columns = await tableCategoryValueService.getCategoryColumns(
|
|
|
|
|
tableName,
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: columns,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`카테고리 컬럼 조회 실패: ${error.message}`);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 컬럼 조회 중 오류가 발생했습니다",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 값 목록 조회 (메뉴 스코프 적용)
|
2025-11-11 14:32:00 +09:00
|
|
|
*
|
|
|
|
|
* Query Parameters:
|
|
|
|
|
* - menuObjid: 메뉴 OBJID (선택사항, 제공 시 형제 메뉴의 카테고리 값 포함)
|
|
|
|
|
* - includeInactive: 비활성 값 포함 여부
|
2025-11-05 15:23:57 +09:00
|
|
|
*/
|
2025-11-07 10:18:34 +09:00
|
|
|
export const getCategoryValues = async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-05 15:23:57 +09:00
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const { tableName, columnName } = req.params;
|
|
|
|
|
const includeInactive = req.query.includeInactive === "true";
|
2025-11-11 14:32:00 +09:00
|
|
|
const menuObjid = req.query.menuObjid ? Number(req.query.menuObjid) : undefined;
|
|
|
|
|
|
|
|
|
|
logger.info("카테고리 값 조회 요청", {
|
|
|
|
|
tableName,
|
|
|
|
|
columnName,
|
|
|
|
|
menuObjid,
|
|
|
|
|
companyCode,
|
|
|
|
|
});
|
2025-11-05 15:23:57 +09:00
|
|
|
|
|
|
|
|
const values = await tableCategoryValueService.getCategoryValues(
|
|
|
|
|
tableName,
|
|
|
|
|
columnName,
|
|
|
|
|
companyCode,
|
2025-11-11 14:32:00 +09:00
|
|
|
includeInactive,
|
|
|
|
|
menuObjid // ← menuObjid 전달
|
2025-11-05 15:23:57 +09:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: values,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`카테고리 값 조회 실패: ${error.message}`);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 조회 중 오류가 발생했습니다",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-11 14:32:00 +09:00
|
|
|
* 카테고리 값 추가 (메뉴 스코프)
|
|
|
|
|
*
|
|
|
|
|
* Body:
|
|
|
|
|
* - menuObjid: 메뉴 OBJID (필수)
|
|
|
|
|
* - 나머지 카테고리 값 정보
|
2025-11-05 15:23:57 +09:00
|
|
|
*/
|
2025-11-07 10:18:34 +09:00
|
|
|
export const addCategoryValue = async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-05 15:23:57 +09:00
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const userId = req.user!.userId;
|
2025-11-11 14:32:00 +09:00
|
|
|
const { menuObjid, ...value } = req.body;
|
|
|
|
|
|
|
|
|
|
if (!menuObjid) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "menuObjid는 필수입니다",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info("카테고리 값 추가 요청", {
|
|
|
|
|
tableName: value.tableName,
|
|
|
|
|
columnName: value.columnName,
|
|
|
|
|
menuObjid,
|
|
|
|
|
companyCode,
|
|
|
|
|
});
|
2025-11-05 15:23:57 +09:00
|
|
|
|
|
|
|
|
const newValue = await tableCategoryValueService.addCategoryValue(
|
|
|
|
|
value,
|
|
|
|
|
companyCode,
|
2025-11-11 14:32:00 +09:00
|
|
|
userId,
|
|
|
|
|
Number(menuObjid) // ← menuObjid 전달
|
2025-11-05 15:23:57 +09:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.status(201).json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: newValue,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`카테고리 값 추가 실패: ${error.message}`);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || "카테고리 값 추가 중 오류가 발생했습니다",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 값 수정
|
|
|
|
|
*/
|
2025-11-07 10:18:34 +09:00
|
|
|
export const updateCategoryValue = async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-05 15:23:57 +09:00
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const userId = req.user!.userId;
|
|
|
|
|
const valueId = parseInt(req.params.valueId);
|
|
|
|
|
const updates = req.body;
|
|
|
|
|
|
|
|
|
|
if (isNaN(valueId)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "유효하지 않은 값 ID입니다",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updatedValue = await tableCategoryValueService.updateCategoryValue(
|
|
|
|
|
valueId,
|
|
|
|
|
updates,
|
|
|
|
|
companyCode,
|
|
|
|
|
userId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: updatedValue,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`카테고리 값 수정 실패: ${error.message}`);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 수정 중 오류가 발생했습니다",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 값 삭제
|
|
|
|
|
*/
|
2025-11-07 10:18:34 +09:00
|
|
|
export const deleteCategoryValue = async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-05 15:23:57 +09:00
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const userId = req.user!.userId;
|
|
|
|
|
const valueId = parseInt(req.params.valueId);
|
|
|
|
|
|
|
|
|
|
if (isNaN(valueId)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "유효하지 않은 값 ID입니다",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await tableCategoryValueService.deleteCategoryValue(
|
|
|
|
|
valueId,
|
|
|
|
|
companyCode,
|
|
|
|
|
userId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: "카테고리 값이 삭제되었습니다",
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`카테고리 값 삭제 실패: ${error.message}`);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || "카테고리 값 삭제 중 오류가 발생했습니다",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 값 일괄 삭제
|
|
|
|
|
*/
|
|
|
|
|
export const bulkDeleteCategoryValues = async (
|
2025-11-07 10:22:49 +09:00
|
|
|
req: AuthenticatedRequest,
|
2025-11-05 15:23:57 +09:00
|
|
|
res: Response
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const userId = req.user!.userId;
|
|
|
|
|
const { valueIds } = req.body;
|
|
|
|
|
|
|
|
|
|
if (!Array.isArray(valueIds) || valueIds.length === 0) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "삭제할 값 ID 목록이 필요합니다",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await tableCategoryValueService.bulkDeleteCategoryValues(
|
|
|
|
|
valueIds,
|
|
|
|
|
companyCode,
|
|
|
|
|
userId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: `${valueIds.length}개의 카테고리 값이 삭제되었습니다`,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`카테고리 값 일괄 삭제 실패: ${error.message}`);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 일괄 삭제 중 오류가 발생했습니다",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 카테고리 값 순서 변경
|
|
|
|
|
*/
|
2025-11-07 10:18:34 +09:00
|
|
|
export const reorderCategoryValues = async (req: AuthenticatedRequest, res: Response) => {
|
2025-11-05 15:23:57 +09:00
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const { orderedValueIds } = req.body;
|
|
|
|
|
|
|
|
|
|
if (!Array.isArray(orderedValueIds) || orderedValueIds.length === 0) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "순서 정보가 필요합니다",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await tableCategoryValueService.reorderCategoryValues(
|
|
|
|
|
orderedValueIds,
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: "카테고리 값 순서가 변경되었습니다",
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`카테고리 값 순서 변경 실패: ${error.message}`);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "카테고리 값 순서 변경 중 오류가 발생했습니다",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-13 12:17:10 +09:00
|
|
|
// ================================================
|
|
|
|
|
// 컬럼 매핑 관련 API (논리명 ↔ 물리명)
|
|
|
|
|
// ================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 컬럼 매핑 조회
|
|
|
|
|
*
|
|
|
|
|
* GET /api/categories/column-mapping/:tableName/:menuObjid
|
|
|
|
|
*
|
|
|
|
|
* 특정 테이블과 메뉴에 대한 논리적 컬럼명 → 물리적 컬럼명 매핑을 조회합니다.
|
|
|
|
|
*
|
|
|
|
|
* @returns { logical_column: physical_column } 형태의 매핑 객체
|
|
|
|
|
*/
|
|
|
|
|
export const getColumnMapping = async (req: AuthenticatedRequest, res: Response) => {
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const { tableName, menuObjid } = req.params;
|
|
|
|
|
|
|
|
|
|
if (!tableName || !menuObjid) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "tableName과 menuObjid는 필수입니다",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info("컬럼 매핑 조회", {
|
|
|
|
|
tableName,
|
|
|
|
|
menuObjid,
|
|
|
|
|
companyCode,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mapping = await tableCategoryValueService.getColumnMapping(
|
|
|
|
|
tableName,
|
|
|
|
|
Number(menuObjid),
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: mapping,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`컬럼 매핑 조회 실패: ${error.message}`);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "컬럼 매핑 조회 중 오류가 발생했습니다",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 컬럼 매핑 생성/수정
|
|
|
|
|
*
|
|
|
|
|
* POST /api/categories/column-mapping
|
|
|
|
|
*
|
|
|
|
|
* Body:
|
|
|
|
|
* - tableName: 테이블명
|
|
|
|
|
* - logicalColumnName: 논리적 컬럼명 (예: status_stock)
|
|
|
|
|
* - physicalColumnName: 물리적 컬럼명 (예: status)
|
|
|
|
|
* - menuObjid: 메뉴 OBJID
|
|
|
|
|
* - description: 설명 (선택사항)
|
|
|
|
|
*/
|
|
|
|
|
export const createColumnMapping = async (req: AuthenticatedRequest, res: Response) => {
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const userId = req.user!.userId;
|
|
|
|
|
const {
|
|
|
|
|
tableName,
|
|
|
|
|
logicalColumnName,
|
|
|
|
|
physicalColumnName,
|
|
|
|
|
menuObjid,
|
|
|
|
|
description,
|
|
|
|
|
} = req.body;
|
|
|
|
|
|
|
|
|
|
// 입력 검증
|
|
|
|
|
if (!tableName || !logicalColumnName || !physicalColumnName || !menuObjid) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "tableName, logicalColumnName, physicalColumnName, menuObjid는 필수입니다",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info("컬럼 매핑 생성", {
|
|
|
|
|
tableName,
|
|
|
|
|
logicalColumnName,
|
|
|
|
|
physicalColumnName,
|
|
|
|
|
menuObjid,
|
|
|
|
|
companyCode,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mapping = await tableCategoryValueService.createColumnMapping(
|
|
|
|
|
tableName,
|
|
|
|
|
logicalColumnName,
|
|
|
|
|
physicalColumnName,
|
|
|
|
|
Number(menuObjid),
|
|
|
|
|
companyCode,
|
|
|
|
|
userId,
|
|
|
|
|
description
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.status(201).json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: mapping,
|
|
|
|
|
message: "컬럼 매핑이 생성되었습니다",
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`컬럼 매핑 생성 실패: ${error.message}`);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || "컬럼 매핑 생성 중 오류가 발생했습니다",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 논리적 컬럼 목록 조회
|
|
|
|
|
*
|
|
|
|
|
* GET /api/categories/logical-columns/:tableName/:menuObjid
|
|
|
|
|
*
|
|
|
|
|
* 특정 테이블과 메뉴에 대한 논리적 컬럼 목록을 조회합니다.
|
|
|
|
|
* (카테고리 값 추가 시 컬럼 선택용)
|
|
|
|
|
*/
|
|
|
|
|
export const getLogicalColumns = async (req: AuthenticatedRequest, res: Response) => {
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const { tableName, menuObjid } = req.params;
|
|
|
|
|
|
|
|
|
|
if (!tableName || !menuObjid) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "tableName과 menuObjid는 필수입니다",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info("논리적 컬럼 목록 조회", {
|
|
|
|
|
tableName,
|
|
|
|
|
menuObjid,
|
|
|
|
|
companyCode,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const columns = await tableCategoryValueService.getLogicalColumns(
|
|
|
|
|
tableName,
|
|
|
|
|
Number(menuObjid),
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: columns,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`논리적 컬럼 목록 조회 실패: ${error.message}`);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "논리적 컬럼 목록 조회 중 오류가 발생했습니다",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 컬럼 매핑 삭제
|
|
|
|
|
*
|
|
|
|
|
* DELETE /api/categories/column-mapping/:mappingId
|
|
|
|
|
*/
|
|
|
|
|
export const deleteColumnMapping = async (req: AuthenticatedRequest, res: Response) => {
|
|
|
|
|
try {
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
const { mappingId } = req.params;
|
|
|
|
|
|
|
|
|
|
if (!mappingId) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: "mappingId는 필수입니다",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info("컬럼 매핑 삭제", {
|
|
|
|
|
mappingId,
|
|
|
|
|
companyCode,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await tableCategoryValueService.deleteColumnMapping(
|
|
|
|
|
Number(mappingId),
|
|
|
|
|
companyCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: "컬럼 매핑이 삭제되었습니다",
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
logger.error(`컬럼 매핑 삭제 실패: ${error.message}`);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: error.message || "컬럼 매핑 삭제 중 오류가 발생했습니다",
|
|
|
|
|
error: error.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|