251120:1700 Backend T3.4
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
// สถานะของการดำเนินการในแต่ละขั้นตอน
|
||||
export enum StepStatus {
|
||||
PENDING = 'PENDING', // รอถึงคิว
|
||||
IN_PROGRESS = 'IN_PROGRESS', // ถึงคิวแล้ว รอ action
|
||||
COMPLETED = 'COMPLETED', // อนุมัติ/ดำเนินการเรียบร้อย
|
||||
REJECTED = 'REJECTED', // ถูกปัดตก
|
||||
SKIPPED = 'SKIPPED', // ถูกข้าม
|
||||
}
|
||||
|
||||
// การกระทำที่ผู้ใช้ทำได้
|
||||
export enum WorkflowAction {
|
||||
APPROVE = 'APPROVE', // อนุมัติ / ยืนยัน / ส่งต่อ
|
||||
REJECT = 'REJECT', // ปฏิเสธ (จบ workflow ทันที)
|
||||
RETURN = 'RETURN', // ส่งกลับ (ไปแก้มาใหม่)
|
||||
ACKNOWLEDGE = 'ACKNOWLEDGE', // รับทราบ (สำหรับ For Info)
|
||||
}
|
||||
|
||||
// ข้อมูลพื้นฐานของขั้นตอน (Step) ที่ Engine ต้องรู้
|
||||
export interface WorkflowStep {
|
||||
sequence: number; // ลำดับที่ (1, 2, 3...)
|
||||
assigneeId?: number; // User ID ที่รับผิดชอบ (ถ้าเจาะจงคน)
|
||||
organizationId?: number; // Org ID ที่รับผิดชอบ (ถ้าเจาะจงหน่วยงาน)
|
||||
roleId?: number; // Role ID ที่รับผิดชอบ (ถ้าเจาะจงตำแหน่ง)
|
||||
status: StepStatus; // สถานะปัจจุบัน
|
||||
}
|
||||
|
||||
// ผลลัพธ์ที่ Engine จะบอกเราหลังจากประมวลผลเสร็จ
|
||||
export interface TransitionResult {
|
||||
nextStepSequence: number | null; // ขั้นตอนต่อไปคือเลขที่เท่าไหร่ (null = จบ workflow)
|
||||
shouldUpdateStatus: boolean; // ต้องอัปเดตสถานะเอกสารหลักไหม? (เช่น เปลี่ยนจาก IN_REVIEW เป็น APPROVED)
|
||||
documentStatus?: string; // สถานะเอกสารหลักที่ควรจะเป็น
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { WorkflowEngineService } from './workflow-engine.service';
|
||||
|
||||
@Module({
|
||||
providers: [WorkflowEngineService],
|
||||
// ✅ เพิ่มบรรทัดนี้ เพื่ออนุญาตให้ Module อื่น (เช่น Correspondence) เรียกใช้ Service นี้ได้
|
||||
exports: [WorkflowEngineService],
|
||||
})
|
||||
export class WorkflowEngineModule {}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { WorkflowEngineService } from './workflow-engine.service';
|
||||
|
||||
describe('WorkflowEngineService', () => {
|
||||
let service: WorkflowEngineService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [WorkflowEngineService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<WorkflowEngineService>(WorkflowEngineService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import { Injectable, BadRequestException } from '@nestjs/common';
|
||||
import {
|
||||
WorkflowStep,
|
||||
WorkflowAction,
|
||||
StepStatus,
|
||||
TransitionResult,
|
||||
} from './interfaces/workflow.interface.js';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowEngineService {
|
||||
/**
|
||||
* คำนวณสถานะถัดไป (Next State Transition)
|
||||
* @param currentSequence ลำดับปัจจุบัน
|
||||
* @param totalSteps จำนวนขั้นตอนทั้งหมด
|
||||
* @param action การกระทำ (Approve/Reject/Return)
|
||||
* @param returnToSequence (Optional) ถ้า Return จะให้กลับไปขั้นไหน
|
||||
*/
|
||||
processAction(
|
||||
currentSequence: number,
|
||||
totalSteps: number,
|
||||
action: WorkflowAction,
|
||||
returnToSequence?: number,
|
||||
): TransitionResult {
|
||||
switch (action) {
|
||||
case WorkflowAction.APPROVE:
|
||||
case WorkflowAction.ACKNOWLEDGE:
|
||||
// ถ้าเป็นขั้นตอนสุดท้าย -> จบ Workflow
|
||||
if (currentSequence >= totalSteps) {
|
||||
return {
|
||||
nextStepSequence: null, // ไม่มีขั้นต่อไปแล้ว
|
||||
shouldUpdateStatus: true,
|
||||
documentStatus: 'COMPLETED', // หรือ APPROVED
|
||||
};
|
||||
}
|
||||
// ถ้ายังไม่จบ -> ไปขั้นต่อไป
|
||||
return {
|
||||
nextStepSequence: currentSequence + 1,
|
||||
shouldUpdateStatus: false,
|
||||
};
|
||||
|
||||
case WorkflowAction.REJECT:
|
||||
// จบ Workflow ทันทีแบบไม่สวย
|
||||
return {
|
||||
nextStepSequence: null,
|
||||
shouldUpdateStatus: true,
|
||||
documentStatus: 'REJECTED',
|
||||
};
|
||||
|
||||
case WorkflowAction.RETURN:
|
||||
// ย้อนกลับไปขั้นตอนก่อนหน้า (หรือที่ระบุ)
|
||||
const targetStep = returnToSequence || currentSequence - 1;
|
||||
if (targetStep < 1) {
|
||||
throw new BadRequestException('Cannot return beyond the first step');
|
||||
}
|
||||
return {
|
||||
nextStepSequence: targetStep,
|
||||
shouldUpdateStatus: true,
|
||||
documentStatus: 'REVISE_REQUIRED', // สถานะเอกสารเป็น "รอแก้ไข"
|
||||
};
|
||||
|
||||
default:
|
||||
throw new BadRequestException(`Invalid action: ${action}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ตรวจสอบว่า User คนนี้ มีสิทธิ์กด Action ในขั้นตอนนี้ไหม
|
||||
* (Logic เบื้องต้น - เดี๋ยวเราจะเชื่อมกับ RBAC จริงๆ ใน Service หลัก)
|
||||
*/
|
||||
validateAccess(
|
||||
step: WorkflowStep,
|
||||
userOrgId: number,
|
||||
userId: number,
|
||||
): boolean {
|
||||
// ถ้าขั้นตอนนี้ยังไม่ Active (เช่น PENDING หรือ SKIPPED) -> ห้ามยุ่ง
|
||||
if (step.status !== StepStatus.IN_PROGRESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// เช็คว่าตรงกับ Organization ที่กำหนดไหม
|
||||
if (step.organizationId && step.organizationId !== userOrgId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// เช็คว่าตรงกับ User ที่กำหนดไหม (ถ้าระบุ)
|
||||
if (step.assigneeId && step.assigneeId !== userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user