ERP-node/backend-node/src/services/excelMappingService.ts

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();