Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management

This commit is contained in:
kjs
2025-12-11 10:43:09 +09:00
42 changed files with 5977 additions and 1202 deletions

View File

@@ -65,12 +65,18 @@ export class BatchSchedulerService {
`배치 스케줄 등록: ${config.batch_name} (ID: ${config.id}, Cron: ${config.cron_schedule})`
);
const task = cron.schedule(config.cron_schedule, async () => {
logger.info(
`스케줄에 의한 배치 실행 시작: ${config.batch_name} (ID: ${config.id})`
);
await this.executeBatchConfig(config);
});
const task = cron.schedule(
config.cron_schedule,
async () => {
logger.info(
`스케줄에 의한 배치 실행 시작: ${config.batch_name} (ID: ${config.id})`
);
await this.executeBatchConfig(config);
},
{
timezone: "Asia/Seoul", // 한국 시간 기준으로 스케줄 실행
}
);
this.scheduledTasks.set(config.id, task);
} catch (error) {

View File

@@ -72,6 +72,11 @@ export class FlowDataMoveService {
// 내부 DB 처리 (기존 로직)
return await db.transaction(async (client) => {
try {
// 트랜잭션 세션 변수 설정 (트리거에서 changed_by 기록용)
await client.query("SELECT set_config('app.user_id', $1, true)", [
userId || "system",
]);
// 1. 단계 정보 조회
const fromStep = await this.flowStepService.findById(fromStepId);
const toStep = await this.flowStepService.findById(toStepId);
@@ -684,6 +689,14 @@ export class FlowDataMoveService {
dbConnectionId,
async (externalClient, dbType) => {
try {
// 외부 DB가 PostgreSQL인 경우에만 세션 변수 설정 시도
if (dbType.toLowerCase() === "postgresql") {
await externalClient.query(
"SELECT set_config('app.user_id', $1, true)",
[userId || "system"]
);
}
// 1. 단계 정보 조회 (내부 DB에서)
const fromStep = await this.flowStepService.findById(fromStepId);
const toStep = await this.flowStepService.findById(toStepId);

View File

@@ -263,4 +263,139 @@ export class FlowExecutionService {
tableName: result[0].table_name,
};
}
/**
* 스텝 데이터 업데이트 (인라인 편집)
* 원본 테이블의 데이터를 직접 업데이트합니다.
*/
async updateStepData(
flowId: number,
stepId: number,
recordId: string,
updateData: Record<string, any>,
userId: string,
companyCode?: string
): Promise<{ success: boolean }> {
try {
// 1. 플로우 정의 조회
const flowDef = await this.flowDefinitionService.findById(flowId);
if (!flowDef) {
throw new Error(`Flow definition not found: ${flowId}`);
}
// 2. 스텝 조회
const step = await this.flowStepService.findById(stepId);
if (!step) {
throw new Error(`Flow step not found: ${stepId}`);
}
// 3. 테이블명 결정
const tableName = step.tableName || flowDef.tableName;
if (!tableName) {
throw new Error("Table name not found");
}
// 4. Primary Key 컬럼 결정 (기본값: id)
const primaryKeyColumn = flowDef.primaryKey || "id";
console.log(
`🔍 [updateStepData] Updating table: ${tableName}, PK: ${primaryKeyColumn}=${recordId}`
);
// 5. SET 절 생성
const updateColumns = Object.keys(updateData);
if (updateColumns.length === 0) {
throw new Error("No columns to update");
}
// 6. 외부 DB vs 내부 DB 구분
if (flowDef.dbSourceType === "external" && flowDef.dbConnectionId) {
// 외부 DB 업데이트
console.log(
"✅ [updateStepData] Using EXTERNAL DB:",
flowDef.dbConnectionId
);
// 외부 DB 연결 정보 조회
const connectionResult = await db.query(
"SELECT * FROM external_db_connection WHERE id = $1",
[flowDef.dbConnectionId]
);
if (connectionResult.length === 0) {
throw new Error(
`External DB connection not found: ${flowDef.dbConnectionId}`
);
}
const connection = connectionResult[0];
const dbType = connection.db_type?.toLowerCase();
// DB 타입에 따른 placeholder 및 쿼리 생성
let setClause: string;
let params: any[];
if (dbType === "mysql" || dbType === "mariadb") {
// MySQL/MariaDB: ? placeholder
setClause = updateColumns.map((col) => `\`${col}\` = ?`).join(", ");
params = [...Object.values(updateData), recordId];
} else if (dbType === "mssql") {
// MSSQL: @p1, @p2 placeholder
setClause = updateColumns
.map((col, idx) => `[${col}] = @p${idx + 1}`)
.join(", ");
params = [...Object.values(updateData), recordId];
} else {
// PostgreSQL: $1, $2 placeholder
setClause = updateColumns
.map((col, idx) => `"${col}" = $${idx + 1}`)
.join(", ");
params = [...Object.values(updateData), recordId];
}
const updateQuery = `UPDATE ${tableName} SET ${setClause} WHERE ${primaryKeyColumn} = ${dbType === "mysql" || dbType === "mariadb" ? "?" : dbType === "mssql" ? `@p${params.length}` : `$${params.length}`}`;
console.log(`📝 [updateStepData] Query: ${updateQuery}`);
console.log(`📝 [updateStepData] Params:`, params);
await executeExternalQuery(flowDef.dbConnectionId, updateQuery, params);
} else {
// 내부 DB 업데이트
console.log("✅ [updateStepData] Using INTERNAL DB");
const setClause = updateColumns
.map((col, idx) => `"${col}" = $${idx + 1}`)
.join(", ");
const params = [...Object.values(updateData), recordId];
const updateQuery = `UPDATE "${tableName}" SET ${setClause} WHERE "${primaryKeyColumn}" = $${params.length}`;
console.log(`📝 [updateStepData] Query: ${updateQuery}`);
console.log(`📝 [updateStepData] Params:`, params);
// 트랜잭션으로 감싸서 사용자 ID 세션 변수 설정 후 업데이트 실행
// (트리거에서 changed_by를 기록하기 위함)
await db.transaction(async (client) => {
// 안전한 파라미터 바인딩 방식 사용
await client.query("SELECT set_config('app.user_id', $1, true)", [
userId,
]);
await client.query(updateQuery, params);
});
}
console.log(
`✅ [updateStepData] Data updated successfully: ${tableName}.${primaryKeyColumn}=${recordId}`,
{
updatedFields: updateColumns,
userId,
}
);
return { success: true };
} catch (error: any) {
console.error("❌ [updateStepData] Error:", error);
throw error;
}
}
}

View File

@@ -191,6 +191,12 @@ export class NodeFlowExecutionService {
try {
result = await transaction(async (client) => {
// 🔥 사용자 ID 세션 변수 설정 (트리거용)
const userId = context.buttonContext?.userId || "system";
await client.query("SELECT set_config('app.user_id', $1, true)", [
userId,
]);
// 트랜잭션 내에서 레벨별 실행
for (const level of levels) {
await this.executeLevel(level, nodes, edges, context, client);

View File

@@ -897,13 +897,13 @@ class NumberingRuleService {
switch (part.partType) {
case "sequence": {
// 순번 (현재 순번으로 미리보기, 증가 안 함)
const length = autoConfig.sequenceLength || 4;
const length = autoConfig.sequenceLength || 3;
return String(rule.currentSequence || 1).padStart(length, "0");
}
case "number": {
// 숫자 (고정 자릿수)
const length = autoConfig.numberLength || 4;
const length = autoConfig.numberLength || 3;
const value = autoConfig.numberValue || 1;
return String(value).padStart(length, "0");
}
@@ -957,13 +957,13 @@ class NumberingRuleService {
switch (part.partType) {
case "sequence": {
// 순번 (자동 증가 숫자)
const length = autoConfig.sequenceLength || 4;
const length = autoConfig.sequenceLength || 3;
return String(rule.currentSequence || 1).padStart(length, "0");
}
case "number": {
// 숫자 (고정 자릿수)
const length = autoConfig.numberLength || 4;
const length = autoConfig.numberLength || 3;
const value = autoConfig.numberValue || 1;
return String(value).padStart(length, "0");
}