177 lines
5.2 KiB
TypeScript
177 lines
5.2 KiB
TypeScript
|
|
/**
|
||
|
|
* 플로우 실행 서비스
|
||
|
|
* 단계별 데이터 카운트 및 리스트 조회
|
||
|
|
*/
|
||
|
|
|
||
|
|
import db from "../database/db";
|
||
|
|
import { FlowStepDataCount, FlowStepDataList } from "../types/flow";
|
||
|
|
import { FlowDefinitionService } from "./flowDefinitionService";
|
||
|
|
import { FlowStepService } from "./flowStepService";
|
||
|
|
import { FlowConditionParser } from "./flowConditionParser";
|
||
|
|
|
||
|
|
export class FlowExecutionService {
|
||
|
|
private flowDefinitionService: FlowDefinitionService;
|
||
|
|
private flowStepService: FlowStepService;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this.flowDefinitionService = new FlowDefinitionService();
|
||
|
|
this.flowStepService = new FlowStepService();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 특정 플로우 단계에 해당하는 데이터 카운트
|
||
|
|
*/
|
||
|
|
async getStepDataCount(flowId: number, stepId: number): Promise<number> {
|
||
|
|
// 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}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (step.flowDefinitionId !== flowId) {
|
||
|
|
throw new Error(`Step ${stepId} does not belong to flow ${flowId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 3. 테이블명 결정: 단계에 지정된 테이블이 있으면 사용, 없으면 플로우의 기본 테이블 사용
|
||
|
|
const tableName = step.tableName || flowDef.tableName;
|
||
|
|
|
||
|
|
// 4. 조건 JSON을 SQL WHERE절로 변환
|
||
|
|
const { where, params } = FlowConditionParser.toSqlWhere(
|
||
|
|
step.conditionJson
|
||
|
|
);
|
||
|
|
|
||
|
|
// 5. 카운트 쿼리 실행
|
||
|
|
const query = `SELECT COUNT(*) as count FROM ${tableName} WHERE ${where}`;
|
||
|
|
const result = await db.query(query, params);
|
||
|
|
|
||
|
|
return parseInt(result[0].count);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 특정 플로우 단계에 해당하는 데이터 리스트
|
||
|
|
*/
|
||
|
|
async getStepDataList(
|
||
|
|
flowId: number,
|
||
|
|
stepId: number,
|
||
|
|
page: number = 1,
|
||
|
|
pageSize: number = 20
|
||
|
|
): Promise<FlowStepDataList> {
|
||
|
|
// 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}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (step.flowDefinitionId !== flowId) {
|
||
|
|
throw new Error(`Step ${stepId} does not belong to flow ${flowId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 3. 테이블명 결정: 단계에 지정된 테이블이 있으면 사용, 없으면 플로우의 기본 테이블 사용
|
||
|
|
const tableName = step.tableName || flowDef.tableName;
|
||
|
|
|
||
|
|
// 4. 조건 JSON을 SQL WHERE절로 변환
|
||
|
|
const { where, params } = FlowConditionParser.toSqlWhere(
|
||
|
|
step.conditionJson
|
||
|
|
);
|
||
|
|
|
||
|
|
const offset = (page - 1) * pageSize;
|
||
|
|
|
||
|
|
// 5. 전체 카운트
|
||
|
|
const countQuery = `SELECT COUNT(*) as count FROM ${tableName} WHERE ${where}`;
|
||
|
|
const countResult = await db.query(countQuery, params);
|
||
|
|
const total = parseInt(countResult[0].count);
|
||
|
|
|
||
|
|
// 6. 테이블의 Primary Key 컬럼 찾기
|
||
|
|
let orderByColumn = "";
|
||
|
|
try {
|
||
|
|
const pkQuery = `
|
||
|
|
SELECT a.attname
|
||
|
|
FROM pg_index i
|
||
|
|
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
|
||
|
|
WHERE i.indrelid = $1::regclass
|
||
|
|
AND i.indisprimary
|
||
|
|
LIMIT 1
|
||
|
|
`;
|
||
|
|
const pkResult = await db.query(pkQuery, [tableName]);
|
||
|
|
if (pkResult.length > 0) {
|
||
|
|
orderByColumn = pkResult[0].attname;
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
// Primary Key를 찾지 못하면 ORDER BY 없이 진행
|
||
|
|
console.warn(`Could not find primary key for table ${tableName}:`, err);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 7. 데이터 조회
|
||
|
|
const orderByClause = orderByColumn ? `ORDER BY ${orderByColumn} DESC` : "";
|
||
|
|
const dataQuery = `
|
||
|
|
SELECT * FROM ${tableName}
|
||
|
|
WHERE ${where}
|
||
|
|
${orderByClause}
|
||
|
|
LIMIT $${params.length + 1} OFFSET $${params.length + 2}
|
||
|
|
`;
|
||
|
|
const dataResult = await db.query(dataQuery, [...params, pageSize, offset]);
|
||
|
|
|
||
|
|
return {
|
||
|
|
records: dataResult,
|
||
|
|
total,
|
||
|
|
page,
|
||
|
|
pageSize,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 플로우의 모든 단계별 데이터 카운트
|
||
|
|
*/
|
||
|
|
async getAllStepCounts(flowId: number): Promise<FlowStepDataCount[]> {
|
||
|
|
const steps = await this.flowStepService.findByFlowId(flowId);
|
||
|
|
const counts: FlowStepDataCount[] = [];
|
||
|
|
|
||
|
|
for (const step of steps) {
|
||
|
|
const count = await this.getStepDataCount(flowId, step.id);
|
||
|
|
counts.push({
|
||
|
|
stepId: step.id,
|
||
|
|
count,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return counts;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 특정 레코드의 현재 플로우 상태 조회
|
||
|
|
*/
|
||
|
|
async getCurrentStatus(
|
||
|
|
flowId: number,
|
||
|
|
recordId: string
|
||
|
|
): Promise<{ currentStepId: number | null; tableName: string } | null> {
|
||
|
|
const query = `
|
||
|
|
SELECT current_step_id, table_name
|
||
|
|
FROM flow_data_status
|
||
|
|
WHERE flow_definition_id = $1 AND record_id = $2
|
||
|
|
`;
|
||
|
|
|
||
|
|
const result = await db.query(query, [flowId, recordId]);
|
||
|
|
|
||
|
|
if (result.length === 0) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
currentStepId: result[0].current_step_id,
|
||
|
|
tableName: result[0].table_name,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|