/** * 플로우 단계 서비스 */ 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 { // 조건 검증 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 { 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 { 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 { 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 { 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 { 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, }; } }