ERP-node/backend-node/src/routes/dataRoutes.ts

1035 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import express from "express";
import { dataService } from "../services/dataService";
import { masterDetailExcelService } from "../services/masterDetailExcelService";
import { authenticateToken } from "../middleware/authMiddleware";
import { AuthenticatedRequest } from "../types/auth";
const router = express.Router();
// ================================
// 마스터-디테일 엑셀 API
// ================================
/**
* 마스터-디테일 관계 정보 조회
* GET /api/data/master-detail/relation/:screenId
*/
router.get(
"/master-detail/relation/:screenId",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { screenId } = req.params;
if (!screenId || isNaN(parseInt(screenId))) {
return res.status(400).json({
success: false,
message: "유효한 screenId가 필요합니다.",
});
}
console.log(`🔍 마스터-디테일 관계 조회: screenId=${screenId}`);
const relation = await masterDetailExcelService.getMasterDetailRelation(
parseInt(screenId)
);
if (!relation) {
return res.json({
success: true,
data: null,
message: "마스터-디테일 구조가 아닙니다.",
});
}
console.log(`✅ 마스터-디테일 관계 발견:`, {
masterTable: relation.masterTable,
detailTable: relation.detailTable,
joinKey: relation.masterKeyColumn,
});
return res.json({
success: true,
data: relation,
});
} catch (error: any) {
console.error("마스터-디테일 관계 조회 오류:", error);
return res.status(500).json({
success: false,
message: "마스터-디테일 관계 조회 중 오류가 발생했습니다.",
error: error.message,
});
}
}
);
/**
* 마스터-디테일 엑셀 다운로드 데이터 조회
* POST /api/data/master-detail/download
*/
router.post(
"/master-detail/download",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { screenId, filters } = req.body;
const companyCode = req.user?.companyCode || "*";
if (!screenId) {
return res.status(400).json({
success: false,
message: "screenId가 필요합니다.",
});
}
console.log(`📥 마스터-디테일 엑셀 다운로드: screenId=${screenId}`);
// 1. 마스터-디테일 관계 조회
const relation = await masterDetailExcelService.getMasterDetailRelation(
parseInt(screenId)
);
if (!relation) {
return res.status(400).json({
success: false,
message: "마스터-디테일 구조가 아닙니다.",
});
}
// 2. JOIN 데이터 조회
const data = await masterDetailExcelService.getJoinedData(
relation,
companyCode,
filters
);
console.log(`✅ 마스터-디테일 데이터 조회 완료: ${data.data.length}`);
return res.json({
success: true,
data,
});
} catch (error: any) {
console.error("마스터-디테일 다운로드 오류:", error);
return res.status(500).json({
success: false,
message: "마스터-디테일 다운로드 중 오류가 발생했습니다.",
error: error.message,
});
}
}
);
/**
* 마스터-디테일 엑셀 업로드
* POST /api/data/master-detail/upload
*/
router.post(
"/master-detail/upload",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { screenId, data } = req.body;
const companyCode = req.user?.companyCode || "*";
const userId = req.user?.userId;
if (!screenId || !data || !Array.isArray(data)) {
return res.status(400).json({
success: false,
message: "screenId와 data 배열이 필요합니다.",
});
}
console.log(`📤 마스터-디테일 엑셀 업로드: screenId=${screenId}, rows=${data.length}`);
// 1. 마스터-디테일 관계 조회
const relation = await masterDetailExcelService.getMasterDetailRelation(
parseInt(screenId)
);
if (!relation) {
return res.status(400).json({
success: false,
message: "마스터-디테일 구조가 아닙니다.",
});
}
// 2. 데이터 업로드
const result = await masterDetailExcelService.uploadJoinedData(
relation,
data,
companyCode,
userId
);
console.log(`✅ 마스터-디테일 업로드 완료:`, {
masterInserted: result.masterInserted,
masterUpdated: result.masterUpdated,
detailInserted: result.detailInserted,
errors: result.errors.length,
});
return res.json({
success: result.success,
data: result,
message: result.success
? `마스터 ${result.masterInserted + result.masterUpdated}건, 디테일 ${result.detailInserted}건 처리되었습니다.`
: "업로드 중 오류가 발생했습니다.",
});
} catch (error: any) {
console.error("마스터-디테일 업로드 오류:", error);
return res.status(500).json({
success: false,
message: "마스터-디테일 업로드 중 오류가 발생했습니다.",
error: error.message,
});
}
}
);
/**
* 마스터-디테일 간단 모드 엑셀 업로드
* - 마스터 정보는 UI에서 선택
* - 디테일 정보만 엑셀에서 업로드
* - 채번 규칙을 통해 마스터 키 자동 생성
*
* POST /api/data/master-detail/upload-simple
*/
router.post(
"/master-detail/upload-simple",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { screenId, detailData, masterFieldValues, numberingRuleId } = req.body;
const companyCode = req.user?.companyCode || "*";
const userId = req.user?.userId || "system";
if (!screenId || !detailData || !Array.isArray(detailData)) {
return res.status(400).json({
success: false,
message: "screenId와 detailData 배열이 필요합니다.",
});
}
console.log(`📤 마스터-디테일 간단 모드 업로드: screenId=${screenId}, rows=${detailData.length}`);
console.log(` 마스터 필드 값:`, masterFieldValues);
console.log(` 채번 규칙 ID:`, numberingRuleId);
// 업로드 실행
const result = await masterDetailExcelService.uploadSimple(
parseInt(screenId),
detailData,
masterFieldValues || {},
numberingRuleId,
companyCode,
userId
);
console.log(`✅ 마스터-디테일 간단 모드 업로드 완료:`, {
masterInserted: result.masterInserted,
detailInserted: result.detailInserted,
generatedKey: result.generatedKey,
errors: result.errors.length,
});
return res.json({
success: result.success,
data: result,
message: result.success
? `마스터 1건(${result.generatedKey}), 디테일 ${result.detailInserted}건 처리되었습니다.`
: "업로드 중 오류가 발생했습니다.",
});
} catch (error: any) {
console.error("마스터-디테일 간단 모드 업로드 오류:", error);
return res.status(500).json({
success: false,
message: "마스터-디테일 업로드 중 오류가 발생했습니다.",
error: error.message,
});
}
}
);
// ================================
// 기존 데이터 API
// ================================
/**
* 조인 데이터 조회 API (다른 라우트보다 먼저 정의)
* GET /api/data/join?leftTable=...&rightTable=...&leftColumn=...&rightColumn=...&leftValue=...
*/
router.get(
"/join",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const {
leftTable,
rightTable,
leftColumn,
rightColumn,
leftValue,
dataFilter,
enableEntityJoin,
displayColumns,
deduplication,
} = req.query;
// 입력값 검증
if (!leftTable || !rightTable || !leftColumn || !rightColumn) {
return res.status(400).json({
success: false,
message:
"필수 파라미터가 누락되었습니다 (leftTable, rightTable, leftColumn, rightColumn).",
error: "MISSING_PARAMETERS",
});
}
// dataFilter 파싱 (JSON 문자열로 전달됨)
let parsedDataFilter = undefined;
if (dataFilter && typeof dataFilter === "string") {
try {
parsedDataFilter = JSON.parse(dataFilter);
} catch (error) {
console.error("dataFilter 파싱 오류:", error);
}
}
// 🆕 enableEntityJoin 파싱
const enableEntityJoinFlag =
enableEntityJoin === "true" ||
(typeof enableEntityJoin === "boolean" && enableEntityJoin);
// SQL 인젝션 방지를 위한 검증
const tables = [leftTable as string, rightTable as string];
const columns = [leftColumn as string, rightColumn as string];
for (const table of tables) {
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {
return res.status(400).json({
success: false,
message: `유효하지 않은 테이블명입니다: ${table}`,
error: "INVALID_TABLE_NAME",
});
}
}
for (const column of columns) {
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(column)) {
return res.status(400).json({
success: false,
message: `유효하지 않은 컬럼명입니다: ${column}`,
error: "INVALID_COLUMN_NAME",
});
}
}
// 회사 코드 추출 (멀티테넌시 필터링)
const userCompany = req.user?.companyCode;
// displayColumns 파싱 (item_info.item_name 등)
let parsedDisplayColumns:
| Array<{ name: string; label?: string }>
| undefined;
if (displayColumns) {
try {
parsedDisplayColumns = JSON.parse(displayColumns as string);
} catch (e) {
console.error("displayColumns 파싱 실패:", e);
}
}
// 🆕 deduplication 파싱
let parsedDeduplication:
| {
enabled: boolean;
groupByColumn: string;
keepStrategy: "latest" | "earliest" | "base_price" | "current_date";
sortColumn?: string;
}
| undefined;
if (deduplication) {
try {
parsedDeduplication = JSON.parse(deduplication as string);
} catch (e) {
console.error("deduplication 파싱 실패:", e);
}
}
console.log(`🔗 조인 데이터 조회:`, {
leftTable,
rightTable,
leftColumn,
rightColumn,
leftValue,
userCompany,
dataFilter: parsedDataFilter,
enableEntityJoin: enableEntityJoinFlag,
displayColumns: parsedDisplayColumns, // 🆕 표시 컬럼 로그
deduplication: parsedDeduplication, // 🆕 중복 제거 로그
});
// 조인 데이터 조회 (회사 코드 + 데이터 필터 + Entity 조인 + 표시 컬럼 + 중복 제거 전달)
const result = await dataService.getJoinedData(
leftTable as string,
rightTable as string,
leftColumn as string,
rightColumn as string,
leftValue as string,
userCompany,
parsedDataFilter,
enableEntityJoinFlag,
parsedDisplayColumns, // 🆕 표시 컬럼 전달
parsedDeduplication // 🆕 중복 제거 설정 전달
);
if (!result.success) {
return res.status(400).json(result);
}
console.log(
`✅ 조인 데이터 조회 성공: ${result.data?.length || 0}개 항목`
);
return res.json({
success: true,
data: result.data,
});
} catch (error) {
console.error("조인 데이터 조회 오류:", error);
return res.status(500).json({
success: false,
message: "조인 데이터 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
);
/**
* 동적 테이블 데이터 조회 API
* GET /api/data/{tableName}
*/
router.get(
"/:tableName",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { tableName } = req.params;
const {
limit,
offset,
page,
size,
orderBy,
searchTerm,
sortBy,
sortOrder,
userLang,
...filters
} = req.query;
// 입력값 검증
if (!tableName || typeof tableName !== "string") {
return res.status(400).json({
success: false,
message: "테이블명이 필요합니다.",
error: "INVALID_TABLE_NAME",
});
}
// SQL 인젝션 방지를 위한 테이블명 검증
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
return res.status(400).json({
success: false,
message: "유효하지 않은 테이블명입니다.",
error: "INVALID_TABLE_NAME",
});
}
// page/size 또는 limit/offset 방식 지원
let finalLimit = 100;
let finalOffset = 0;
if (page && size) {
// page/size 방식
const pageNum = parseInt(page as string) || 1;
const sizeNum = parseInt(size as string) || 100;
finalLimit = sizeNum;
finalOffset = (pageNum - 1) * sizeNum;
} else if (limit || offset) {
// limit/offset 방식
finalLimit = parseInt(limit as string) || 10;
finalOffset = parseInt(offset as string) || 0;
}
console.log(`📊 데이터 조회 요청: ${tableName}`, {
limit: finalLimit,
offset: finalOffset,
orderBy: orderBy || sortBy,
searchTerm,
filters,
user: req.user?.userId,
});
// filters에서 searchTerm과 sortOrder 제거 (이미 별도로 처리됨)
const cleanFilters = { ...filters };
delete cleanFilters.searchTerm;
delete cleanFilters.sortOrder;
// 데이터 조회
const result = await dataService.getTableData({
tableName,
limit: finalLimit,
offset: finalOffset,
orderBy: (orderBy || sortBy) as string,
filters: cleanFilters as Record<string, string>,
userCompany: req.user?.companyCode,
});
if (!result.success) {
return res.status(400).json(result);
}
console.log(
`✅ 데이터 조회 성공: ${tableName}, ${result.data?.length || 0}개 항목`
);
// 페이징 정보 포함하여 반환
const total = result.data?.length || 0;
const responsePage =
finalLimit > 0 ? Math.floor(finalOffset / finalLimit) + 1 : 1;
const responseSize = finalLimit;
const totalPages = responseSize > 0 ? Math.ceil(total / responseSize) : 1;
return res.json({
success: true,
data: result.data,
total,
page: responsePage,
size: responseSize,
totalPages,
});
} catch (error) {
console.error("데이터 조회 오류:", error);
return res.status(500).json({
success: false,
message: "데이터 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
);
/**
* 테이블 컬럼 정보 조회 API
* GET /api/data/{tableName}/columns
*/
router.get(
"/:tableName/columns",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { tableName } = req.params;
// 입력값 검증
if (!tableName || typeof tableName !== "string") {
return res.status(400).json({
success: false,
message: "테이블명이 필요합니다.",
error: "INVALID_TABLE_NAME",
});
}
// SQL 인젝션 방지를 위한 테이블명 검증
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
return res.status(400).json({
success: false,
message: "유효하지 않은 테이블명입니다.",
error: "INVALID_TABLE_NAME",
});
}
console.log(`📋 컬럼 정보 조회: ${tableName}`);
// 컬럼 정보 조회
const result = await dataService.getTableColumns(tableName);
if (!result.success) {
return res.status(400).json(result);
}
console.log(
`✅ 컬럼 정보 조회 성공: ${tableName}, ${result.data?.length || 0}개 컬럼`
);
return res.json(result);
} catch (error) {
console.error("컬럼 정보 조회 오류:", error);
return res.status(500).json({
success: false,
message: "컬럼 정보 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
);
/**
* 레코드 상세 조회 API
* GET /api/data/{tableName}/{id}
*/
router.get(
"/:tableName/:id",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { tableName, id } = req.params;
// 입력값 검증
if (!tableName || typeof tableName !== "string") {
return res.status(400).json({
success: false,
message: "테이블명이 필요합니다.",
error: "INVALID_TABLE_NAME",
});
}
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
return res.status(400).json({
success: false,
message: "유효하지 않은 테이블명입니다.",
error: "INVALID_TABLE_NAME",
});
}
const { enableEntityJoin, groupByColumns } = req.query;
const enableEntityJoinFlag =
enableEntityJoin === "true" ||
(typeof enableEntityJoin === "boolean" && enableEntityJoin);
// groupByColumns 파싱 (JSON 문자열 또는 쉼표 구분)
let groupByColumnsArray: string[] = [];
if (groupByColumns) {
try {
if (typeof groupByColumns === "string") {
// JSON 형식이면 파싱, 아니면 쉼표로 분리
groupByColumnsArray = groupByColumns.startsWith("[")
? JSON.parse(groupByColumns)
: groupByColumns.split(",").map((c) => c.trim());
}
} catch (error) {
console.warn("groupByColumns 파싱 실패:", error);
}
}
console.log(`🔍 레코드 상세 조회: ${tableName}/${id}`, {
enableEntityJoin: enableEntityJoinFlag,
groupByColumns: groupByColumnsArray,
});
// 레코드 상세 조회 (Entity Join 옵션 + 그룹핑 옵션 포함)
const result = await dataService.getRecordDetail(
tableName,
id,
enableEntityJoinFlag,
groupByColumnsArray
);
if (!result.success) {
return res.status(400).json(result);
}
if (!result.data) {
return res.status(404).json({
success: false,
message: "레코드를 찾을 수 없습니다.",
});
}
console.log(`✅ 레코드 조회 성공: ${tableName}/${id}`);
return res.json({
success: true,
data: result.data,
});
} catch (error) {
console.error("레코드 상세 조회 오류:", error);
return res.status(500).json({
success: false,
message: "레코드 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
);
/**
* 그룹화된 데이터 UPSERT API
* POST /api/data/upsert-grouped
*
* 요청 본문:
* {
* tableName: string,
* parentKeys: { customer_id: "CUST-0002", item_id: "SLI-2025-0002" },
* records: [ { customer_item_code: "84-44", start_date: "2025-11-18", ... }, ... ]
* }
*/
router.post(
"/upsert-grouped",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { tableName, parentKeys, records } = req.body;
// 입력값 검증
if (!tableName || !parentKeys || !records || !Array.isArray(records)) {
return res.status(400).json({
success: false,
message:
"필수 파라미터가 누락되었습니다 (tableName, parentKeys, records).",
error: "MISSING_PARAMETERS",
});
}
// 테이블명 검증
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
return res.status(400).json({
success: false,
message: "유효하지 않은 테이블명입니다.",
error: "INVALID_TABLE_NAME",
});
}
console.log(`🔄 그룹화된 데이터 UPSERT: ${tableName}`, {
parentKeys,
recordCount: records.length,
userCompany: req.user?.companyCode,
userId: req.user?.userId,
});
// UPSERT 수행
const result = await dataService.upsertGroupedRecords(
tableName,
parentKeys,
records,
req.user?.companyCode,
req.user?.userId
);
if (!result.success) {
return res.status(400).json(result);
}
console.log(`✅ 그룹화된 데이터 UPSERT 성공: ${tableName}`, {
inserted: result.data?.inserted || 0,
updated: result.data?.updated || 0,
deleted: result.data?.deleted || 0,
});
return res.json({
success: true,
message: "데이터가 저장되었습니다.",
inserted: result.data?.inserted || 0,
updated: result.data?.updated || 0,
deleted: result.data?.deleted || 0,
});
} catch (error) {
console.error("그룹화된 데이터 UPSERT 오류:", error);
return res.status(500).json({
success: false,
message: "데이터 저장 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
);
/**
* 레코드 생성 API
* POST /api/data/{tableName}
*/
router.post(
"/:tableName",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { tableName } = req.params;
const data = req.body;
// 입력값 검증
if (!tableName || typeof tableName !== "string") {
return res.status(400).json({
success: false,
message: "테이블명이 필요합니다.",
error: "INVALID_TABLE_NAME",
});
}
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
return res.status(400).json({
success: false,
message: "유효하지 않은 테이블명입니다.",
error: "INVALID_TABLE_NAME",
});
}
console.log(` 레코드 생성: ${tableName}`, data);
// company_code와 company_name 자동 추가 (멀티테넌시)
const enrichedData = { ...data };
// 테이블에 company_code 컬럼이 있는지 확인하고 자동으로 추가
const hasCompanyCode = await dataService.checkColumnExists(
tableName,
"company_code"
);
if (hasCompanyCode && req.user?.companyCode) {
enrichedData.company_code = req.user.companyCode;
console.log(`🏢 company_code 자동 추가: ${req.user.companyCode}`);
}
// 테이블에 company_name 컬럼이 있는지 확인하고 자동으로 추가
const hasCompanyName = await dataService.checkColumnExists(
tableName,
"company_name"
);
if (hasCompanyName && req.user?.companyName) {
enrichedData.company_name = req.user.companyName;
console.log(`🏢 company_name 자동 추가: ${req.user.companyName}`);
}
// 레코드 생성
const result = await dataService.createRecord(tableName, enrichedData);
if (!result.success) {
return res.status(400).json(result);
}
console.log(`✅ 레코드 생성 성공: ${tableName}`);
return res.status(201).json({
success: true,
data: result.data,
message: "레코드가 생성되었습니다.",
});
} catch (error) {
console.error("레코드 생성 오류:", error);
return res.status(500).json({
success: false,
message: "레코드 생성 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
);
/**
* 레코드 수정 API
* PUT /api/data/{tableName}/{id}
*/
router.put(
"/:tableName/:id",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { tableName, id } = req.params;
const data = req.body;
// 입력값 검증
if (!tableName || typeof tableName !== "string") {
return res.status(400).json({
success: false,
message: "테이블명이 필요합니다.",
error: "INVALID_TABLE_NAME",
});
}
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
return res.status(400).json({
success: false,
message: "유효하지 않은 테이블명입니다.",
error: "INVALID_TABLE_NAME",
});
}
console.log(`✏️ 레코드 수정: ${tableName}/${id}`, data);
// 레코드 수정
const result = await dataService.updateRecord(tableName, id, data);
if (!result.success) {
return res.status(400).json(result);
}
console.log(`✅ 레코드 수정 성공: ${tableName}/${id}`);
return res.json({
success: true,
data: result.data,
message: "레코드가 수정되었습니다.",
});
} catch (error) {
console.error("레코드 수정 오류:", error);
return res.status(500).json({
success: false,
message: "레코드 수정 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
);
/**
* 레코드 삭제 API
* DELETE /api/data/{tableName}/{id}
*/
/**
* 복합키 레코드 삭제 API (POST)
* POST /api/data/:tableName/delete
* Body: { user_id: 'xxx', dept_code: 'yyy' }
*/
router.post(
"/:tableName/delete",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { tableName } = req.params;
const compositeKey = req.body;
// 입력값 검증
if (!tableName || typeof tableName !== "string") {
return res.status(400).json({
success: false,
message: "테이블명이 필요합니다.",
error: "INVALID_TABLE_NAME",
});
}
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
return res.status(400).json({
success: false,
message: "유효하지 않은 테이블명입니다.",
error: "INVALID_TABLE_NAME",
});
}
console.log(`🗑️ 복합키 레코드 삭제: ${tableName}`, compositeKey);
// 레코드 삭제 (복합키 객체 전달)
const result = await dataService.deleteRecord(tableName, compositeKey);
if (!result.success) {
return res.status(400).json(result);
}
console.log(`✅ 레코드 삭제 성공: ${tableName}`);
return res.json(result);
} catch (error: any) {
console.error(`레코드 삭제 오류 (${req.params.tableName}):`, error);
return res.status(500).json({
success: false,
message: "레코드 삭제 중 오류가 발생했습니다.",
error: error.message,
});
}
}
);
/**
* 그룹 삭제 API
* POST /api/data/:tableName/delete-group
*/
router.post(
"/:tableName/delete-group",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { tableName } = req.params;
const filterConditions = req.body;
const userCompany = req.user?.companyCode;
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
return res.status(400).json({
success: false,
message: "유효하지 않은 테이블명입니다.",
});
}
console.log(`🗑️ 그룹 삭제:`, { tableName, filterConditions, userCompany });
const result = await dataService.deleteGroupRecords(
tableName,
filterConditions,
userCompany // 회사 코드 전달
);
if (!result.success) {
return res.status(400).json(result);
}
console.log(`✅ 그룹 삭제: ${result.data?.deleted}`);
return res.json(result);
} catch (error: any) {
console.error("그룹 삭제 오류:", error);
return res.status(500).json({
success: false,
message: "그룹 삭제 실패",
error: error.message,
});
}
}
);
router.delete(
"/:tableName/:id",
authenticateToken,
async (req: AuthenticatedRequest, res) => {
try {
const { tableName, id } = req.params;
// 입력값 검증
if (!tableName || typeof tableName !== "string") {
return res.status(400).json({
success: false,
message: "테이블명이 필요합니다.",
error: "INVALID_TABLE_NAME",
});
}
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
return res.status(400).json({
success: false,
message: "유효하지 않은 테이블명입니다.",
error: "INVALID_TABLE_NAME",
});
}
console.log(`🗑️ 레코드 삭제: ${tableName}/${id}`);
// 레코드 삭제
const result = await dataService.deleteRecord(tableName, id);
if (!result.success) {
return res.status(400).json(result);
}
console.log(`✅ 레코드 삭제 성공: ${tableName}/${id}`);
return res.json({
success: true,
message: "레코드가 삭제되었습니다.",
});
} catch (error) {
console.error("레코드 삭제 오류:", error);
return res.status(500).json({
success: false,
message: "레코드 삭제 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
);
export default router;