290 lines
7.8 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|