Głęboka refactoryzacja kodu

This commit is contained in:
Flatlogic Bot 2026-01-10 20:46:53 +00:00
parent a703aeb1e2
commit 3b1a26adc9
4 changed files with 372 additions and 215 deletions

View File

@ -1,6 +1,7 @@
<?php <?php
// Cache-buster: 1720638682 // Cache-buster: 1720638682
require_once __DIR__ . '/db/config.php'; require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/lib/WorkflowExceptions.php';
class WorkflowEngine { class WorkflowEngine {
@ -31,7 +32,8 @@ class WorkflowEngine {
foreach ($process_definitions_raw as $def) { foreach ($process_definitions_raw as $def) {
$definitions[$def['id']] = [ $definitions[$def['id']] = [
'id' => $def['id'], 'id' => $def['id'],
'name' => $def['name'] 'name' => $def['name'],
'is_active' => $def['is_active']
]; ];
$definition_map[$def['id']] = !empty($def['definition_json']) ? json_decode($def['definition_json'], true) : null; $definition_map[$def['id']] = !empty($def['definition_json']) ? json_decode($def['definition_json'], true) : null;
} }
@ -87,20 +89,18 @@ class WorkflowEngine {
$instances[$instance['person_id']][$def_id] = $enriched_instance; $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 ($people as $person) {
foreach ($definitions as $def) { foreach ($definitions as $def) {
if (!isset($instances[$person['id']][$def['id']])) { if (!isset($instances[$person['id']][$def['id']])) {
$is_eligible = true; $process_definition_raw = $process_definitions_raw[array_search($def['id'], array_column($process_definitions_raw, 'id'))];
try { $eligibility = $this->checkEligibility($person['id'], $process_definition_raw);
$process_definition = $process_definitions_raw[array_search($def['id'], array_column($process_definitions_raw, 'id'))]; $instances[$person['id']][$def['id']] = ['is_eligible' => $eligibility['is_eligible']];
$this->checkEligibility($person['id'], $process_definition);
} catch (WorkflowNotAllowedException $e) {
$is_eligible = false;
}
$instances[$person['id']][$def['id']] = ['is_eligible' => $is_eligible];
} }
} }
} }
*/
// Fetch ancillary data // Fetch ancillary data
$stmt_functions = $this->pdo->query("SELECT * FROM functions ORDER BY display_order"); $stmt_functions = $this->pdo->query("SELECT * FROM functions ORDER BY display_order");
@ -253,6 +253,8 @@ class WorkflowEngine {
foreach ($transition['actions'] as $action) { foreach ($transition['actions'] as $action) {
if ($action['type'] === 'start_process') { if ($action['type'] === 'start_process') {
$this->executeStartProcessAction($instance['person_id'], $action, $userId); $this->executeStartProcessAction($instance['person_id'], $action, $userId);
} elseif ($action['type'] === 'set_data') {
$this->executeSetDataAction($instanceId, $action);
} }
} }
} }
@ -436,6 +438,13 @@ class WorkflowEngine {
return $instance ?: null; 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 { 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 = $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]); $stmt_events->execute([$instanceId]);
@ -476,44 +485,101 @@ class WorkflowEngine {
return $definition['nodes'] ?? []; return $definition['nodes'] ?? [];
} }
private function checkEligibility(int $personId, array $definition): void { public function checkEligibility(int $personId, int $processDefinitionId): array {
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : []; $stmt_def = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
if (empty($definition_json) || empty($definition_json['eligibility_rules'])) { $stmt_def->execute([$processDefinitionId]);
return; // No rules to check $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_json['eligibility_rules'] as $rule) { foreach ($definition['eligibility_rules'] as $rule) {
try {
$params = $rule['params'] ?? $rule;
switch ($rule['type']) { switch ($rule['type']) {
case 'process_completed': case 'checkProcessCompletedRule':
$this->checkProcessCompletedRule($personId, $rule); case 'process_completed': // Backward compatibility
$this->checkProcessCompletedRule($personId, $params);
break;
case 'checkProcessDataRule':
$this->checkProcessDataRule($personId, $params);
break; break;
// Add other rule types here // Add other rule types here
} }
} catch (WorkflowNotAllowedException $e) {
$reasons[] = $e->getMessage();
} }
} }
private function checkProcessCompletedRule(int $personId, array $rule): void { return ['is_eligible' => empty($reasons), 'reasons' => $reasons];
$stmt = $this->pdo->prepare(" }
SELECT pi.id
FROM process_instances pi private function checkProcessCompletedRule(int $personId, array $params): void {
JOIN process_definitions pd ON pi.process_definition_id = pd.id $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 ");
WHERE pi.person_id = ? AND pd.name = ? AND pi.current_status = ? $stmt->execute([$personId, $params['process_code'], $params['expected_status']]);
");
$stmt->execute([$personId, $rule['process_name'], $rule['expected_status']]);
$instance = $stmt->fetch(PDO::FETCH_ASSOC); $instance = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$instance) { if (!$instance) {
throw new WorkflowNotAllowedException("Not eligible to start this process. Prerequisite process '{$rule['process_name']}' not completed with status '{$rule['expected_status']}'."); 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 { private function executeStartProcessAction(int $personId, array $action, int $userId): void {
$stmt = $this->pdo->prepare("SELECT id FROM process_definitions WHERE name = ?"); $stmt = $this->pdo->prepare("SELECT id FROM process_definitions WHERE code = ?");
$stmt->execute([$action['process_name']]); $stmt->execute([$action['process_code']]);
$processDefinitionId = $stmt->fetchColumn(); $processDefinitionId = $stmt->fetchColumn();
if ($processDefinitionId) { if ($processDefinitionId) {
$this->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId); $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]);
}
} }

View File

@ -7,28 +7,25 @@ session_start();
// Security check // Security check
if (!isset($_SESSION['user_id'])) { if (!isset($_SESSION['user_id'])) {
throw new WorkflowNotAllowedException('Brak autoryzacji'); http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
} }
$person_id = $_GET['person_id'] ?? null; $person_id = $_GET['person_id'] ?? null;
$process_definition_id = $_GET['process_id'] ?? null; $process_definition_id = $_GET['process_id'] ?? null;
if (!$person_id || !$process_definition_id) { if (!$person_id || !$process_definition_id) {
throw new WorkflowRuleFailedException('Brakujące parametry'); http_response_code(400);
echo json_encode(['error' => 'Missing person_id or process_id']);
exit;
} }
$userId = $_SESSION['user_id']; $userId = $_SESSION['user_id'];
$engine = new WorkflowEngine(); $engine = new WorkflowEngine();
$pdo = db(); $pdo = db();
// 1. Get or create instance // Fetch Person and Process Definition details first
$instance = $engine->getOrCreateInstanceByDefId($person_id, $process_definition_id, $userId);
if (!$instance) {
throw new WorkflowNotFoundException("Nie można pobrać lub utworzyć instancji procesu.");
}
$instanceId = $instance['id'];
// 2. Fetch all related data
$stmt_person = $pdo->prepare("SELECT first_name, last_name FROM people WHERE id = ?"); $stmt_person = $pdo->prepare("SELECT first_name, last_name FROM people WHERE id = ?");
$stmt_person->execute([$person_id]); $stmt_person->execute([$person_id]);
$person = $stmt_person->fetch(); $person = $stmt_person->fetch();
@ -36,24 +33,37 @@ $person = $stmt_person->fetch();
$stmt_process = $pdo->prepare("SELECT * FROM process_definitions WHERE id = ?"); $stmt_process = $pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
$stmt_process->execute([$process_definition_id]); $stmt_process->execute([$process_definition_id]);
$process = $stmt_process->fetch(); $process = $stmt_process->fetch();
$definition = $process && $process['definition_json'] ? json_decode($process['definition_json'], true) : null;
$isChecklist = ($definition && isset($definition['type']) && $definition['type'] === 'checklist');
$events = $engine->getEvents($instanceId); if (!$person || !$process) {
http_response_code(404);
echo "<p class='text-danger'>Could not find person or process.</p>";
exit;
}
// Try to find an existing instance
$instance = $engine->getInstanceByDefId($person_id, $process_definition_id);
?> ?>
<!-- Title for the modal, to be grabbed by JS --> <!-- Title for the modal, to be grabbed by JS -->
<div id="instance-modal-title" class="d-none"> <div id="instance-modal-title" class="d-none">
<?= htmlspecialchars($person['first_name'] . ' ' . $person['last_name']) ?> - <?= htmlspecialchars($process['name']) ?> <?= htmlspecialchars($person['first_name']." ".$person['last_name']) ?> - <?= htmlspecialchars($process['name']) ?>
</div> </div>
<?php if ($isChecklist): ?> <?php if ($instance): // INSTANCE EXISTS ?>
<?php
$instanceId = $instance['id'];
$definition = $process['definition_json'] ? json_decode($process['definition_json'], true) : null;
$isChecklist = ($definition && isset($definition['type']) && $definition['type'] === 'checklist');
$events = $engine->getEvents($instanceId);
?>
<?php if ($isChecklist): ?>
<?php <?php
$tasks = $definition['tasks'] ?? []; $tasks = $definition['tasks'] ?? [];
$instanceData = $instance && $instance['data_json'] ? json_decode($instance['data_json'], true) : []; $instanceData = $instance['data_json'] ? json_decode($instance['data_json'], true) : [];
?> ?>
<div class="checklist-modal-container"> <div class="checklist-modal-container" data-instance-id="<?= $instanceId ?>">
<h5>Zadania do wykonania</h5> <h5>Zadania do wykonania</h5>
<div class="checklist-container"> <div class="checklist-container">
<?php foreach ($tasks as $task): <?php foreach ($tasks as $task):
@ -69,8 +79,7 @@ $events = $engine->getEvents($instanceId);
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
</div> </div>
<?php else: ?>
<?php else: ?>
<?php <?php
$currentNodeId = $instance['current_node_id']; $currentNodeId = $instance['current_node_id'];
$all_nodes = $engine->getProcessDefinitionNodes($process_definition_id); $all_nodes = $engine->getProcessDefinitionNodes($process_definition_id);
@ -79,7 +88,7 @@ $events = $engine->getEvents($instanceId);
$available_target_node_ids = array_map(function($t) { return $t['to']; }, $availableTransitions); $available_target_node_ids = array_map(function($t) { return $t['to']; }, $availableTransitions);
$available_transitions_map = []; $available_transitions_map = [];
foreach ($availableTransitions as $t) { foreach ($availableTransitions as $t) {
$available_transitions_map[$t['to']] = $t; $available_transitions_map[$t['id']] = $t;
} }
$visited_nodes = []; $visited_nodes = [];
@ -92,15 +101,12 @@ $events = $engine->getEvents($instanceId);
<div class="process-steps-container"> <div class="process-steps-container">
<h5>Kroki procesu</h5> <h5>Kroki procesu</h5>
<ul class="list-group"> <ul class="list-group">
<?php foreach ($all_nodes as $nodeId => $node): ?> <?php foreach ($all_nodes as $nodeId => $node):
<?php
$is_current = ($currentNodeId === $nodeId); $is_current = ($currentNodeId === $nodeId);
$is_completed = isset($visited_nodes[$nodeId]) && !$is_current; $is_completed = isset($visited_nodes[$nodeId]) && !$is_current;
$is_available = in_array($nodeId, $available_target_node_ids);
$status_icon = ''; $status_icon = '';
$li_class = ''; $li_class = '';
$button = '';
if ($is_current) { if ($is_current) {
$li_class = 'list-group-item-primary'; $li_class = 'list-group-item-primary';
@ -110,18 +116,7 @@ $events = $engine->getEvents($instanceId);
$status_icon = '<i class="bi bi-check-circle-fill text-success me-2"></i>'; $status_icon = '<i class="bi bi-check-circle-fill text-success me-2"></i>';
} else { } else {
$li_class = 'text-muted'; $li_class = 'text-muted';
$status_icon = '<i class="bi bi-lock-fill me-2"></i>'; $status_icon = '<i class="bi bi-circle me-2"></i>';
}
if ($is_available) {
$transition = $available_transitions_map[$nodeId];
$button = <<<HTML
<button class="btn btn-sm btn-primary apply-transition-btn"
data-instance-id="{$instanceId}"
data-transition-id="{$transition['id']}">
{$transition['name']}
</button>
HTML;
} }
?> ?>
<li class="list-group-item d-flex justify-content-between align-items-center <?= $li_class ?>"> <li class="list-group-item d-flex justify-content-between align-items-center <?= $li_class ?>">
@ -129,26 +124,39 @@ HTML;
<?= $status_icon ?> <?= $status_icon ?>
<strong><?= htmlspecialchars($node['name']) ?></strong> <strong><?= htmlspecialchars($node['name']) ?></strong>
</div> </div>
<?= $button ?>
</li> </li>
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
<div class="mt-3">
<h5>Available Actions</h5>
<?php if (empty($availableTransitions)): ?>
<p>No actions available.</p>
<?php else: ?>
<?php foreach ($availableTransitions as $transition): ?>
<button class="btn btn-sm btn-primary apply-transition-btn"
data-instance-id="<?= $instanceId ?>"
data-transition-id="<?= $transition['id'] ?>">
<?= htmlspecialchars($transition['name']) ?>
</button>
<?php endforeach; ?>
<?php endif; ?>
</div> </div>
<?php endif; ?> </div>
<?php endif; ?>
<hr> <hr>
<div class="add-note-container"> <div class="add-note-container">
<h5>Dodaj notatkę</h5> <h5>Dodaj notatkę</h5>
<div class="mb-3"> <div class="mb-3">
<textarea id="noteMessage" class="form-control" rows="2" placeholder="Wpisz treść notatki..."></textarea> <textarea id="noteMessage" class="form-control" rows="2" placeholder="Wpisz treść notatki..."></textarea>
</div> </div>
<button id="addNoteBtn" class="btn btn-secondary" data-instance-id="<?= $instanceId ?>">Dodaj notatkę</button> <button id="addNoteBtn" class="btn btn-secondary" data-instance-id="<?= $instanceId ?>">Dodaj notatkę</button>
</div> </div>
<hr> <hr>
<div class="history-container"> <div class="history-container">
<h5>Historia</h5> <h5>Historia</h5>
<?php if (empty($events)): ?> <?php if (empty($events)): ?>
<p>Brak zdarzeń.</p> <p>Brak zdarzeń.</p>
@ -169,7 +177,28 @@ HTML;
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php else: // NO INSTANCE EXISTS ?>
<?php
$eligibility = $engine->checkEligibility($person_id, $process_definition_id);
?>
<div class="text-center">
<?php if ($eligibility['is_eligible']): ?>
<h4>Process Not Started</h4>
<p>This process has not been started for this person.</p>
<button id="startProcessBtn" class="btn btn-primary" data-person-id="<?= $person_id ?>" data-process-id="<?= $process_definition_id ?>">
Start Process
</button>
<?php else: ?>
<h4>Not Eligible</h4>
<p>This person is not eligible to start this process.</p>
<ul class="list-group list-group-flush text-start">
<?php foreach ($eligibility['reasons'] as $reason): ?>
<li class="list-group-item list-group-item-danger"><?= htmlspecialchars($reason) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<?php endif; ?>

View File

@ -2,48 +2,96 @@
require_once 'db/config.php'; require_once 'db/config.php';
require_once 'lib/ErrorHandler.php'; require_once 'lib/ErrorHandler.php';
register_error_handler();
session_start(); session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') { function validate_definition_json($json) {
if (empty($json)) {
return; // No validation for empty json
}
$data = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
http_response_code(422);
throw new WorkflowRuleFailedException('Invalid JSON format in definition.');
}
$allowed_statuses = ['none', 'negative', 'in_progress', 'positive'];
if (isset($data['nodes'])) {
foreach ($data['nodes'] as $node) {
if (isset($node['ui_hints']['status']) && !in_array($node['ui_hints']['status'], $allowed_statuses)) {
http_response_code(422);
throw new WorkflowRuleFailedException('Invalid status in ui_hints. Allowed values are: ' . implode(', ', $allowed_statuses));
}
}
}
if (isset($data['transitions'])) {
foreach ($data['transitions'] as $transition) {
if (isset($transition['actions'])) {
foreach ($transition['actions'] as $action) {
if ($action['type'] === 'start_process' && isset($action['process_name'])) {
http_response_code(422);
throw new WorkflowRuleFailedException('Use process_code instead of process_name in transition actions.');
}
}
}
}
}
if (isset($data['eligibility_rules'])) {
foreach ($data['eligibility_rules'] as $rule) {
if ($rule['type'] === 'process_completed' && isset($rule['process_name'])) {
http_response_code(422);
throw new WorkflowRuleFailedException('Use process_code instead of process_name in eligibility_rules.');
}
}
}
}
try {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$processId = $_POST['process_id'] ?? null; $processId = $_POST['process_id'] ?? null;
$name = $_POST['name'] ?? ''; $name = $_POST['name'] ?? '';
$definition_json = $_POST['definition_json'] ?? ''; $definition_json = $_POST['definition_json'] ?? '';
validate_definition_json($definition_json);
// Generate a simple code from the name
$code = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $name)));
if (empty($name)) { if (empty($name)) {
throw new WorkflowRuleFailedException('Process name is required.'); throw new WorkflowRuleFailedException('Process name is required.');
} }
// Validate JSON
if (!empty($definition_json)) {
json_decode($definition_json);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new WorkflowRuleFailedException('Invalid JSON format in definition.');
}
}
$pdo = db(); $pdo = db();
if (empty($processId)) { if (empty($processId)) {
// Create new process // Create new process
$sql = 'INSERT INTO process_definitions (name, definition_json, is_active) VALUES (?, ?, 1)'; $sql = 'INSERT INTO process_definitions (name, code, definition_json, is_active) VALUES (?, ?, ?, 1)';
$params = [$name, $definition_json, 1]; $params = [$name, $code, $definition_json];
$message = 'Process created successfully.'; $message = 'Process created successfully.';
} else { } else {
// Update existing process // Update existing process
$sql = 'UPDATE process_definitions SET name = ?, definition_json = ? WHERE id = ?'; $is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : 0;
$params = [$name, $definition_json, $processId]; $sql = 'UPDATE process_definitions SET name = ?, code = ?, definition_json = ?, is_active = ? WHERE id = ?';
$params = [$name, $code, $definition_json, $is_active, $processId];
$message = 'Process updated successfully.'; $message = 'Process updated successfully.';
} }
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute($params); $stmt->execute($params);
if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) { if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
header('Content-Type: application/json'); header('Content-Type: application/json');
echo json_encode(['message' => $message]); echo json_encode(['message' => $message]);
} else { } else {
$_SESSION['success_message'] = $message; $_SESSION['success_message'] = $message;
header('Location: process_definitions.php'); header('Location: process_definitions.php');
}
exit(); exit();
}
}
} catch (WorkflowRuleFailedException $e) {
header('Content-Type: application/json');
echo json_encode(['error' => $e->getMessage()]);
} }

View File

@ -206,36 +206,50 @@ $status_colors = [
$instance = $instances[$person['id']][$process['id']] ?? null; $instance = $instances[$person['id']][$process['id']] ?? null;
$lastActivity = $instance && isset($instance['last_activity_at']) ? date('d/m/y', strtotime($instance['last_activity_at'])) : ''; $lastActivity = $instance && isset($instance['last_activity_at']) ? date('d/m/y', strtotime($instance['last_activity_at'])) : '';
$is_eligible = $instance ? ($instance['is_eligible'] ?? true) : false; // Correctly check eligibility using the WorkflowEngine
$eligibilityCheck = $workflowEngine->checkEligibility($person['id'], $process['id']);
$is_eligible = $eligibilityCheck['is_eligible'];
if ($instance && isset($instance['id'])) { // Existing instance $is_active = $process['is_active'] ?? true;
$modal_target = ''; // Default to not clickable
$is_clickable = false;
if (!$is_active) {
$status = 'inactive';
$color = $status_colors['inactive'];
$title = 'Process inactive';
} elseif ($instance && isset($instance['id'])) { // Existing instance
$status = $instance['computed_status']; $status = $instance['computed_status'];
$color = $status_colors[$status] ?? $status_colors['inactive']; $color = $status_colors[$status] ?? $status_colors['inactive'];
$title = ucfirst($status); $title = !empty($instance['computed_reason']) ? $instance['computed_reason'] : ucfirst($status);
if (!empty($instance['computed_reason'])) {
$title = $instance['computed_reason'];
}
$modal_target = '#instanceModal'; $modal_target = '#instanceModal';
$is_clickable = true;
} else { // No instance } else { // No instance
if ($is_eligible) { if ($is_eligible) {
$status = 'not_started'; $status = 'not_started';
$color = $status_colors[$status]; $color = $status_colors[$status];
$title = 'Not Started'; $title = 'Not Started';
$modal_target = '#bulkInitModal'; $modal_target = '#instanceModal';
$is_clickable = true;
} else { } else {
$status = 'ineligible'; $status = 'ineligible';
$color = '#e9ecef'; // A light gray color $color = '#e9ecef'; // A light gray color for the circle
$title = 'Not eligible'; $title = implode(' ', $eligibilityCheck['reasons']); // Use the reason from the engine
$modal_target = ''; // Prevent modal $modal_target = '#instanceModal'; // Still open the modal to show details
$is_clickable = true;
} }
} }
?> ?>
<td class="text-center align-middle" <td class="text-center align-middle"
style="cursor: <?= $modal_target ? 'pointer' : 'not-allowed' ?>;" <?php if ($is_clickable): ?>
<?= $modal_target ? 'data-bs-toggle="modal"' : '' ?> style="cursor: pointer;"
data-bs-toggle="modal"
data-bs-target="<?= $modal_target ?>" data-bs-target="<?= $modal_target ?>"
data-person-id="<?= $person['id'] ?>" data-person-id="<?= $person['id'] ?>"
data-process-id="<?= $process['id'] ?>" data-process-id="<?= $process['id'] ?>"
<?php else: ?>
style="cursor: not-allowed;"
<?php endif; ?>
title="<?= htmlspecialchars($title) ?>"> title="<?= htmlspecialchars($title) ?>">
<span style="height: 20px; width: 20px; background-color: <?= $color ?>; border-radius: 50%; display: inline-block;"></span> <span style="height: 20px; width: 20px; background-color: <?= $color ?>; border-radius: 50%; display: inline-block;"></span>
<small class="text-muted d-block mt-1"><?= $lastActivity ?></small> <small class="text-muted d-block mt-1"><?= $lastActivity ?></small>