Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into common/feat/dashboard-map
This commit is contained in:
@@ -132,6 +132,16 @@ router.post("/", authenticateToken, async (req: AuthenticatedRequest, res: Respo
|
||||
return res.status(400).json({ success: false, error: "최소 1개 이상의 규칙 파트가 필요합니다" });
|
||||
}
|
||||
|
||||
// 🆕 scopeType이 'table'인 경우 tableName 필수 체크
|
||||
if (ruleConfig.scopeType === "table") {
|
||||
if (!ruleConfig.tableName || ruleConfig.tableName.trim() === "") {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: "테이블 범위 규칙은 테이블명(tableName)이 필수입니다",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const newRule = await numberingRuleService.createRule(ruleConfig, companyCode, userId);
|
||||
|
||||
logger.info("✅ [POST /numbering-rules] 채번 규칙 생성 성공:", {
|
||||
|
||||
@@ -99,10 +99,18 @@ export class DynamicFormService {
|
||||
}
|
||||
|
||||
try {
|
||||
// YYYY-MM-DD 형식인 경우 시간 추가해서 Date 객체 생성
|
||||
// YYYY-MM-DD 형식인 경우
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
||||
console.log(`📅 날짜 타입 변환: ${value} -> Date 객체`);
|
||||
return new Date(value + "T00:00:00");
|
||||
// DATE 타입이면 문자열 그대로 유지
|
||||
if (lowerDataType === "date") {
|
||||
console.log(`📅 날짜 문자열 유지: ${value} -> "${value}" (DATE 타입)`);
|
||||
return value; // 문자열 그대로 반환
|
||||
}
|
||||
// TIMESTAMP 타입이면 Date 객체로 변환
|
||||
else {
|
||||
console.log(`📅 날짜시간 변환: ${value} -> Date 객체 (TIMESTAMP 타입)`);
|
||||
return new Date(value + "T00:00:00");
|
||||
}
|
||||
}
|
||||
// 다른 날짜 형식도 Date 객체로 변환
|
||||
else {
|
||||
@@ -300,13 +308,13 @@ export class DynamicFormService {
|
||||
) {
|
||||
// YYYY-MM-DD HH:mm:ss 형태의 문자열을 Date 객체로 변환
|
||||
if (value.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)) {
|
||||
console.log(`📅 날짜 변환: ${key} = "${value}" -> Date 객체`);
|
||||
console.log(`📅 날짜시간 변환: ${key} = "${value}" -> Date 객체`);
|
||||
dataToInsert[key] = new Date(value);
|
||||
}
|
||||
// YYYY-MM-DD 형태의 문자열을 Date 객체로 변환
|
||||
// YYYY-MM-DD 형태의 문자열은 그대로 유지 (DATE 타입으로 저장)
|
||||
else if (value.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
||||
console.log(`📅 날짜 변환: ${key} = "${value}" -> Date 객체`);
|
||||
dataToInsert[key] = new Date(value + "T00:00:00");
|
||||
console.log(`📅 날짜 유지: ${key} = "${value}" -> 문자열 그대로 (DATE 타입)`);
|
||||
// dataToInsert[key] = value; // 문자열 그대로 유지 (이미 올바른 형식)
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -849,10 +857,22 @@ export class DynamicFormService {
|
||||
const values: any[] = Object.values(changedFields);
|
||||
values.push(id); // WHERE 조건용 ID 추가
|
||||
|
||||
// 🔑 Primary Key 타입에 맞게 캐스팅
|
||||
const pkDataType = columnTypes[primaryKeyColumn];
|
||||
let pkCast = '';
|
||||
if (pkDataType === 'integer' || pkDataType === 'bigint' || pkDataType === 'smallint') {
|
||||
pkCast = '::integer';
|
||||
} else if (pkDataType === 'numeric' || pkDataType === 'decimal') {
|
||||
pkCast = '::numeric';
|
||||
} else if (pkDataType === 'uuid') {
|
||||
pkCast = '::uuid';
|
||||
}
|
||||
// text, varchar 등은 캐스팅 불필요
|
||||
|
||||
const updateQuery = `
|
||||
UPDATE ${tableName}
|
||||
SET ${setClause}
|
||||
WHERE ${primaryKeyColumn} = $${values.length}::text
|
||||
WHERE ${primaryKeyColumn} = $${values.length}${pkCast}
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
|
||||
@@ -1418,9 +1418,9 @@ export class ScreenManagementService {
|
||||
console.log(`=== 레이아웃 로드 시작 ===`);
|
||||
console.log(`화면 ID: ${screenId}`);
|
||||
|
||||
// 권한 확인
|
||||
const screens = await query<{ company_code: string | null }>(
|
||||
`SELECT company_code FROM screen_definitions WHERE screen_id = $1 LIMIT 1`,
|
||||
// 권한 확인 및 테이블명 조회
|
||||
const screens = await query<{ company_code: string | null; table_name: string | null }>(
|
||||
`SELECT company_code, table_name FROM screen_definitions WHERE screen_id = $1 LIMIT 1`,
|
||||
[screenId]
|
||||
);
|
||||
|
||||
@@ -1512,11 +1512,13 @@ export class ScreenManagementService {
|
||||
console.log(`반환할 컴포넌트 수: ${components.length}`);
|
||||
console.log(`최종 격자 설정:`, gridSettings);
|
||||
console.log(`최종 해상도 설정:`, screenResolution);
|
||||
console.log(`테이블명:`, existingScreen.table_name);
|
||||
|
||||
return {
|
||||
components,
|
||||
gridSettings,
|
||||
screenResolution,
|
||||
tableName: existingScreen.table_name, // 🆕 테이블명 추가
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1165,6 +1165,23 @@ export class TableManagementService {
|
||||
paramCount: number;
|
||||
} | null> {
|
||||
try {
|
||||
// 🔧 날짜 범위 문자열 "YYYY-MM-DD|YYYY-MM-DD" 체크 (최우선!)
|
||||
if (typeof value === "string" && value.includes("|")) {
|
||||
const columnInfo = await this.getColumnWebTypeInfo(tableName, columnName);
|
||||
if (columnInfo && (columnInfo.webType === "date" || columnInfo.webType === "datetime")) {
|
||||
return this.buildDateRangeCondition(columnName, value, paramIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// 🔧 날짜 범위 객체 {from, to} 체크
|
||||
if (typeof value === "object" && value !== null && ("from" in value || "to" in value)) {
|
||||
// 날짜 범위 객체는 그대로 전달
|
||||
const columnInfo = await this.getColumnWebTypeInfo(tableName, columnName);
|
||||
if (columnInfo && (columnInfo.webType === "date" || columnInfo.webType === "datetime")) {
|
||||
return this.buildDateRangeCondition(columnName, value, paramIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// 🔧 {value, operator} 형태의 필터 객체 처리
|
||||
let actualValue = value;
|
||||
let operator = "contains"; // 기본값
|
||||
@@ -1193,6 +1210,12 @@ export class TableManagementService {
|
||||
|
||||
// 컬럼 타입 정보 조회
|
||||
const columnInfo = await this.getColumnWebTypeInfo(tableName, columnName);
|
||||
logger.info(`🔍 [buildAdvancedSearchCondition] ${tableName}.${columnName}`,
|
||||
`webType=${columnInfo?.webType || 'NULL'}`,
|
||||
`inputType=${columnInfo?.inputType || 'NULL'}`,
|
||||
`actualValue=${JSON.stringify(actualValue)}`,
|
||||
`operator=${operator}`
|
||||
);
|
||||
|
||||
if (!columnInfo) {
|
||||
// 컬럼 정보가 없으면 operator에 따른 기본 검색
|
||||
@@ -1292,20 +1315,41 @@ export class TableManagementService {
|
||||
const values: any[] = [];
|
||||
let paramCount = 0;
|
||||
|
||||
if (typeof value === "object" && value !== null) {
|
||||
// 문자열 형식의 날짜 범위 파싱 ("YYYY-MM-DD|YYYY-MM-DD")
|
||||
if (typeof value === "string" && value.includes("|")) {
|
||||
const [fromStr, toStr] = value.split("|");
|
||||
|
||||
if (fromStr && fromStr.trim() !== "") {
|
||||
// VARCHAR 컬럼을 DATE로 캐스팅하여 비교
|
||||
conditions.push(`${columnName}::date >= $${paramIndex + paramCount}::date`);
|
||||
values.push(fromStr.trim());
|
||||
paramCount++;
|
||||
}
|
||||
if (toStr && toStr.trim() !== "") {
|
||||
// 종료일은 해당 날짜의 23:59:59까지 포함
|
||||
conditions.push(`${columnName}::date <= $${paramIndex + paramCount}::date`);
|
||||
values.push(toStr.trim());
|
||||
paramCount++;
|
||||
}
|
||||
}
|
||||
// 객체 형식의 날짜 범위 ({from, to})
|
||||
else if (typeof value === "object" && value !== null) {
|
||||
if (value.from) {
|
||||
conditions.push(`${columnName} >= $${paramIndex + paramCount}`);
|
||||
// VARCHAR 컬럼을 DATE로 캐스팅하여 비교
|
||||
conditions.push(`${columnName}::date >= $${paramIndex + paramCount}::date`);
|
||||
values.push(value.from);
|
||||
paramCount++;
|
||||
}
|
||||
if (value.to) {
|
||||
conditions.push(`${columnName} <= $${paramIndex + paramCount}`);
|
||||
// 종료일은 해당 날짜의 23:59:59까지 포함
|
||||
conditions.push(`${columnName}::date <= $${paramIndex + paramCount}::date`);
|
||||
values.push(value.to);
|
||||
paramCount++;
|
||||
}
|
||||
} else if (typeof value === "string" && value.trim() !== "") {
|
||||
// 단일 날짜 검색 (해당 날짜의 데이터)
|
||||
conditions.push(`DATE(${columnName}) = DATE($${paramIndex})`);
|
||||
}
|
||||
// 단일 날짜 검색
|
||||
else if (typeof value === "string" && value.trim() !== "") {
|
||||
conditions.push(`${columnName}::date = $${paramIndex}::date`);
|
||||
values.push(value);
|
||||
paramCount = 1;
|
||||
}
|
||||
@@ -1544,6 +1588,7 @@ export class TableManagementService {
|
||||
columnName: string
|
||||
): Promise<{
|
||||
webType: string;
|
||||
inputType?: string;
|
||||
codeCategory?: string;
|
||||
referenceTable?: string;
|
||||
referenceColumn?: string;
|
||||
@@ -1552,29 +1597,44 @@ export class TableManagementService {
|
||||
try {
|
||||
const result = await queryOne<{
|
||||
web_type: string | null;
|
||||
input_type: string | null;
|
||||
code_category: string | null;
|
||||
reference_table: string | null;
|
||||
reference_column: string | null;
|
||||
display_column: string | null;
|
||||
}>(
|
||||
`SELECT web_type, code_category, reference_table, reference_column, display_column
|
||||
`SELECT web_type, input_type, code_category, reference_table, reference_column, display_column
|
||||
FROM column_labels
|
||||
WHERE table_name = $1 AND column_name = $2
|
||||
LIMIT 1`,
|
||||
[tableName, columnName]
|
||||
);
|
||||
|
||||
logger.info(`🔍 [getColumnWebTypeInfo] ${tableName}.${columnName} 조회 결과:`, {
|
||||
found: !!result,
|
||||
web_type: result?.web_type,
|
||||
input_type: result?.input_type,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
logger.warn(`⚠️ [getColumnWebTypeInfo] 컬럼 정보 없음: ${tableName}.${columnName}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
webType: result.web_type || "",
|
||||
// web_type이 없으면 input_type을 사용 (레거시 호환)
|
||||
const webType = result.web_type || result.input_type || "";
|
||||
|
||||
const columnInfo = {
|
||||
webType: webType,
|
||||
inputType: result.input_type || "",
|
||||
codeCategory: result.code_category || undefined,
|
||||
referenceTable: result.reference_table || undefined,
|
||||
referenceColumn: result.reference_column || undefined,
|
||||
displayColumn: result.display_column || undefined,
|
||||
};
|
||||
|
||||
logger.info(`✅ [getColumnWebTypeInfo] 반환값: webType=${columnInfo.webType}, inputType=${columnInfo.inputType}`);
|
||||
return columnInfo;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`컬럼 웹타입 정보 조회 실패: ${tableName}.${columnName}`,
|
||||
|
||||
@@ -101,6 +101,7 @@ export interface LayoutData {
|
||||
components: ComponentData[];
|
||||
gridSettings?: GridSettings;
|
||||
screenResolution?: ScreenResolution;
|
||||
tableName?: string; // 🆕 화면에 연결된 테이블명
|
||||
}
|
||||
|
||||
// 그리드 설정
|
||||
|
||||
Reference in New Issue
Block a user