690406:2310 Done Task BE-ERR-01
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
NotFoundException,
|
||||
ValidationException,
|
||||
WorkflowException,
|
||||
} from '../../../common/exceptions';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { WorkflowDslSchema, WorkflowDsl } from './workflow-dsl.schema';
|
||||
@@ -36,16 +41,18 @@ export class WorkflowDslParser {
|
||||
return await this.workflowDefRepo.save(definition);
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof SyntaxError) {
|
||||
throw new BadRequestException(`Invalid JSON: ${error.message}`);
|
||||
throw new ValidationException(`Invalid JSON: ${error.message}`);
|
||||
}
|
||||
const err = error as {
|
||||
name?: string;
|
||||
errors?: unknown;
|
||||
errors?: unknown[];
|
||||
message?: string;
|
||||
};
|
||||
if (err.name === 'ZodError') {
|
||||
throw new BadRequestException(
|
||||
`Invalid workflow DSL: ${JSON.stringify(err.errors)}`
|
||||
throw new WorkflowException(
|
||||
'INVALID_WORKFLOW_DSL',
|
||||
`Invalid workflow DSL: ${JSON.stringify(err.errors)}`,
|
||||
'Workflow DSL ไม่ถูกต้อง'
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
@@ -66,16 +73,20 @@ export class WorkflowDslParser {
|
||||
|
||||
// 1. Validate initial state
|
||||
if (!stateSet.has(dsl.initialState)) {
|
||||
throw new BadRequestException(
|
||||
`Initial state "${dsl.initialState}" not found in states array`
|
||||
throw new WorkflowException(
|
||||
'DSL_INVALID_INITIAL_STATE',
|
||||
`Initial state "${dsl.initialState}" not found in states array`,
|
||||
'Initial State ไม่พบใน States Array'
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Validate final states
|
||||
dsl.finalStates.forEach((state) => {
|
||||
if (!stateSet.has(state)) {
|
||||
throw new BadRequestException(
|
||||
`Final state "${state}" not found in states array`
|
||||
throw new WorkflowException(
|
||||
'DSL_INVALID_FINAL_STATE',
|
||||
`Final state "${state}" not found in states array`,
|
||||
'Final State ไม่พบใน States Array'
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -86,15 +97,19 @@ export class WorkflowDslParser {
|
||||
dsl.transitions.forEach((transition, index) => {
|
||||
// Check 'from' state
|
||||
if (!stateSet.has(transition.from)) {
|
||||
throw new BadRequestException(
|
||||
`Transition ${index}: 'from' state "${transition.from}" not found in states array`
|
||||
throw new WorkflowException(
|
||||
'DSL_INVALID_TRANSITION_FROM',
|
||||
`Transition ${index}: 'from' state "${transition.from}" not found in states array`,
|
||||
'Transition อ้างอิง State ที่ไม่พบ'
|
||||
);
|
||||
}
|
||||
|
||||
// Check 'to' state
|
||||
if (!stateSet.has(transition.to)) {
|
||||
throw new BadRequestException(
|
||||
`Transition ${index}: 'to' state "${transition.to}" not found in states array`
|
||||
throw new WorkflowException(
|
||||
'DSL_INVALID_TRANSITION_TO',
|
||||
`Transition ${index}: 'to' state "${transition.to}" not found in states array`,
|
||||
'Transition อ้างอิง State ที่ไม่พบ'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,8 +135,10 @@ export class WorkflowDslParser {
|
||||
dsl.transitions.forEach((transition) => {
|
||||
const key = `${transition.from}-${transition.trigger}-${transition.to}`;
|
||||
if (transitionKeys.has(key)) {
|
||||
throw new BadRequestException(
|
||||
`Duplicate transition: ${transition.from} --[${transition.trigger}]--> ${transition.to}`
|
||||
throw new WorkflowException(
|
||||
'DSL_DUPLICATE_TRANSITION',
|
||||
`Duplicate transition: ${transition.from} --[${transition.trigger}]--> ${transition.to}`,
|
||||
'DSL มี Transition ซ้ำซ้อน'
|
||||
);
|
||||
}
|
||||
transitionKeys.add(key);
|
||||
@@ -158,9 +175,7 @@ export class WorkflowDslParser {
|
||||
});
|
||||
|
||||
if (!definition) {
|
||||
throw new BadRequestException(
|
||||
`Workflow definition ${definitionId} not found`
|
||||
);
|
||||
throw new NotFoundException('Workflow definition', String(definitionId));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -171,8 +186,10 @@ export class WorkflowDslParser {
|
||||
`Failed to parse stored DSL for definition ${definitionId}`,
|
||||
error
|
||||
);
|
||||
throw new BadRequestException(
|
||||
`Invalid stored DSL: ${error instanceof Error ? error.message : String(error)}`
|
||||
throw new WorkflowException(
|
||||
'INVALID_STORED_DSL',
|
||||
`Invalid stored DSL: ${error instanceof Error ? error.message : String(error)}`,
|
||||
'DSL ที่บันทึกไว้ไม่ถูกต้อง'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
// File: src/modules/workflow-engine/workflow-dsl.service.ts
|
||||
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
ValidationException,
|
||||
WorkflowException,
|
||||
} from '../../common/exceptions';
|
||||
|
||||
// ==========================================
|
||||
// 1. Interfaces for RAW DSL (Input from User)
|
||||
@@ -86,8 +90,11 @@ export class WorkflowDslService {
|
||||
for (const rawState of dsl.states) {
|
||||
if (rawState.initial) {
|
||||
if (initialFound) {
|
||||
throw new BadRequestException(
|
||||
`DSL Error: Multiple initial states found (at "${rawState.name}").`
|
||||
throw new WorkflowException(
|
||||
'DSL_MULTIPLE_INITIAL_STATES',
|
||||
`DSL Error: Multiple initial states found (at "${rawState.name}")`,
|
||||
'DSL มี Initial State หลายค่า แต่ละ Workflow ต้องมีเพียง Initial State เดียว',
|
||||
['ตรวจสอบโครงสร้าง DSL และแก้ไข Initial State']
|
||||
);
|
||||
}
|
||||
compiled.initialState = rawState.name;
|
||||
@@ -104,8 +111,11 @@ export class WorkflowDslService {
|
||||
for (const [action, rule] of Object.entries(rawState.on)) {
|
||||
// Validation: Target state must exist
|
||||
if (!definedStates.has(rule.to)) {
|
||||
throw new BadRequestException(
|
||||
`DSL Error: State "${rawState.name}" transitions via "${action}" to unknown state "${rule.to}".`
|
||||
throw new WorkflowException(
|
||||
'DSL_UNKNOWN_TRANSITION_TARGET',
|
||||
`DSL Error: State "${rawState.name}" transitions via "${action}" to unknown state "${rule.to}"`,
|
||||
'DSL อ้างอิง State ที่ไม่พบ',
|
||||
['ตรวจสอบชื่อ State ที่กำหนดใน Transition']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,7 +143,12 @@ export class WorkflowDslService {
|
||||
}
|
||||
|
||||
if (!initialFound) {
|
||||
throw new BadRequestException('DSL Error: No initial state defined.');
|
||||
throw new WorkflowException(
|
||||
'DSL_NO_INITIAL_STATE',
|
||||
'DSL Error: No initial state defined',
|
||||
'DSL ไม่มีการกำหนด Initial State',
|
||||
['เพิ่ม initial: true ใน State หนึ่ง']
|
||||
);
|
||||
}
|
||||
|
||||
return compiled;
|
||||
@@ -153,15 +168,21 @@ export class WorkflowDslService {
|
||||
|
||||
// 1. Validate State Existence
|
||||
if (!stateConfig) {
|
||||
throw new BadRequestException(
|
||||
`Runtime Error: Current state "${currentState}" is invalid.`
|
||||
throw new WorkflowException(
|
||||
'WORKFLOW_INVALID_CURRENT_STATE',
|
||||
`Runtime Error: Current state "${currentState}" is invalid`,
|
||||
'Workflow อยู่ในสถานะที่ไม่รู้จัก',
|
||||
['ตรวจสอบ DSL ของ Workflow']
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Check if terminal
|
||||
if (stateConfig.terminal) {
|
||||
throw new BadRequestException(
|
||||
`Runtime Error: Cannot transition from terminal state "${currentState}".`
|
||||
throw new WorkflowException(
|
||||
'WORKFLOW_TERMINAL_STATE',
|
||||
`Runtime Error: Cannot transition from terminal state "${currentState}"`,
|
||||
'ไม่สามารถดำเนินการจาก State สุดท้ายได้',
|
||||
['เอกสารสิ้นสุดกระบวนการแล้ว']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -169,8 +190,11 @@ export class WorkflowDslService {
|
||||
const transition = stateConfig.transitions[action];
|
||||
if (!transition) {
|
||||
const allowed = Object.keys(stateConfig.transitions).join(', ');
|
||||
throw new BadRequestException(
|
||||
`Invalid Action: "${action}" is not allowed from "${currentState}". Allowed: [${allowed}]`
|
||||
throw new WorkflowException(
|
||||
'WORKFLOW_INVALID_ACTION',
|
||||
`Invalid Action: "${action}" is not allowed from "${currentState}". Allowed: [${allowed}]`,
|
||||
`ไม่สามารถดำเนินการ "${action}" ในสถานะปัจจุบัน ทำได้: [${allowed}]`,
|
||||
['เลือกการดำเนินการที่อนุญาตจากรายการ']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -181,8 +205,11 @@ export class WorkflowDslService {
|
||||
if (transition.condition) {
|
||||
const isMet = this.evaluateCondition(transition.condition, context);
|
||||
if (!isMet) {
|
||||
throw new BadRequestException(
|
||||
'Condition Failed: The criteria for this transition are not met.'
|
||||
throw new WorkflowException(
|
||||
'WORKFLOW_CONDITION_NOT_MET',
|
||||
'Condition Failed: The criteria for this transition are not met',
|
||||
'เงื่อนไขสำหรับการดำเนินการนี้ไม่ผ่าน',
|
||||
['ตรวจสอบเงื่อนไขที่กำหนดใน Workflow']
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -199,12 +226,12 @@ export class WorkflowDslService {
|
||||
|
||||
private validateSchemaStructure(dsl: unknown) {
|
||||
if (!dsl || typeof dsl !== 'object') {
|
||||
throw new BadRequestException('DSL must be a JSON object.');
|
||||
throw new ValidationException('DSL must be a JSON object');
|
||||
}
|
||||
const d = dsl as Record<string, unknown>;
|
||||
if (!d.workflow || !d.states || !Array.isArray(d.states)) {
|
||||
throw new BadRequestException(
|
||||
'DSL Error: Missing required fields (workflow, states).'
|
||||
throw new ValidationException(
|
||||
'DSL Error: Missing required fields (workflow, states)'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -226,15 +253,23 @@ export class WorkflowDslService {
|
||||
if (requiredRoles.length > 0) {
|
||||
const hasRole = requiredRoles.some((r) => userRoles.includes(r));
|
||||
if (!hasRole) {
|
||||
throw new BadRequestException(
|
||||
`Access Denied: Required roles [${requiredRoles.join(', ')}]`
|
||||
throw new WorkflowException(
|
||||
'WORKFLOW_ROLE_REQUIRED',
|
||||
`Access Denied: Required roles [${requiredRoles.join(', ')}]`,
|
||||
`ต้องมี Role: [${requiredRoles.join(', ')}] จึงจะดำเนินการนี้ได้`,
|
||||
['ติดต่อผู้ดูแลระบบเพื่อขอสิทธิ์']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check Specific User
|
||||
if (req.userId && String(req.userId) !== String(userId)) {
|
||||
throw new BadRequestException('Access Denied: User mismatch.');
|
||||
throw new WorkflowException(
|
||||
'WORKFLOW_USER_MISMATCH',
|
||||
'Access Denied: User mismatch',
|
||||
'ผู้ใช้ไม่ได้รับอนุญาตให้ดำเนินการนี้',
|
||||
['ตรวจสอบว่าเล็็กชื่ออีเมลที่ป้อนให้เข้าสู่ระบบ']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
// File: src/modules/workflow-engine/workflow-engine.service.ts
|
||||
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { NotFoundException, WorkflowException } from '../../common/exceptions';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
// Entities
|
||||
import { WorkflowDefinition } from './entities/workflow-definition.entity';
|
||||
import { WorkflowHistory } from './entities/workflow-history.entity';
|
||||
@@ -104,9 +99,7 @@ export class WorkflowEngineService {
|
||||
): Promise<WorkflowDefinition> {
|
||||
const definition = await this.workflowDefRepo.findOne({ where: { id } });
|
||||
if (!definition) {
|
||||
throw new NotFoundException(
|
||||
`Workflow Definition with ID "${id}" not found`
|
||||
);
|
||||
throw new NotFoundException('Workflow Definition', id);
|
||||
}
|
||||
|
||||
if (dto.dsl) {
|
||||
@@ -115,8 +108,11 @@ export class WorkflowEngineService {
|
||||
definition.dsl = dto.dsl as unknown as Record<string, unknown>;
|
||||
definition.compiled = compiled as unknown as Record<string, unknown>;
|
||||
} catch (error: unknown) {
|
||||
throw new BadRequestException(
|
||||
`Invalid DSL: ${error instanceof Error ? error.message : String(error)}`
|
||||
throw new WorkflowException(
|
||||
'INVALID_WORKFLOW_DSL',
|
||||
`Invalid DSL: ${error instanceof Error ? error.message : String(error)}`,
|
||||
'Workflow DSL ไม่ถูกต้อง กรุณาตรวจสอบโครงสร้าง',
|
||||
['ตรวจสอบ syntax ของ DSL', 'ดูตัวอย่าง DSL ที่ถูกต้อง']
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -149,9 +145,7 @@ export class WorkflowEngineService {
|
||||
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`
|
||||
);
|
||||
throw new NotFoundException('Workflow Definition', id);
|
||||
}
|
||||
return definition;
|
||||
}
|
||||
@@ -197,9 +191,7 @@ export class WorkflowEngineService {
|
||||
});
|
||||
|
||||
if (!definition) {
|
||||
throw new NotFoundException(
|
||||
`Workflow "${workflowCode}" not found or inactive.`
|
||||
);
|
||||
throw new NotFoundException('Workflow', workflowCode);
|
||||
}
|
||||
|
||||
// 2. หา Initial State จาก Compiled Structure
|
||||
@@ -209,8 +201,11 @@ export class WorkflowEngineService {
|
||||
const initialState = compiled.initialState;
|
||||
|
||||
if (!initialState) {
|
||||
throw new BadRequestException(
|
||||
`Workflow "${workflowCode}" has no initial state defined.`
|
||||
throw new WorkflowException(
|
||||
'WORKFLOW_NO_INITIAL_STATE',
|
||||
`Workflow "${workflowCode}" has no initial state defined`,
|
||||
'Workflow ไม่มี Initial State ที่กำหนด',
|
||||
['ตรวจสอบ DSL ของ Workflow นี้']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -242,9 +237,7 @@ export class WorkflowEngineService {
|
||||
});
|
||||
|
||||
if (!instance) {
|
||||
throw new NotFoundException(
|
||||
`Workflow Instance "${instanceId}" not found`
|
||||
);
|
||||
throw new NotFoundException('Workflow Instance', instanceId);
|
||||
}
|
||||
|
||||
return instance;
|
||||
@@ -276,14 +269,15 @@ export class WorkflowEngineService {
|
||||
});
|
||||
|
||||
if (!instance) {
|
||||
throw new NotFoundException(
|
||||
`Workflow Instance "${instanceId}" not found.`
|
||||
);
|
||||
throw new NotFoundException('Workflow Instance', instanceId);
|
||||
}
|
||||
|
||||
if (instance.status !== WorkflowStatus.ACTIVE) {
|
||||
throw new BadRequestException(
|
||||
`Workflow is not active (Status: ${instance.status}).`
|
||||
throw new WorkflowException(
|
||||
'WORKFLOW_NOT_ACTIVE',
|
||||
`Workflow is not active (Status: ${instance.status})`,
|
||||
'Workflow ไม่อยู่ในสถานะ Active',
|
||||
['ตรวจสอบสถานะของ Workflow']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -427,7 +421,12 @@ export class WorkflowEngineService {
|
||||
case 'RETURN': {
|
||||
const targetStep = returnToSequence || currentSequence - 1;
|
||||
if (targetStep < 1) {
|
||||
throw new BadRequestException('Cannot return beyond the first step');
|
||||
throw new WorkflowException(
|
||||
'WORKFLOW_INVALID_RETURN_TARGET',
|
||||
'Cannot return beyond the first step',
|
||||
'ไม่สามารถส่งคืนไปเกินกว่าขั้นตอนแรกได้',
|
||||
['ตรวจสอบลำดับขั้นตอนที่ต้องการส่งคืน']
|
||||
);
|
||||
}
|
||||
return {
|
||||
nextStepSequence: targetStep,
|
||||
|
||||
Reference in New Issue
Block a user