Merge branch 'jskim-node' of http://39.117.244.52:3000/kjs/ERP-node into mhkim-node
This commit is contained in:
@@ -3563,19 +3563,21 @@ export async function getTableSchema(
|
||||
|
||||
logger.info("테이블 스키마 조회", { tableName, companyCode });
|
||||
|
||||
// information_schema와 table_type_columns를 JOIN하여 컬럼 정보와 라벨 정보 함께 가져오기
|
||||
// 회사별 라벨 우선, 없으면 공통(*) 라벨 사용
|
||||
// information_schema와 table_type_columns를 JOIN하여 컬럼 정보 + 회사별 제약조건 함께 가져오기
|
||||
// 회사별 설정 우선, 없으면 공통(*) 설정 사용
|
||||
const schemaQuery = `
|
||||
SELECT
|
||||
ic.column_name,
|
||||
ic.data_type,
|
||||
ic.is_nullable,
|
||||
ic.is_nullable AS db_is_nullable,
|
||||
ic.column_default,
|
||||
ic.character_maximum_length,
|
||||
ic.numeric_precision,
|
||||
ic.numeric_scale,
|
||||
COALESCE(ttc_company.column_label, ttc_common.column_label) AS column_label,
|
||||
COALESCE(ttc_company.display_order, ttc_common.display_order) AS display_order
|
||||
COALESCE(ttc_company.display_order, ttc_common.display_order) AS display_order,
|
||||
COALESCE(ttc_company.is_nullable, ttc_common.is_nullable) AS ttc_is_nullable,
|
||||
COALESCE(ttc_company.is_unique, ttc_common.is_unique) AS ttc_is_unique
|
||||
FROM information_schema.columns ic
|
||||
LEFT JOIN table_type_columns ttc_common
|
||||
ON ttc_common.table_name = ic.table_name
|
||||
@@ -3600,17 +3602,28 @@ export async function getTableSchema(
|
||||
return;
|
||||
}
|
||||
|
||||
// 컬럼 정보를 간단한 형태로 변환 (라벨 정보 포함)
|
||||
const columnList = columns.map((col: any) => ({
|
||||
name: col.column_name,
|
||||
label: col.column_label || col.column_name, // 라벨이 없으면 컬럼명 사용
|
||||
type: col.data_type,
|
||||
nullable: col.is_nullable === "YES",
|
||||
default: col.column_default,
|
||||
maxLength: col.character_maximum_length,
|
||||
precision: col.numeric_precision,
|
||||
scale: col.numeric_scale,
|
||||
}));
|
||||
// 컬럼 정보를 간단한 형태로 변환 (회사별 제약조건 반영)
|
||||
const columnList = columns.map((col: any) => {
|
||||
// DB level nullable + 회사별 table_type_columns 제약조건 통합
|
||||
// table_type_columns에서 is_nullable = 'N'이면 필수 (DB가 nullable이어도)
|
||||
const dbNullable = col.db_is_nullable === "YES";
|
||||
const ttcNotNull = col.ttc_is_nullable === "N";
|
||||
const effectiveNullable = ttcNotNull ? false : dbNullable;
|
||||
|
||||
const ttcUnique = col.ttc_is_unique === "Y";
|
||||
|
||||
return {
|
||||
name: col.column_name,
|
||||
label: col.column_label || col.column_name,
|
||||
type: col.data_type,
|
||||
nullable: effectiveNullable,
|
||||
unique: ttcUnique,
|
||||
default: col.column_default,
|
||||
maxLength: col.character_maximum_length,
|
||||
precision: col.numeric_precision,
|
||||
scale: col.numeric_scale,
|
||||
};
|
||||
});
|
||||
|
||||
logger.info(`테이블 스키마 조회 성공: ${columnList.length}개 컬럼`);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Request, Response } from "express";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import { query } from "../database/db";
|
||||
import logger from "../utils/logger";
|
||||
import { TableManagementService } from "../services/tableManagementService";
|
||||
|
||||
/**
|
||||
* 데이터 액션 실행
|
||||
@@ -81,6 +82,19 @@ async function executeMainDatabaseAction(
|
||||
company_code: companyCode,
|
||||
};
|
||||
|
||||
// UNIQUE 제약조건 검증 (INSERT/UPDATE/UPSERT 전)
|
||||
if (["insert", "update", "upsert"].includes(actionType.toLowerCase())) {
|
||||
const tms = new TableManagementService();
|
||||
const uniqueViolations = await tms.validateUniqueConstraints(
|
||||
tableName,
|
||||
dataWithCompany,
|
||||
companyCode
|
||||
);
|
||||
if (uniqueViolations.length > 0) {
|
||||
throw new Error(`중복된 값이 존재합니다: ${uniqueViolations.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
switch (actionType.toLowerCase()) {
|
||||
case "insert":
|
||||
return await executeInsert(tableName, dataWithCompany);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Response } from "express";
|
||||
import { dynamicFormService } from "../services/dynamicFormService";
|
||||
import { enhancedDynamicFormService } from "../services/enhancedDynamicFormService";
|
||||
import { TableManagementService } from "../services/tableManagementService";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import { formatPgError } from "../utils/pgErrorUtil";
|
||||
|
||||
@@ -48,6 +49,21 @@ export const saveFormData = async (
|
||||
formDataWithMeta.company_code = companyCode;
|
||||
}
|
||||
|
||||
// UNIQUE 제약조건 검증 (INSERT 전)
|
||||
const tms = new TableManagementService();
|
||||
const uniqueViolations = await tms.validateUniqueConstraints(
|
||||
tableName,
|
||||
formDataWithMeta,
|
||||
companyCode || "*"
|
||||
);
|
||||
if (uniqueViolations.length > 0) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: `중복된 값이 존재합니다: ${uniqueViolations.join(", ")}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 클라이언트 IP 주소 추출
|
||||
const ipAddress =
|
||||
req.ip ||
|
||||
@@ -112,6 +128,21 @@ export const saveFormDataEnhanced = async (
|
||||
formDataWithMeta.company_code = companyCode;
|
||||
}
|
||||
|
||||
// UNIQUE 제약조건 검증 (INSERT 전)
|
||||
const tmsEnhanced = new TableManagementService();
|
||||
const uniqueViolations = await tmsEnhanced.validateUniqueConstraints(
|
||||
tableName,
|
||||
formDataWithMeta,
|
||||
companyCode || "*"
|
||||
);
|
||||
if (uniqueViolations.length > 0) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: `중복된 값이 존재합니다: ${uniqueViolations.join(", ")}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 개선된 서비스 사용
|
||||
const result = await enhancedDynamicFormService.saveFormData(
|
||||
screenId,
|
||||
@@ -153,12 +184,28 @@ export const updateFormData = async (
|
||||
const formDataWithMeta = {
|
||||
...data,
|
||||
updated_by: userId,
|
||||
writer: data.writer || userId, // ✅ writer가 없으면 userId로 설정
|
||||
writer: data.writer || userId,
|
||||
updated_at: new Date(),
|
||||
};
|
||||
|
||||
// UNIQUE 제약조건 검증 (UPDATE 시 자기 자신 제외)
|
||||
const tmsUpdate = new TableManagementService();
|
||||
const uniqueViolations = await tmsUpdate.validateUniqueConstraints(
|
||||
tableName,
|
||||
formDataWithMeta,
|
||||
companyCode || "*",
|
||||
id
|
||||
);
|
||||
if (uniqueViolations.length > 0) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: `중복된 값이 존재합니다: ${uniqueViolations.join(", ")}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await dynamicFormService.updateFormData(
|
||||
id, // parseInt 제거 - 문자열 ID 지원
|
||||
id,
|
||||
tableName,
|
||||
formDataWithMeta
|
||||
);
|
||||
@@ -209,11 +256,27 @@ export const updateFormDataPartial = async (
|
||||
const newDataWithMeta = {
|
||||
...newData,
|
||||
updated_by: userId,
|
||||
writer: newData.writer || userId, // ✅ writer가 없으면 userId로 설정
|
||||
writer: newData.writer || userId,
|
||||
};
|
||||
|
||||
// UNIQUE 제약조건 검증 (부분 UPDATE 시 자기 자신 제외)
|
||||
const tmsPartial = new TableManagementService();
|
||||
const uniqueViolations = await tmsPartial.validateUniqueConstraints(
|
||||
tableName,
|
||||
newDataWithMeta,
|
||||
companyCode || "*",
|
||||
id
|
||||
);
|
||||
if (uniqueViolations.length > 0) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: `중복된 값이 존재합니다: ${uniqueViolations.join(", ")}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await dynamicFormService.updateFormDataPartial(
|
||||
id, // 🔧 parseInt 제거 - UUID 문자열도 지원
|
||||
id,
|
||||
tableName,
|
||||
originalData,
|
||||
newDataWithMeta
|
||||
|
||||
@@ -2087,6 +2087,23 @@ export async function multiTableSave(
|
||||
return;
|
||||
}
|
||||
|
||||
// UNIQUE 제약조건 검증 (트랜잭션 전에)
|
||||
const tmsMulti = new TableManagementService();
|
||||
const uniqueViolations = await tmsMulti.validateUniqueConstraints(
|
||||
mainTable.tableName,
|
||||
mainData,
|
||||
companyCode,
|
||||
isUpdate ? mainData[mainTable.primaryKeyColumn] : undefined
|
||||
);
|
||||
if (uniqueViolations.length > 0) {
|
||||
client.release();
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: `중복된 값이 존재합니다: ${uniqueViolations.join(", ")}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await client.query("BEGIN");
|
||||
|
||||
// 1. 메인 테이블 저장
|
||||
|
||||
Reference in New Issue
Block a user