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

290 lines
7.8 KiB
TypeScript

/**
* 플로우 단계 서비스
*/
import db from "../database/db";
import {
FlowStep,
CreateFlowStepRequest,
UpdateFlowStepRequest,
FlowConditionGroup,
} from "../types/flow";
import { FlowConditionParser } from "./flowConditionParser";
export class FlowStepService {
/**
* 플로우 단계 생성
*/
async create(request: CreateFlowStepRequest): Promise<FlowStep> {
// 조건 검증
if (request.conditionJson) {
FlowConditionParser.validateConditionGroup(request.conditionJson);
}
const query = `
INSERT INTO flow_step (
flow_definition_id, step_name, step_order, table_name, condition_json,
color, position_x, position_y, move_type, status_column, status_value,
target_table, field_mappings, required_fields,
integration_type, integration_config
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
RETURNING *
`;
const result = await db.query(query, [
request.flowDefinitionId,
request.stepName,
request.stepOrder,
request.tableName || null,
request.conditionJson ? JSON.stringify(request.conditionJson) : null,
request.color || "#3B82F6",
request.positionX || 0,
request.positionY || 0,
request.moveType || null,
request.statusColumn || null,
request.statusValue || null,
request.targetTable || null,
request.fieldMappings ? JSON.stringify(request.fieldMappings) : null,
request.requiredFields ? JSON.stringify(request.requiredFields) : null,
request.integrationType || "internal",
request.integrationConfig
? JSON.stringify(request.integrationConfig)
: null,
]);
return this.mapToFlowStep(result[0]);
}
/**
* 특정 플로우의 모든 단계 조회
*/
async findByFlowId(flowDefinitionId: number): Promise<FlowStep[]> {
const query = `
SELECT * FROM flow_step
WHERE flow_definition_id = $1
ORDER BY step_order ASC
`;
const result = await db.query(query, [flowDefinitionId]);
return result.map(this.mapToFlowStep);
}
/**
* 플로우 단계 단일 조회
*/
async findById(id: number): Promise<FlowStep | null> {
const query = "SELECT * FROM flow_step WHERE id = $1";
const result = await db.query(query, [id]);
if (result.length === 0) {
return null;
}
return this.mapToFlowStep(result[0]);
}
/**
* 플로우 단계 수정
*/
async update(
id: number,
request: UpdateFlowStepRequest
): Promise<FlowStep | null> {
console.log("🔧 FlowStepService.update called with:", {
id,
statusColumn: request.statusColumn,
statusValue: request.statusValue,
fullRequest: JSON.stringify(request),
});
// 조건 검증
if (request.conditionJson) {
FlowConditionParser.validateConditionGroup(request.conditionJson);
}
const fields: string[] = [];
const params: any[] = [];
let paramIndex = 1;
if (request.stepName !== undefined) {
fields.push(`step_name = $${paramIndex}`);
params.push(request.stepName);
paramIndex++;
}
if (request.stepOrder !== undefined) {
fields.push(`step_order = $${paramIndex}`);
params.push(request.stepOrder);
paramIndex++;
}
if (request.tableName !== undefined) {
fields.push(`table_name = $${paramIndex}`);
params.push(request.tableName);
paramIndex++;
}
if (request.conditionJson !== undefined) {
fields.push(`condition_json = $${paramIndex}`);
params.push(
request.conditionJson ? JSON.stringify(request.conditionJson) : null
);
paramIndex++;
}
if (request.color !== undefined) {
fields.push(`color = $${paramIndex}`);
params.push(request.color);
paramIndex++;
}
if (request.positionX !== undefined) {
fields.push(`position_x = $${paramIndex}`);
params.push(request.positionX);
paramIndex++;
}
if (request.positionY !== undefined) {
fields.push(`position_y = $${paramIndex}`);
params.push(request.positionY);
paramIndex++;
}
// 하이브리드 플로우 필드
if (request.moveType !== undefined) {
fields.push(`move_type = $${paramIndex}`);
params.push(request.moveType);
paramIndex++;
}
if (request.statusColumn !== undefined) {
fields.push(`status_column = $${paramIndex}`);
params.push(request.statusColumn);
paramIndex++;
}
if (request.statusValue !== undefined) {
fields.push(`status_value = $${paramIndex}`);
params.push(request.statusValue);
paramIndex++;
}
if (request.targetTable !== undefined) {
fields.push(`target_table = $${paramIndex}`);
params.push(request.targetTable);
paramIndex++;
}
if (request.fieldMappings !== undefined) {
fields.push(`field_mappings = $${paramIndex}`);
params.push(
request.fieldMappings ? JSON.stringify(request.fieldMappings) : null
);
paramIndex++;
}
if (request.requiredFields !== undefined) {
fields.push(`required_fields = $${paramIndex}`);
params.push(
request.requiredFields ? JSON.stringify(request.requiredFields) : null
);
paramIndex++;
}
// 외부 연동 필드
if (request.integrationType !== undefined) {
fields.push(`integration_type = $${paramIndex}`);
params.push(request.integrationType);
paramIndex++;
}
if (request.integrationConfig !== undefined) {
fields.push(`integration_config = $${paramIndex}`);
params.push(
request.integrationConfig
? JSON.stringify(request.integrationConfig)
: null
);
paramIndex++;
}
if (fields.length === 0) {
return this.findById(id);
}
fields.push(`updated_at = NOW()`);
const query = `
UPDATE flow_step
SET ${fields.join(", ")}
WHERE id = $${paramIndex}
RETURNING *
`;
params.push(id);
const result = await db.query(query, params);
if (result.length === 0) {
return null;
}
return this.mapToFlowStep(result[0]);
}
/**
* 플로우 단계 삭제
*/
async delete(id: number): Promise<boolean> {
const query = "DELETE FROM flow_step WHERE id = $1 RETURNING id";
const result = await db.query(query, [id]);
return result.length > 0;
}
/**
* 단계 순서 재정렬
*/
async reorder(
flowDefinitionId: number,
stepOrders: { id: number; order: number }[]
): Promise<void> {
await db.transaction(async (client) => {
for (const { id, order } of stepOrders) {
await client.query(
"UPDATE flow_step SET step_order = $1, updated_at = NOW() WHERE id = $2 AND flow_definition_id = $3",
[order, id, flowDefinitionId]
);
}
});
}
/**
* DB 행을 FlowStep 객체로 변환
*/
private mapToFlowStep(row: any): FlowStep {
return {
id: row.id,
flowDefinitionId: row.flow_definition_id,
stepName: row.step_name,
stepOrder: row.step_order,
tableName: row.table_name || undefined,
conditionJson: row.condition_json as FlowConditionGroup | undefined,
color: row.color,
positionX: row.position_x,
positionY: row.position_y,
// 하이브리드 플로우 지원 필드
moveType: row.move_type || undefined,
statusColumn: row.status_column || undefined,
statusValue: row.status_value || undefined,
targetTable: row.target_table || undefined,
fieldMappings: row.field_mappings || undefined,
requiredFields: row.required_fields || undefined,
// 외부 연동 필드
integrationType: row.integration_type || "internal",
integrationConfig: row.integration_config || undefined,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
}
}