599 lines
26 KiB
PHP
599 lines
26 KiB
PHP
<?php
|
|
// Cache-buster: 1720638682
|
|
require_once __DIR__ . '/db/config.php';
|
|
require_once __DIR__ . '/lib/WorkflowExceptions.php';
|
|
|
|
class WorkflowEngine {
|
|
|
|
private $pdo;
|
|
|
|
public function __construct() {
|
|
$this->pdo = db();
|
|
}
|
|
|
|
public function getDashboardMatrix(): array {
|
|
// Get all people (potential assignees)
|
|
$stmt_people = $this->pdo->prepare("
|
|
SELECT p.*, bg.name as bni_group_name
|
|
FROM people p
|
|
LEFT JOIN bni_groups bg ON p.bni_group_id = bg.id
|
|
ORDER BY p.last_name, p.first_name
|
|
");
|
|
$stmt_people->execute();
|
|
$people = $stmt_people->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Fetch all process definitions with their JSON
|
|
$stmt_defs = $this->pdo->prepare("SELECT id, name, definition_json FROM process_definitions ORDER BY name");
|
|
$stmt_defs->execute();
|
|
$process_definitions_raw = $stmt_defs->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$definitions = [];
|
|
$definition_map = [];
|
|
foreach ($process_definitions_raw as $def) {
|
|
$definitions[$def['id']] = [
|
|
'id' => $def['id'],
|
|
'name' => $def['name'],
|
|
'is_active' => $def['is_active']
|
|
];
|
|
$definition_map[$def['id']] = !empty($def['definition_json']) ? json_decode($def['definition_json'], true) : null;
|
|
}
|
|
|
|
// Fetch instances
|
|
$stmt_instances = $this->pdo->prepare("SELECT * FROM process_instances");
|
|
$stmt_instances->execute();
|
|
$instances_data = $stmt_instances->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$instances = [];
|
|
foreach ($instances_data as $instance) {
|
|
$enriched_instance = $instance;
|
|
$def_id = $instance['process_definition_id'];
|
|
$node_id = $instance['current_node_id'];
|
|
|
|
$definition = $definition_map[$def_id] ?? null;
|
|
|
|
if ($definition && isset($definition['type']) && $definition['type'] === 'checklist') {
|
|
$tasks = $definition['tasks'] ?? [];
|
|
$instanceData = $instance['data_json'] ? json_decode($instance['data_json'], true) : [];
|
|
$totalTasks = count($tasks);
|
|
$completedTasks = 0;
|
|
if(is_array($instanceData)) {
|
|
foreach ($tasks as $task) {
|
|
if (!empty($instanceData[$task['code']])) {
|
|
$completedTasks++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if ($totalTasks > 0 && $completedTasks === $totalTasks) {
|
|
$status = 'completed';
|
|
} elseif ($completedTasks > 0) {
|
|
$status = 'in_progress';
|
|
} else {
|
|
$status = 'inactive';
|
|
}
|
|
$enriched_instance['computed_status'] = $status;
|
|
$enriched_instance['computed_reason'] = "$completedTasks/$totalTasks completed";
|
|
$enriched_instance['computed_next_step'] = '';
|
|
} else if ($definition && isset($definition['nodes'][$node_id])) {
|
|
$node_info = $definition['nodes'][$node_id];
|
|
$enriched_instance['computed_status'] = $node_info['ui_hints']['status'] ?? $instance['current_status'];
|
|
$enriched_instance['computed_reason'] = $node_info['ui_hints']['reason'] ?? $instance['current_reason'];
|
|
$enriched_instance['computed_next_step'] = $node_info['ui_hints']['next_step'] ?? $instance['suggested_next_step'];
|
|
} else {
|
|
$enriched_instance['computed_status'] = $instance['current_status'];
|
|
$enriched_instance['computed_reason'] = $instance['current_reason'];
|
|
$enriched_instance['computed_next_step'] = $instance['suggested_next_step'];
|
|
}
|
|
|
|
$instances[$instance['person_id']][$def_id] = $enriched_instance;
|
|
}
|
|
|
|
// Remove pre-emptive eligibility check. This is now handled on-demand by _get_instance_details.php
|
|
/*
|
|
foreach ($people as $person) {
|
|
foreach ($definitions as $def) {
|
|
if (!isset($instances[$person['id']][$def['id']])) {
|
|
$process_definition_raw = $process_definitions_raw[array_search($def['id'], array_column($process_definitions_raw, 'id'))];
|
|
$eligibility = $this->checkEligibility($person['id'], $process_definition_raw);
|
|
$instances[$person['id']][$def['id']] = ['is_eligible' => $eligibility['is_eligible']];
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
// Fetch ancillary data
|
|
$stmt_functions = $this->pdo->query("SELECT * FROM functions ORDER BY display_order");
|
|
$all_functions = $stmt_functions->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$stmt_person_functions = $this->pdo->query("SELECT user_id, function_id FROM user_functions");
|
|
$person_functions_map = [];
|
|
while ($row = $stmt_person_functions->fetch(PDO::FETCH_ASSOC)) {
|
|
$person_functions_map[$row['user_id']][] = $row['function_id'];
|
|
}
|
|
|
|
$stmt_bni_groups = $this->pdo->query("SELECT * FROM bni_groups ORDER BY name");
|
|
$bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
|
|
return [
|
|
'people' => $people,
|
|
'definitions' => array_values($definitions),
|
|
'instances' => $instances,
|
|
'all_functions' => $all_functions,
|
|
'person_functions_map' => $person_functions_map,
|
|
'bni_groups' => $bni_groups,
|
|
];
|
|
}
|
|
|
|
public function startProcess(string $processCode, int $personId, int $userId): int {
|
|
$this->pdo->beginTransaction();
|
|
try {
|
|
// 1. Find active process definition by code.
|
|
$stmt_def = $this->pdo->prepare("SELECT * FROM process_definitions WHERE code = ? AND is_active = 1");
|
|
$stmt_def->execute([$processCode]);
|
|
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$definition) {
|
|
// If no process definition is found, check if there is a definition for a checklist
|
|
$stmt_def = $this->pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
|
|
$stmt_def->execute([$processCode]);
|
|
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$definition) {
|
|
throw new WorkflowNotFoundException("Process definition with code or id '$processCode' not found.");
|
|
}
|
|
|
|
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : [];
|
|
if (empty($definition_json) || $definition_json['type'] !== 'checklist') {
|
|
throw new WorkflowNotAllowedException("Process definition with code '$processCode' not found or not a checklist.");
|
|
}
|
|
|
|
// For checklists, there's no start_node_id, so we can proceed with instance creation
|
|
$startNodeId = null;
|
|
|
|
} else {
|
|
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : [];
|
|
if (empty($definition_json) || !isset($definition_json['start_node_id'])) {
|
|
throw new WorkflowRuleFailedException("Process definition is missing start_node_id.");
|
|
}
|
|
$startNodeId = $definition_json['start_node_id'];
|
|
}
|
|
|
|
// 2. Create a new process instance.
|
|
$stmt_insert = $this->pdo->prepare(
|
|
"INSERT INTO process_instances (person_id, process_definition_id, current_node_id, current_status, last_activity_at) VALUES (?, ?, ?, 'in_progress', NOW())"
|
|
);
|
|
$stmt_insert->execute([$personId, $definition['id'], $startNodeId]);
|
|
$instanceId = $this->pdo->lastInsertId();
|
|
|
|
// 3. Create a system event for process start.
|
|
$this->addEvent($instanceId, 'system', 'Process started.', $startNodeId, [], $userId);
|
|
|
|
$this->pdo->commit();
|
|
return (int)$instanceId;
|
|
} catch (Exception $e) {
|
|
$this->pdo->rollBack();
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
public function getProcessState(int $instanceId): ?array {
|
|
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE id = ?");
|
|
$stmt->execute([$instanceId]);
|
|
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$instance) {
|
|
return null;
|
|
}
|
|
|
|
$stmt_def = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
|
|
$stmt_def->execute([$instance['process_definition_id']]);
|
|
$definition_json = $stmt_def->fetchColumn();
|
|
$definition = !empty($definition_json) ? json_decode($definition_json, true) : [];
|
|
|
|
$currentNodeId = $instance['current_node_id'];
|
|
$nodeInfo = $definition['nodes'][$currentNodeId] ?? null;
|
|
|
|
return [
|
|
'instance' => $instance,
|
|
'definition' => $definition,
|
|
'currentNode' => $nodeInfo,
|
|
];
|
|
}
|
|
|
|
public function applyTransition(int $instanceId, string $transitionId, array $inputPayload, int $userId): array {
|
|
$this->pdo->beginTransaction();
|
|
try {
|
|
$state = $this->getProcessState($instanceId);
|
|
if (!$state) {
|
|
throw new WorkflowNotFoundException("Process instance not found.");
|
|
}
|
|
|
|
$instance = $state['instance'];
|
|
$definition = $state['definition'];
|
|
$currentNodeId = $instance['current_node_id'];
|
|
|
|
$transition = null;
|
|
foreach ($definition['transitions'] as $t) {
|
|
if ($t['from'] === $currentNodeId && $t['id'] === $transitionId) {
|
|
$transition = $t;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$transition) {
|
|
throw new WorkflowNotAllowedException("Transition not found or not allowed from the current node.");
|
|
}
|
|
|
|
// TODO: Add rule validation here
|
|
|
|
$newNodeId = $transition['to'];
|
|
$newNodeInfo = $definition['nodes'][$newNodeId] ?? null;
|
|
|
|
$newStatus = $newNodeInfo['ui_hints']['status'] ?? 'in_progress';
|
|
$newReason = $newNodeInfo['ui_hints']['reason'] ?? '';
|
|
$newNextStep = $newNodeInfo['ui_hints']['next_step'] ?? '';
|
|
|
|
$stmt_update = $this->pdo->prepare(
|
|
"UPDATE process_instances SET current_node_id = ?, current_status = ?, current_reason = ?, suggested_next_step = ?, last_activity_at = NOW() WHERE id = ?"
|
|
);
|
|
$stmt_update->execute([
|
|
$newNodeId,
|
|
$newStatus,
|
|
$newReason,
|
|
$newNextStep,
|
|
$instanceId
|
|
]);
|
|
|
|
$message = $inputPayload['message'] ?? $transition['name'];
|
|
$this->addEvent($instanceId, 'transition_applied', $message, $newNodeId, $inputPayload, $userId);
|
|
|
|
if (isset($transition['actions'])) {
|
|
foreach ($transition['actions'] as $action) {
|
|
if ($action['type'] === 'start_process') {
|
|
$this->executeStartProcessAction($instance['person_id'], $action, $userId);
|
|
} elseif ($action['type'] === 'set_data') {
|
|
$this->executeSetDataAction($instanceId, $action);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->pdo->commit();
|
|
|
|
return [
|
|
'instanceId' => $instanceId,
|
|
'currentNodeId' => $newNodeId,
|
|
'currentStatus' => $newStatus,
|
|
'currentReason' => $newReason,
|
|
'suggestedNextStep' => $newNextStep,
|
|
'lastActivityAt' => date('Y-m-d H:i:s'),
|
|
];
|
|
} catch (Exception $e) {
|
|
$this->pdo->rollBack();
|
|
// Re-throw the original exception to be handled by the global error handler
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
public function addNote(int $instanceId, string $message, int $userId): bool {
|
|
$state = $this->getProcessState($instanceId);
|
|
if (!$state) {
|
|
throw new WorkflowNotFoundException("Process instance #$instanceId not found.");
|
|
}
|
|
$currentNodeId = $state['instance']['current_node_id'];
|
|
$payload = ['message' => $message];
|
|
$this->addEvent($instanceId, 'note', $message, $currentNodeId, $payload, $userId);
|
|
return true;
|
|
}
|
|
|
|
public function applyManualStatus(int $instanceId, string $status, string $reasonOrNote, int $userId): bool {
|
|
$this->pdo->beginTransaction();
|
|
try {
|
|
$state = $this->getProcessState($instanceId);
|
|
if (!$state) {
|
|
throw new WorkflowNotFoundException("Process instance #$instanceId not found.");
|
|
}
|
|
|
|
$stmt_update = $this->pdo->prepare(
|
|
"UPDATE process_instances SET current_status = ?, current_reason = ?, last_activity_at = NOW() WHERE id = ?"
|
|
);
|
|
$stmt_update->execute([$status, $reasonOrNote, $instanceId]);
|
|
|
|
$currentNodeId = $state['instance']['current_node_id'];
|
|
$message = "Status manually set to '$status'.";
|
|
if (!empty($reasonOrNote)) {
|
|
$message .= " Reason: $reasonOrNote";
|
|
}
|
|
|
|
$this->addEvent($instanceId, 'manual_status_change', $message, $currentNodeId, ['status' => $status, 'reason' => $reasonOrNote], $userId);
|
|
|
|
$this->pdo->commit();
|
|
return true;
|
|
} catch (Exception $e) {
|
|
$this->pdo->rollBack();
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
public function bulkAddNotes(array $notes): array
|
|
{
|
|
$results = [];
|
|
foreach ($notes as $note) {
|
|
try {
|
|
$this->addNote((int)$note['instance_id'], $note['message'], (int)$note['user_id']);
|
|
$results[] = ['instance_id' => $note['instance_id'], 'success' => true];
|
|
} catch (Exception $e) {
|
|
$results[] = ['instance_id' => $note['instance_id'], 'success' => false, 'error' => $e->getMessage()];
|
|
}
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
public function bulkManualStatus(array $statuses): array
|
|
{
|
|
$results = [];
|
|
foreach ($statuses as $status) {
|
|
try {
|
|
$this->applyManualStatus((int)$status['instance_id'], $status['status'], $status['reason'] ?? '', (int)$status['user_id']);
|
|
$results[] = ['instance_id' => $status['instance_id'], 'success' => true];
|
|
} catch (Exception $e) {
|
|
$results[] = ['instance_id' => $status['instance_id'], 'success' => false, 'error' => $e->getMessage()];
|
|
}
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
public function updateChecklistStatus(int $instanceId, string $taskCode, bool $isChecked, int $userId): array
|
|
{
|
|
$this->pdo->beginTransaction();
|
|
try {
|
|
// Get current data_json
|
|
$stmt = $this->pdo->prepare("SELECT data_json, process_definition_id FROM process_instances WHERE id = ?");
|
|
$stmt->execute([$instanceId]);
|
|
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$instance) {
|
|
throw new WorkflowNotFoundException("Process instance #$instanceId not found.");
|
|
}
|
|
|
|
$data = $instance['data_json'] ? json_decode($instance['data_json'], true) : [];
|
|
|
|
// Update the specific task status
|
|
$data[$taskCode] = $isChecked;
|
|
$newDataJson = json_encode($data);
|
|
|
|
// Save new data_json and update timestamp
|
|
$stmt = $this->pdo->prepare("UPDATE process_instances SET data_json = ?, last_activity_at = CURRENT_TIMESTAMP WHERE id = ?");
|
|
$stmt->execute([$newDataJson, $instanceId]);
|
|
|
|
// Add an event for the checklist update
|
|
$message = "Checklist task '$taskCode' marked as " . ($isChecked ? 'complete' : 'incomplete') . ".";
|
|
$this->addEvent($instanceId, 'checklist_update', $message, null, ['task' => $taskCode, 'checked' => $isChecked], $userId);
|
|
|
|
// Calculate progress
|
|
$stmt_def = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
|
|
$stmt_def->execute([$instance['process_definition_id']]);
|
|
$definitionJson = $stmt_def->fetchColumn();
|
|
$definition = json_decode($definitionJson, true);
|
|
$totalTasks = count($definition['tasks'] ?? []);
|
|
$completedTasks = count(array_filter($data));
|
|
|
|
$this->pdo->commit();
|
|
|
|
return [
|
|
'success' => true,
|
|
'progress' => [
|
|
'completed' => $completedTasks,
|
|
'total' => $totalTasks
|
|
],
|
|
'lastActivityAt' => date('d/m/y')
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
$this->pdo->rollBack();
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
|
|
private function addEvent(int $instanceId, string $eventType, string $message, ?string $nodeId, array $payload, int $userId): void {
|
|
$stmt = $this->pdo->prepare(
|
|
"INSERT INTO process_events (process_instance_id, event_type, message, node_id, payload_json, created_by, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())"
|
|
);
|
|
$stmt->execute([$instanceId, $eventType, $message, $nodeId, json_encode($payload), $userId]);
|
|
}
|
|
|
|
public function getOrCreateInstanceByDefId(int $personId, int $processDefinitionId, int $userId): ?array {
|
|
if (!is_int($processDefinitionId) || $processDefinitionId <= 0) {
|
|
throw new InvalidArgumentException("processDefinitionId must be a positive integer.");
|
|
}
|
|
if (!is_int($personId) || $personId <= 0) {
|
|
throw new InvalidArgumentException("personId must be a positive integer.");
|
|
}
|
|
|
|
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE `person_id` = ? AND `process_definition_id` = ?");
|
|
$stmt->execute([$personId, $processDefinitionId]);
|
|
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$instance) {
|
|
$stmt_def = $this->pdo->prepare("SELECT definition_json, code, is_active FROM process_definitions WHERE id = ?");
|
|
$stmt_def->execute([$processDefinitionId]);
|
|
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$definition) {
|
|
throw new WorkflowNotFoundException("Process definition #$processDefinitionId not found.");
|
|
}
|
|
|
|
if (empty($definition['is_active'])) {
|
|
throw new WorkflowNotAllowedException("Process is not active and cannot be started.");
|
|
}
|
|
|
|
$eligibility = $this->checkEligibility($personId, $processDefinitionId);
|
|
if (!$eligibility['is_eligible']) {
|
|
throw new WorkflowEligibilityException("Person is not eligible to start this process.", $eligibility['reasons']);
|
|
}
|
|
|
|
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : [];
|
|
|
|
$processCode = ($definition_json && isset($definition_json['type']) && $definition_json['type'] === 'checklist')
|
|
? (string) $processDefinitionId
|
|
: $definition['code'];
|
|
|
|
if ($processCode) {
|
|
$instanceId = $this->startProcess($processCode, $personId, $userId);
|
|
if ($instanceId) {
|
|
$stmt->execute([$personId, $processDefinitionId]);
|
|
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $instance ?: null;
|
|
}
|
|
|
|
public function getInstanceByDefId(int $personId, int $processDefinitionId): ?array {
|
|
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE `person_id` = ? AND `process_definition_id` = ?");
|
|
$stmt->execute([$personId, $processDefinitionId]);
|
|
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
return $instance ?: null;
|
|
}
|
|
|
|
public function getEvents(int $instanceId): array {
|
|
$stmt_events = $this->pdo->prepare("SELECT pe.*, p.email as user_email, p.first_name, p.last_name FROM process_events pe JOIN people p ON pe.created_by = p.id WHERE pe.process_instance_id = ? ORDER BY pe.created_at DESC");
|
|
$stmt_events->execute([$instanceId]);
|
|
return $stmt_events->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
public function getAvailableTransitions(int $instanceId): array {
|
|
$state = $this->getProcessState($instanceId);
|
|
if (!$state) {
|
|
return [];
|
|
}
|
|
|
|
$currentNodeId = $state['instance']['current_node_id'];
|
|
$definition = $state['definition'];
|
|
|
|
$transitions = [];
|
|
if (isset($definition['transitions'])) {
|
|
foreach ($definition['transitions'] as $t) {
|
|
if ($t['from'] === $currentNodeId) {
|
|
$transitions[] = $t;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $transitions;
|
|
}
|
|
|
|
public function getProcessDefinitionNodes(int $processDefinitionId): array {
|
|
$stmt = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
|
|
$stmt->execute([$processDefinitionId]);
|
|
$json = $stmt->fetchColumn();
|
|
|
|
if (!$json) {
|
|
return [];
|
|
}
|
|
|
|
$definition = !empty($json) ? json_decode($json, true) : [];
|
|
return $definition['nodes'] ?? [];
|
|
}
|
|
|
|
public function checkEligibility(int $personId, int $processDefinitionId): array {
|
|
$stmt_def = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
|
|
$stmt_def->execute([$processDefinitionId]);
|
|
$definition_json = $stmt_def->fetchColumn();
|
|
|
|
$definition = !empty($definition_json) ? json_decode($definition_json, true) : [];
|
|
|
|
$reasons = [];
|
|
if (empty($definition) || empty($definition['eligibility_rules'])) {
|
|
return ['is_eligible' => true, 'reasons' => []];
|
|
}
|
|
|
|
foreach ($definition['eligibility_rules'] as $rule) {
|
|
try {
|
|
$params = $rule['params'] ?? $rule;
|
|
switch ($rule['type']) {
|
|
case 'checkProcessCompletedRule':
|
|
case 'process_completed': // Backward compatibility
|
|
$this->checkProcessCompletedRule($personId, $params);
|
|
break;
|
|
case 'checkProcessDataRule':
|
|
$this->checkProcessDataRule($personId, $params);
|
|
break;
|
|
// Add other rule types here
|
|
}
|
|
} catch (WorkflowNotAllowedException $e) {
|
|
$reasons[] = $e->getMessage();
|
|
}
|
|
}
|
|
|
|
return ['is_eligible' => empty($reasons), 'reasons' => $reasons];
|
|
}
|
|
|
|
private function checkProcessCompletedRule(int $personId, array $params): void {
|
|
$stmt = $this->pdo->prepare("\n SELECT pi.id\n FROM process_instances pi\n JOIN process_definitions pd ON pi.process_definition_id = pd.id\n WHERE pi.person_id = ? AND pd.code = ? AND pi.current_status = ?\n ORDER BY pi.last_activity_at DESC\n LIMIT 1\n ");
|
|
$stmt->execute([$personId, $params['process_code'], $params['expected_status']]);
|
|
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$instance) {
|
|
throw new WorkflowNotAllowedException("Prerequisite process '{$params['process_code']}' not completed with status '{$params['expected_status']}'.");
|
|
}
|
|
}
|
|
|
|
private function checkProcessDataRule(int $personId, array $params): void {
|
|
$stmt = $this->pdo->prepare("
|
|
SELECT pi.data_json
|
|
FROM process_instances pi
|
|
JOIN process_definitions pd ON pi.process_definition_id = pd.id
|
|
WHERE pi.person_id = ? AND pd.code = ? AND pi.current_status = ?
|
|
ORDER BY pi.last_activity_at DESC
|
|
LIMIT 1
|
|
");
|
|
$stmt->execute([$personId, $params['process_code'], $params['expected_status']]);
|
|
$data_json = $stmt->fetchColumn();
|
|
|
|
if (!$data_json) {
|
|
throw new WorkflowNotAllowedException("Not eligible to start this process. Prerequisite process '{$params['process_code']}' not found with status '{$params['expected_status']}'.");
|
|
}
|
|
|
|
$data = json_decode($data_json, true);
|
|
if (!is_array($data)) {
|
|
$data = [];
|
|
}
|
|
|
|
foreach ($params['expected_data'] as $key => $expected_value) {
|
|
if (!isset($data[$key]) || $data[$key] !== $expected_value) {
|
|
throw new WorkflowNotAllowedException("Not eligible. Condition not met: '$key' is not '$expected_value'.");
|
|
}
|
|
}
|
|
}
|
|
|
|
private function executeStartProcessAction(int $personId, array $action, int $userId): void {
|
|
$stmt = $this->pdo->prepare("SELECT id FROM process_definitions WHERE code = ?");
|
|
$stmt->execute([$action['process_code']]);
|
|
$processDefinitionId = $stmt->fetchColumn();
|
|
|
|
if ($processDefinitionId) {
|
|
$this->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
|
|
}
|
|
}
|
|
|
|
private function executeSetDataAction(int $instanceId, array $action): void {
|
|
$stmt = $this->pdo->prepare("SELECT data_json FROM process_instances WHERE id = ?");
|
|
$stmt->execute([$instanceId]);
|
|
$dataJson = $stmt->fetchColumn();
|
|
|
|
$data = $dataJson ? json_decode($dataJson, true) : [];
|
|
|
|
$key = $action['params']['key'];
|
|
$value = $action['params']['value'];
|
|
$data[$key] = $value;
|
|
|
|
$newDataJson = json_encode($data);
|
|
|
|
$stmt_update = $this->pdo->prepare("UPDATE process_instances SET data_json = ? WHERE id = ?");
|
|
$stmt_update->execute([$newDataJson, $instanceId]);
|
|
}
|
|
} |