260228:1427 20260228:14:00 workflow update
All checks were successful
Build and Deploy / deploy (push) Successful in 2m37s

This commit is contained in:
admin
2026-02-28 14:27:33 +07:00
parent 276d06e950
commit efd5183906
6 changed files with 73 additions and 33 deletions

View File

@@ -52,12 +52,26 @@ export class WorkflowEngineController {
return this.workflowService.createDefinition(dto); return this.workflowService.createDefinition(dto);
} }
@Get('definitions')
@ApiOperation({ summary: 'ดึง Workflow Definition ทั้งหมด' })
@RequirePermission('system.manage_all')
async getDefinitions() {
return this.workflowService.getDefinitions();
}
@Get('definitions/:id')
@ApiOperation({ summary: 'ดึง Workflow Definition ด้วย ID' })
@RequirePermission('system.manage_all')
async getDefinitionById(@Param('id') id: string) {
return this.workflowService.getDefinitionById(id);
}
@Patch('definitions/:id') @Patch('definitions/:id')
@ApiOperation({ summary: 'แก้ไข Workflow Definition (Re-compile DSL)' }) @ApiOperation({ summary: 'แก้ไข Workflow Definition (Re-compile DSL)' })
@RequirePermission('system.manage_all') @RequirePermission('system.manage_all')
async updateDefinition( async updateDefinition(
@Param('id') id: string, @Param('id') id: string,
@Body() dto: UpdateWorkflowDefinitionDto, @Body() dto: UpdateWorkflowDefinitionDto
) { ) {
return this.workflowService.update(id, dto); return this.workflowService.update(id, dto);
} }
@@ -81,7 +95,7 @@ export class WorkflowEngineController {
async processTransition( async processTransition(
@Param('id') instanceId: string, @Param('id') instanceId: string,
@Body() dto: WorkflowTransitionDto, @Body() dto: WorkflowTransitionDto,
@Request() req: any, @Request() req: any
) { ) {
// ดึง User ID จาก Token (req.user มาจาก JwtStrategy) // ดึง User ID จาก Token (req.user มาจาก JwtStrategy)
const userId = req.user?.userId; const userId = req.user?.userId;
@@ -91,7 +105,7 @@ export class WorkflowEngineController {
dto.action, dto.action,
userId, userId,
dto.comment, dto.comment,
dto.payload, dto.payload
); );
} }

View File

@@ -121,6 +121,33 @@ export class WorkflowEngineService {
return this.workflowDefRepo.save(definition); return this.workflowDefRepo.save(definition);
} }
/**
* ดึง Workflow Definition ทั้งหมด (เฉพาะ Version ล่าสุดของแต่ละ Workflow Code)
*/
async getDefinitions(): Promise<WorkflowDefinition[]> {
// หา version ล่าสุดของแต่ละ workflow_code
// ใช้ query builder เพื่อ group by และหา max version
const latestDefinitions = await this.workflowDefRepo
.createQueryBuilder('def')
.where(
'def.version = (SELECT MAX(sub.version) FROM workflow_definitions sub WHERE sub.workflow_code = def.workflow_code)',
)
.getMany();
return latestDefinitions;
}
/**
* ดึง Workflow Definition ตาม ID หรือ Code
*/
async getDefinitionById(id: string): Promise<WorkflowDefinition> {
const definition = await this.workflowDefRepo.findOne({ where: { id } });
if (!definition) {
throw new NotFoundException(`Workflow Definition with ID "${id}" not found`);
}
return definition;
}
/** /**
* ดึง Action ที่ทำได้ ณ State ปัจจุบัน * ดึง Action ที่ทำได้ ณ State ปัจจุบัน
*/ */

View File

@@ -21,7 +21,7 @@ import Link from 'next/link';
export default function WorkflowEditPage() { export default function WorkflowEditPage() {
const params = useParams(); const params = useParams();
const router = useRouter(); const router = useRouter();
const id = params?.id === 'new' ? null : Number(params?.id); const id = params?.id === 'new' ? null : params?.id as string;
const [workflowData, setWorkflowData] = useState<Partial<Workflow>>({ const [workflowData, setWorkflowData] = useState<Partial<Workflow>>({
workflowName: '', workflowName: '',
@@ -31,7 +31,7 @@ export default function WorkflowEditPage() {
isActive: true, isActive: true,
}); });
const { data: fetchedWorkflow, isLoading: loadingWorkflow } = useWorkflowDefinition(id as number); const { data: fetchedWorkflow, isLoading: loadingWorkflow } = useWorkflowDefinition(id as string);
const createMutation = useCreateWorkflowDefinition(); const createMutation = useCreateWorkflowDefinition();
const updateMutation = useUpdateWorkflowDefinition(); const updateMutation = useUpdateWorkflowDefinition();

View File

@@ -1,35 +1,15 @@
'use client'; 'use client';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card'; import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Plus, Edit, Copy, Trash, Loader2 } from 'lucide-react'; import { Plus, Edit, Copy, Trash, Loader2 } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { useWorkflowDefinitions } from '@/hooks/use-workflows';
import { Workflow } from '@/types/workflow'; import { Workflow } from '@/types/workflow';
import { workflowApi } from '@/lib/api/workflows';
import { toast } from 'sonner';
export default function WorkflowsPage() { export default function WorkflowsPage() {
const [workflows, setWorkflows] = useState<Workflow[]>([]); const { data: workflows = [], isLoading: loading } = useWorkflowDefinitions();
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchWorkflows = async () => {
setLoading(true);
try {
const data = await workflowApi.getWorkflows();
setWorkflows(data);
} catch (error) {
toast.error('Failed to load workflows');
console.error('[WorkflowsPage]', error);
} finally {
setLoading(false);
}
};
fetchWorkflows();
}, []);
return ( return (
<div className="p-6 space-y-6"> <div className="p-6 space-y-6">
@@ -52,7 +32,7 @@ export default function WorkflowsPage() {
</div> </div>
) : ( ) : (
<div className="grid gap-4"> <div className="grid gap-4">
{workflows.map((workflow) => ( {workflows.map((workflow: Workflow) => (
<Card key={workflow.workflowId} className="p-6"> <Card key={workflow.workflowId} className="p-6">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div className="flex-1"> <div className="flex-1">

View File

@@ -7,6 +7,23 @@ import {
GetAvailableActionsDto, GetAvailableActionsDto,
} from '@/types/dto/workflow-engine/workflow-engine.dto'; } from '@/types/dto/workflow-engine/workflow-engine.dto';
import { Workflow } from '@/types/workflow';
const mapWorkflow = (backendObj: any): Workflow => {
if (!backendObj) throw new Error('Workflow not found');
return {
workflowId: backendObj.id,
workflowName: backendObj.dsl?.workflowName || backendObj.workflow_code,
description: backendObj.description || backendObj.dsl?.description || '',
workflowType: backendObj.workflow_code,
version: backendObj.version || 1,
isActive: backendObj.is_active,
dslDefinition: typeof backendObj.dsl === 'string' ? backendObj.dsl : backendObj.dsl?.dslDefinition || JSON.stringify(backendObj.dsl, null, 2),
stepCount: backendObj.compiled?.states ? Object.keys(backendObj.compiled.states).length : 0,
updatedAt: backendObj.updated_at || new Date().toISOString(),
};
};
export const workflowEngineService = { export const workflowEngineService = {
// --- Engine Execution (Low-Level) --- // --- Engine Execution (Low-Level) ---
@@ -34,18 +51,20 @@ export const workflowEngineService = {
* ดึง Workflow Definition ทั้งหมด * ดึง Workflow Definition ทั้งหมด
* GET /workflow-engine/definitions * GET /workflow-engine/definitions
*/ */
getDefinitions: async () => { getDefinitions: async (): Promise<Workflow[]> => {
const response = await apiClient.get('/workflow-engine/definitions'); const response = await apiClient.get('/workflow-engine/definitions');
return response.data?.data || response.data; const data = response.data?.data || response.data;
return Array.isArray(data) ? data.map(mapWorkflow) : data;
}, },
/** /**
* ดึง Workflow Definition ตาม ID * ดึง Workflow Definition ตาม ID
* GET /workflow-engine/definitions/:id * GET /workflow-engine/definitions/:id
*/ */
getDefinitionById: async (id: string | number) => { getDefinitionById: async (id: string | number): Promise<Workflow> => {
const response = await apiClient.get(`/workflow-engine/definitions/${id}`); const response = await apiClient.get(`/workflow-engine/definitions/${id}`);
return response.data?.data || response.data; const data = response.data?.data || response.data;
return mapWorkflow(data);
}, },
/** /**

View File

@@ -11,7 +11,7 @@ export interface WorkflowStep {
} }
export interface Workflow { export interface Workflow {
workflowId: number; workflowId: string | number;
workflowName: string; workflowName: string;
description: string; description: string;
workflowType: WorkflowType; workflowType: WorkflowType;