182 lines
4.9 KiB
TypeScript
182 lines
4.9 KiB
TypeScript
|
|
/**
|
||
|
|
* 플로우 데이터 이동 서비스
|
||
|
|
* 데이터의 플로우 단계 이동 및 오딧 로그 관리
|
||
|
|
*/
|
||
|
|
|
||
|
|
import db from "../database/db";
|
||
|
|
import { FlowAuditLog } from "../types/flow";
|
||
|
|
import { FlowDefinitionService } from "./flowDefinitionService";
|
||
|
|
|
||
|
|
export class FlowDataMoveService {
|
||
|
|
private flowDefinitionService: FlowDefinitionService;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this.flowDefinitionService = new FlowDefinitionService();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 데이터를 다음 플로우 단계로 이동
|
||
|
|
*/
|
||
|
|
async moveDataToStep(
|
||
|
|
flowId: number,
|
||
|
|
recordId: string,
|
||
|
|
toStepId: number,
|
||
|
|
userId: string,
|
||
|
|
note?: string
|
||
|
|
): Promise<void> {
|
||
|
|
await db.transaction(async (client) => {
|
||
|
|
// 1. 플로우 정의 조회
|
||
|
|
const flowDef = await this.flowDefinitionService.findById(flowId);
|
||
|
|
if (!flowDef) {
|
||
|
|
throw new Error(`Flow definition not found: ${flowId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. 현재 상태 조회
|
||
|
|
const currentStatusQuery = `
|
||
|
|
SELECT current_step_id, table_name
|
||
|
|
FROM flow_data_status
|
||
|
|
WHERE flow_definition_id = $1 AND record_id = $2
|
||
|
|
`;
|
||
|
|
const currentStatusResult = await client.query(currentStatusQuery, [
|
||
|
|
flowId,
|
||
|
|
recordId,
|
||
|
|
]);
|
||
|
|
const currentStatus =
|
||
|
|
currentStatusResult.rows.length > 0
|
||
|
|
? {
|
||
|
|
currentStepId: currentStatusResult.rows[0].current_step_id,
|
||
|
|
tableName: currentStatusResult.rows[0].table_name,
|
||
|
|
}
|
||
|
|
: null;
|
||
|
|
const fromStepId = currentStatus?.currentStepId || null;
|
||
|
|
|
||
|
|
// 3. flow_data_status 업데이트 또는 삽입
|
||
|
|
if (currentStatus) {
|
||
|
|
await client.query(
|
||
|
|
`
|
||
|
|
UPDATE flow_data_status
|
||
|
|
SET current_step_id = $1, updated_by = $2, updated_at = NOW()
|
||
|
|
WHERE flow_definition_id = $3 AND record_id = $4
|
||
|
|
`,
|
||
|
|
[toStepId, userId, flowId, recordId]
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
await client.query(
|
||
|
|
`
|
||
|
|
INSERT INTO flow_data_status
|
||
|
|
(flow_definition_id, table_name, record_id, current_step_id, updated_by)
|
||
|
|
VALUES ($1, $2, $3, $4, $5)
|
||
|
|
`,
|
||
|
|
[flowId, flowDef.tableName, recordId, toStepId, userId]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 4. 오딧 로그 기록
|
||
|
|
await client.query(
|
||
|
|
`
|
||
|
|
INSERT INTO flow_audit_log
|
||
|
|
(flow_definition_id, table_name, record_id, from_step_id, to_step_id, changed_by, note)
|
||
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||
|
|
`,
|
||
|
|
[
|
||
|
|
flowId,
|
||
|
|
flowDef.tableName,
|
||
|
|
recordId,
|
||
|
|
fromStepId,
|
||
|
|
toStepId,
|
||
|
|
userId,
|
||
|
|
note || null,
|
||
|
|
]
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 여러 데이터를 동시에 다음 단계로 이동
|
||
|
|
*/
|
||
|
|
async moveBatchData(
|
||
|
|
flowId: number,
|
||
|
|
recordIds: string[],
|
||
|
|
toStepId: number,
|
||
|
|
userId: string,
|
||
|
|
note?: string
|
||
|
|
): Promise<void> {
|
||
|
|
for (const recordId of recordIds) {
|
||
|
|
await this.moveDataToStep(flowId, recordId, toStepId, userId, note);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 데이터의 플로우 이력 조회
|
||
|
|
*/
|
||
|
|
async getAuditLogs(
|
||
|
|
flowId: number,
|
||
|
|
recordId: string
|
||
|
|
): Promise<FlowAuditLog[]> {
|
||
|
|
const query = `
|
||
|
|
SELECT
|
||
|
|
fal.*,
|
||
|
|
fs_from.step_name as from_step_name,
|
||
|
|
fs_to.step_name as to_step_name
|
||
|
|
FROM flow_audit_log fal
|
||
|
|
LEFT JOIN flow_step fs_from ON fal.from_step_id = fs_from.id
|
||
|
|
LEFT JOIN flow_step fs_to ON fal.to_step_id = fs_to.id
|
||
|
|
WHERE fal.flow_definition_id = $1 AND fal.record_id = $2
|
||
|
|
ORDER BY fal.changed_at DESC
|
||
|
|
`;
|
||
|
|
|
||
|
|
const result = await db.query(query, [flowId, recordId]);
|
||
|
|
|
||
|
|
return result.map((row) => ({
|
||
|
|
id: row.id,
|
||
|
|
flowDefinitionId: row.flow_definition_id,
|
||
|
|
tableName: row.table_name,
|
||
|
|
recordId: row.record_id,
|
||
|
|
fromStepId: row.from_step_id,
|
||
|
|
toStepId: row.to_step_id,
|
||
|
|
changedBy: row.changed_by,
|
||
|
|
changedAt: row.changed_at,
|
||
|
|
note: row.note,
|
||
|
|
fromStepName: row.from_step_name,
|
||
|
|
toStepName: row.to_step_name,
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 특정 플로우의 모든 이력 조회
|
||
|
|
*/
|
||
|
|
async getFlowAuditLogs(
|
||
|
|
flowId: number,
|
||
|
|
limit: number = 100
|
||
|
|
): Promise<FlowAuditLog[]> {
|
||
|
|
const query = `
|
||
|
|
SELECT
|
||
|
|
fal.*,
|
||
|
|
fs_from.step_name as from_step_name,
|
||
|
|
fs_to.step_name as to_step_name
|
||
|
|
FROM flow_audit_log fal
|
||
|
|
LEFT JOIN flow_step fs_from ON fal.from_step_id = fs_from.id
|
||
|
|
LEFT JOIN flow_step fs_to ON fal.to_step_id = fs_to.id
|
||
|
|
WHERE fal.flow_definition_id = $1
|
||
|
|
ORDER BY fal.changed_at DESC
|
||
|
|
LIMIT $2
|
||
|
|
`;
|
||
|
|
|
||
|
|
const result = await db.query(query, [flowId, limit]);
|
||
|
|
|
||
|
|
return result.map((row) => ({
|
||
|
|
id: row.id,
|
||
|
|
flowDefinitionId: row.flow_definition_id,
|
||
|
|
tableName: row.table_name,
|
||
|
|
recordId: row.record_id,
|
||
|
|
fromStepId: row.from_step_id,
|
||
|
|
toStepId: row.to_step_id,
|
||
|
|
changedBy: row.changed_by,
|
||
|
|
changedAt: row.changed_at,
|
||
|
|
note: row.note,
|
||
|
|
fromStepName: row.from_step_name,
|
||
|
|
toStepName: row.to_step_name,
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
}
|