284 lines
8.0 KiB
TypeScript
284 lines
8.0 KiB
TypeScript
|
|
import { getPool } from "../database/db";
|
||
|
|
import { logger } from "../utils/logger";
|
||
|
|
import crypto from "crypto";
|
||
|
|
|
||
|
|
export interface ExcelMappingTemplate {
|
||
|
|
id?: number;
|
||
|
|
tableName: string;
|
||
|
|
excelColumns: string[];
|
||
|
|
excelColumnsHash: string;
|
||
|
|
columnMappings: Record<string, string | null>; // { "엑셀컬럼": "시스템컬럼" }
|
||
|
|
companyCode: string;
|
||
|
|
createdDate?: Date;
|
||
|
|
updatedDate?: Date;
|
||
|
|
}
|
||
|
|
|
||
|
|
class ExcelMappingService {
|
||
|
|
/**
|
||
|
|
* 엑셀 컬럼 목록으로 해시 생성
|
||
|
|
* 정렬 후 MD5 해시 생성하여 동일한 컬럼 구조 식별
|
||
|
|
*/
|
||
|
|
generateColumnsHash(columns: string[]): string {
|
||
|
|
// 컬럼 목록을 정렬하여 순서와 무관하게 동일한 해시 생성
|
||
|
|
const sortedColumns = [...columns].sort();
|
||
|
|
const columnsString = sortedColumns.join("|");
|
||
|
|
return crypto.createHash("md5").update(columnsString).digest("hex");
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 엑셀 컬럼 구조로 매핑 템플릿 조회
|
||
|
|
* 동일한 컬럼 구조가 있으면 기존 매핑 반환
|
||
|
|
*/
|
||
|
|
async findMappingByColumns(
|
||
|
|
tableName: string,
|
||
|
|
excelColumns: string[],
|
||
|
|
companyCode: string
|
||
|
|
): Promise<ExcelMappingTemplate | null> {
|
||
|
|
try {
|
||
|
|
const hash = this.generateColumnsHash(excelColumns);
|
||
|
|
|
||
|
|
logger.info("엑셀 매핑 템플릿 조회", {
|
||
|
|
tableName,
|
||
|
|
excelColumns,
|
||
|
|
hash,
|
||
|
|
companyCode,
|
||
|
|
});
|
||
|
|
|
||
|
|
const pool = getPool();
|
||
|
|
|
||
|
|
// 회사별 매핑 먼저 조회, 없으면 공통(*) 매핑 조회
|
||
|
|
let query: string;
|
||
|
|
let params: any[];
|
||
|
|
|
||
|
|
if (companyCode === "*") {
|
||
|
|
query = `
|
||
|
|
SELECT
|
||
|
|
id,
|
||
|
|
table_name as "tableName",
|
||
|
|
excel_columns as "excelColumns",
|
||
|
|
excel_columns_hash as "excelColumnsHash",
|
||
|
|
column_mappings as "columnMappings",
|
||
|
|
company_code as "companyCode",
|
||
|
|
created_date as "createdDate",
|
||
|
|
updated_date as "updatedDate"
|
||
|
|
FROM excel_mapping_template
|
||
|
|
WHERE table_name = $1
|
||
|
|
AND excel_columns_hash = $2
|
||
|
|
ORDER BY updated_date DESC
|
||
|
|
LIMIT 1
|
||
|
|
`;
|
||
|
|
params = [tableName, hash];
|
||
|
|
} else {
|
||
|
|
query = `
|
||
|
|
SELECT
|
||
|
|
id,
|
||
|
|
table_name as "tableName",
|
||
|
|
excel_columns as "excelColumns",
|
||
|
|
excel_columns_hash as "excelColumnsHash",
|
||
|
|
column_mappings as "columnMappings",
|
||
|
|
company_code as "companyCode",
|
||
|
|
created_date as "createdDate",
|
||
|
|
updated_date as "updatedDate"
|
||
|
|
FROM excel_mapping_template
|
||
|
|
WHERE table_name = $1
|
||
|
|
AND excel_columns_hash = $2
|
||
|
|
AND (company_code = $3 OR company_code = '*')
|
||
|
|
ORDER BY
|
||
|
|
CASE WHEN company_code = $3 THEN 0 ELSE 1 END,
|
||
|
|
updated_date DESC
|
||
|
|
LIMIT 1
|
||
|
|
`;
|
||
|
|
params = [tableName, hash, companyCode];
|
||
|
|
}
|
||
|
|
|
||
|
|
const result = await pool.query(query, params);
|
||
|
|
|
||
|
|
if (result.rows.length > 0) {
|
||
|
|
logger.info("기존 매핑 템플릿 발견", {
|
||
|
|
id: result.rows[0].id,
|
||
|
|
tableName,
|
||
|
|
});
|
||
|
|
return result.rows[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
logger.info("매핑 템플릿 없음 - 새 구조", { tableName, hash });
|
||
|
|
return null;
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error(`매핑 템플릿 조회 실패: ${error.message}`, { error });
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 매핑 템플릿 저장 (UPSERT)
|
||
|
|
* 동일한 테이블+컬럼구조+회사코드가 있으면 업데이트, 없으면 삽입
|
||
|
|
*/
|
||
|
|
async saveMappingTemplate(
|
||
|
|
tableName: string,
|
||
|
|
excelColumns: string[],
|
||
|
|
columnMappings: Record<string, string | null>,
|
||
|
|
companyCode: string,
|
||
|
|
userId?: string
|
||
|
|
): Promise<ExcelMappingTemplate> {
|
||
|
|
try {
|
||
|
|
const hash = this.generateColumnsHash(excelColumns);
|
||
|
|
|
||
|
|
logger.info("엑셀 매핑 템플릿 저장 (UPSERT)", {
|
||
|
|
tableName,
|
||
|
|
excelColumns,
|
||
|
|
hash,
|
||
|
|
columnMappings,
|
||
|
|
companyCode,
|
||
|
|
});
|
||
|
|
|
||
|
|
const pool = getPool();
|
||
|
|
|
||
|
|
const query = `
|
||
|
|
INSERT INTO excel_mapping_template (
|
||
|
|
table_name,
|
||
|
|
excel_columns,
|
||
|
|
excel_columns_hash,
|
||
|
|
column_mappings,
|
||
|
|
company_code,
|
||
|
|
created_date,
|
||
|
|
updated_date
|
||
|
|
) VALUES ($1, $2, $3, $4, $5, NOW(), NOW())
|
||
|
|
ON CONFLICT (table_name, excel_columns_hash, company_code)
|
||
|
|
DO UPDATE SET
|
||
|
|
column_mappings = EXCLUDED.column_mappings,
|
||
|
|
updated_date = NOW()
|
||
|
|
RETURNING
|
||
|
|
id,
|
||
|
|
table_name as "tableName",
|
||
|
|
excel_columns as "excelColumns",
|
||
|
|
excel_columns_hash as "excelColumnsHash",
|
||
|
|
column_mappings as "columnMappings",
|
||
|
|
company_code as "companyCode",
|
||
|
|
created_date as "createdDate",
|
||
|
|
updated_date as "updatedDate"
|
||
|
|
`;
|
||
|
|
|
||
|
|
const result = await pool.query(query, [
|
||
|
|
tableName,
|
||
|
|
excelColumns,
|
||
|
|
hash,
|
||
|
|
JSON.stringify(columnMappings),
|
||
|
|
companyCode,
|
||
|
|
]);
|
||
|
|
|
||
|
|
logger.info("매핑 템플릿 저장 완료", {
|
||
|
|
id: result.rows[0].id,
|
||
|
|
tableName,
|
||
|
|
hash,
|
||
|
|
});
|
||
|
|
|
||
|
|
return result.rows[0];
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error(`매핑 템플릿 저장 실패: ${error.message}`, { error });
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 테이블의 모든 매핑 템플릿 조회
|
||
|
|
*/
|
||
|
|
async getMappingTemplates(
|
||
|
|
tableName: string,
|
||
|
|
companyCode: string
|
||
|
|
): Promise<ExcelMappingTemplate[]> {
|
||
|
|
try {
|
||
|
|
logger.info("테이블 매핑 템플릿 목록 조회", { tableName, companyCode });
|
||
|
|
|
||
|
|
const pool = getPool();
|
||
|
|
|
||
|
|
let query: string;
|
||
|
|
let params: any[];
|
||
|
|
|
||
|
|
if (companyCode === "*") {
|
||
|
|
query = `
|
||
|
|
SELECT
|
||
|
|
id,
|
||
|
|
table_name as "tableName",
|
||
|
|
excel_columns as "excelColumns",
|
||
|
|
excel_columns_hash as "excelColumnsHash",
|
||
|
|
column_mappings as "columnMappings",
|
||
|
|
company_code as "companyCode",
|
||
|
|
created_date as "createdDate",
|
||
|
|
updated_date as "updatedDate"
|
||
|
|
FROM excel_mapping_template
|
||
|
|
WHERE table_name = $1
|
||
|
|
ORDER BY updated_date DESC
|
||
|
|
`;
|
||
|
|
params = [tableName];
|
||
|
|
} else {
|
||
|
|
query = `
|
||
|
|
SELECT
|
||
|
|
id,
|
||
|
|
table_name as "tableName",
|
||
|
|
excel_columns as "excelColumns",
|
||
|
|
excel_columns_hash as "excelColumnsHash",
|
||
|
|
column_mappings as "columnMappings",
|
||
|
|
company_code as "companyCode",
|
||
|
|
created_date as "createdDate",
|
||
|
|
updated_date as "updatedDate"
|
||
|
|
FROM excel_mapping_template
|
||
|
|
WHERE table_name = $1
|
||
|
|
AND (company_code = $2 OR company_code = '*')
|
||
|
|
ORDER BY updated_date DESC
|
||
|
|
`;
|
||
|
|
params = [tableName, companyCode];
|
||
|
|
}
|
||
|
|
|
||
|
|
const result = await pool.query(query, params);
|
||
|
|
|
||
|
|
logger.info(`매핑 템플릿 ${result.rows.length}개 조회`, { tableName });
|
||
|
|
|
||
|
|
return result.rows;
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error(`매핑 템플릿 목록 조회 실패: ${error.message}`, { error });
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 매핑 템플릿 삭제
|
||
|
|
*/
|
||
|
|
async deleteMappingTemplate(
|
||
|
|
id: number,
|
||
|
|
companyCode: string
|
||
|
|
): Promise<boolean> {
|
||
|
|
try {
|
||
|
|
logger.info("매핑 템플릿 삭제", { id, companyCode });
|
||
|
|
|
||
|
|
const pool = getPool();
|
||
|
|
|
||
|
|
let query: string;
|
||
|
|
let params: any[];
|
||
|
|
|
||
|
|
if (companyCode === "*") {
|
||
|
|
query = `DELETE FROM excel_mapping_template WHERE id = $1`;
|
||
|
|
params = [id];
|
||
|
|
} else {
|
||
|
|
query = `DELETE FROM excel_mapping_template WHERE id = $1 AND company_code = $2`;
|
||
|
|
params = [id, companyCode];
|
||
|
|
}
|
||
|
|
|
||
|
|
const result = await pool.query(query, params);
|
||
|
|
|
||
|
|
if (result.rowCount && result.rowCount > 0) {
|
||
|
|
logger.info("매핑 템플릿 삭제 완료", { id });
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
logger.warn("삭제할 매핑 템플릿 없음", { id, companyCode });
|
||
|
|
return false;
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error(`매핑 템플릿 삭제 실패: ${error.message}`, { error });
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export default new ExcelMappingService();
|
||
|
|
|