Wersja po posprzątaniu kodu
This commit is contained in:
parent
524c7007ab
commit
a703aeb1e2
@ -12,14 +12,29 @@ class WorkflowEngine {
|
||||
|
||||
public function getDashboardMatrix(): array {
|
||||
// Get all people (potential assignees)
|
||||
$stmt_people = $this->pdo->prepare("SELECT id, firstName, lastName, companyName, role, email, phone FROM people ORDER BY lastName, firstName");
|
||||
$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
|
||||
$stmt_defs = $this->pdo->prepare("SELECT id, name FROM process_definitions ORDER BY name");
|
||||
// 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 = $stmt_defs->fetchAll(PDO::FETCH_ASSOC);
|
||||
$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']
|
||||
];
|
||||
$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");
|
||||
@ -28,21 +43,94 @@ class WorkflowEngine {
|
||||
|
||||
$instances = [];
|
||||
foreach ($instances_data as $instance) {
|
||||
$instances[$instance['person_id']][$instance['process_definition_id']] = $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;
|
||||
}
|
||||
|
||||
foreach ($people as $person) {
|
||||
foreach ($definitions as $def) {
|
||||
if (!isset($instances[$person['id']][$def['id']])) {
|
||||
$is_eligible = true;
|
||||
try {
|
||||
$process_definition = $process_definitions_raw[array_search($def['id'], array_column($process_definitions_raw, 'id'))];
|
||||
$this->checkEligibility($person['id'], $process_definition);
|
||||
} catch (WorkflowNotAllowedException $e) {
|
||||
$is_eligible = false;
|
||||
}
|
||||
$instances[$person['id']][$def['id']] = ['is_eligible' => $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' => $process_definitions,
|
||||
'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 {
|
||||
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 active = 1");
|
||||
$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);
|
||||
|
||||
@ -53,12 +141,12 @@ class WorkflowEngine {
|
||||
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$definition) {
|
||||
throw new Exception("Process definition with code or id '$processCode' not found.");
|
||||
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 Exception("Process definition with code '$processCode' not found or not a 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
|
||||
@ -67,14 +155,14 @@ class WorkflowEngine {
|
||||
} 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 Exception("Process definition is missing 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, lastActivityAt) VALUES (?, ?, ?, 'in_progress', NOW())"
|
||||
"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();
|
||||
@ -86,8 +174,7 @@ class WorkflowEngine {
|
||||
return (int)$instanceId;
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
error_log("Error in startProcess: " . $e->getMessage());
|
||||
return null;
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,19 +202,18 @@ class WorkflowEngine {
|
||||
];
|
||||
}
|
||||
|
||||
public function applyTransition(int $instanceId, string $transitionId, array $inputPayload, int $userId): bool {
|
||||
public function applyTransition(int $instanceId, string $transitionId, array $inputPayload, int $userId): array {
|
||||
$this->pdo->beginTransaction();
|
||||
try {
|
||||
$state = $this->getProcessState($instanceId);
|
||||
if (!$state) {
|
||||
throw new Exception("Process instance not found.");
|
||||
throw new WorkflowNotFoundException("Process instance not found.");
|
||||
}
|
||||
|
||||
$instance = $state['instance'];
|
||||
$definition = $state['definition'];
|
||||
$currentNodeId = $instance['current_node_id'];
|
||||
|
||||
// Find the transition from the definition
|
||||
$transition = null;
|
||||
foreach ($definition['transitions'] as $t) {
|
||||
if ($t['from'] === $currentNodeId && $t['id'] === $transitionId) {
|
||||
@ -137,7 +223,7 @@ class WorkflowEngine {
|
||||
}
|
||||
|
||||
if (!$transition) {
|
||||
throw new Exception("Transition not found or not allowed from the current node.");
|
||||
throw new WorkflowNotAllowedException("Transition not found or not allowed from the current node.");
|
||||
}
|
||||
|
||||
// TODO: Add rule validation here
|
||||
@ -145,37 +231,53 @@ class WorkflowEngine {
|
||||
$newNodeId = $transition['to'];
|
||||
$newNodeInfo = $definition['nodes'][$newNodeId] ?? null;
|
||||
|
||||
// Update instance
|
||||
$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 = ?, lastActivityAt = NOW() WHERE id = ?"
|
||||
"UPDATE process_instances SET current_node_id = ?, current_status = ?, current_reason = ?, suggested_next_step = ?, last_activity_at = NOW() WHERE id = ?"
|
||||
);
|
||||
$stmt_update->execute([
|
||||
$newNodeId,
|
||||
$newNodeInfo['ui_hints']['status'] ?? 'in_progress',
|
||||
$newNodeInfo['ui_hints']['reason'] ?? '',
|
||||
$newNodeInfo['ui_hints']['next_step'] ?? '',
|
||||
$newStatus,
|
||||
$newReason,
|
||||
$newNextStep,
|
||||
$instanceId
|
||||
]);
|
||||
|
||||
// Add event
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->pdo->commit();
|
||||
return true;
|
||||
|
||||
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();
|
||||
error_log("Error in applyTransition: " . $e->getMessage());
|
||||
return false;
|
||||
// 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) {
|
||||
// Even if the instance isn't found, we shouldn't crash. Log and return false.
|
||||
error_log("addNote failed: Process instance $instanceId not found.");
|
||||
return false;
|
||||
throw new WorkflowNotFoundException("Process instance #$instanceId not found.");
|
||||
}
|
||||
$currentNodeId = $state['instance']['current_node_id'];
|
||||
$payload = ['message' => $message];
|
||||
@ -183,33 +285,145 @@ class WorkflowEngine {
|
||||
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 (processInstanceId, event_type, message, node_id, payload_json, createdBy, createdAt) VALUES (?, ?, ?, ?, ?, ?, NOW())"
|
||||
"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 {
|
||||
public function getOrCreateInstanceByDefId(int $personId, int $processDefinitionId, int $userId): ?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);
|
||||
|
||||
if (!$instance) {
|
||||
// For checklists, the process code is the process definition ID
|
||||
$stmt_def = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
|
||||
$stmt_def = $this->pdo->prepare("SELECT definition_json, code FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$processDefinitionId]);
|
||||
$definition_json = $stmt_def->fetchColumn();
|
||||
$definition = !empty($definition_json) ? json_decode($definition_json, true) : [];
|
||||
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($definition && isset($definition['type']) && $definition['type'] === 'checklist') {
|
||||
$processCode = (string) $processDefinitionId;
|
||||
} else {
|
||||
$stmt_def = $this->pdo->prepare("SELECT code FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$processDefinitionId]);
|
||||
$processCode = $stmt_def->fetchColumn();
|
||||
if (!$definition) {
|
||||
throw new WorkflowNotFoundException("Process definition #$processDefinitionId not found.");
|
||||
}
|
||||
|
||||
$this->checkEligibility($personId, $definition);
|
||||
|
||||
$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) {
|
||||
@ -219,11 +433,11 @@ class WorkflowEngine {
|
||||
}
|
||||
}
|
||||
|
||||
return $instance !== false ? $instance : null;
|
||||
return $instance ?: null;
|
||||
}
|
||||
|
||||
public function getEvents(int $instanceId): array {
|
||||
$stmt_events = $this->pdo->prepare("SELECT pe.*, p.email as user_email, p.firstName, p.lastName FROM process_events pe JOIN people p ON pe.createdBy = p.id WHERE pe.processInstanceId = ? ORDER BY pe.createdAt 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]);
|
||||
return $stmt_events->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
@ -261,4 +475,45 @@ class WorkflowEngine {
|
||||
$definition = !empty($json) ? json_decode($json, true) : [];
|
||||
return $definition['nodes'] ?? [];
|
||||
}
|
||||
|
||||
private function checkEligibility(int $personId, array $definition): void {
|
||||
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : [];
|
||||
if (empty($definition_json) || empty($definition_json['eligibility_rules'])) {
|
||||
return; // No rules to check
|
||||
}
|
||||
|
||||
foreach ($definition_json['eligibility_rules'] as $rule) {
|
||||
switch ($rule['type']) {
|
||||
case 'process_completed':
|
||||
$this->checkProcessCompletedRule($personId, $rule);
|
||||
break;
|
||||
// Add other rule types here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkProcessCompletedRule(int $personId, array $rule): void {
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT pi.id
|
||||
FROM process_instances pi
|
||||
JOIN process_definitions pd ON pi.process_definition_id = pd.id
|
||||
WHERE pi.person_id = ? AND pd.name = ? AND pi.current_status = ?
|
||||
");
|
||||
$stmt->execute([$personId, $rule['process_name'], $rule['expected_status']]);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$instance) {
|
||||
throw new WorkflowNotAllowedException("Not eligible to start this process. Prerequisite process '{$rule['process_name']}' not completed with status '{$rule['expected_status']}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private function executeStartProcessAction(int $personId, array $action, int $userId): void {
|
||||
$stmt = $this->pdo->prepare("SELECT id FROM process_definitions WHERE name = ?");
|
||||
$stmt->execute([$action['process_name']]);
|
||||
$processDefinitionId = $stmt->fetchColumn();
|
||||
|
||||
if ($processDefinitionId) {
|
||||
$this->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,31 +1,28 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Unauthorized');
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$instanceId = $_POST['instanceId'] ?? null;
|
||||
$eventType = $_POST['eventType'] ?? null;
|
||||
$description = $_POST['description'] ?? null;
|
||||
$instanceId = $_POST['instance_id'] ?? null;
|
||||
$message = $_POST['message'] ?? null;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (!$instanceId || !$eventType) {
|
||||
http_response_code(400);
|
||||
die('Missing parameters');
|
||||
if (!$instanceId || !$message) {
|
||||
throw new WorkflowRuleFailedException('Missing parameters: instance_id and message are required.');
|
||||
}
|
||||
|
||||
// Create the event
|
||||
$stmt = $pdo->prepare("INSERT INTO process_events (processInstanceId, eventType, description, createdBy) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$instanceId, $eventType, $description, $userId]);
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$workflowEngine->addNote((int)$instanceId, $message, (int)$userId);
|
||||
|
||||
// Update the last activity time for the instance
|
||||
$stmt_update = $pdo->prepare("UPDATE process_instances SET lastActivityAt = NOW() WHERE id = ?");
|
||||
$stmt_update->execute([$instanceId]);
|
||||
|
||||
// Redirect back to the dashboard
|
||||
header("Location: process_dashboard.php");
|
||||
if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['message' => 'Note added successfully.']);
|
||||
} else {
|
||||
header('Location: ' . $_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
exit;
|
||||
|
||||
@ -1,23 +1,18 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
register_error_handler();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$response = ['success' => false, 'message' => 'Wystąpił nieoczekiwany błąd.'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
$response['message'] = 'Invalid request method.';
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
throw new WorkflowNotAllowedException('Invalid request method.');
|
||||
}
|
||||
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
if (!isset($_POST['instanceId']) || !isset($_POST['transitionId'])) {
|
||||
http_response_code(400);
|
||||
$response['message'] = 'Błąd: Brak wymaganych parametrów.';
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
throw new WorkflowNotAllowedException('Błąd: Brak wymaganych parametrów.');
|
||||
}
|
||||
|
||||
$instanceId = (int)$_POST['instanceId'];
|
||||
@ -26,42 +21,21 @@ $userId = $_SESSION['user_id'] ?? null;
|
||||
$payload = $_POST['payload'] ?? [];
|
||||
|
||||
if (!$userId) {
|
||||
http_response_code(401);
|
||||
$response['message'] = 'Błąd: Sesja wygasła.';
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
throw new WorkflowNotAllowedException('Błąd: Sesja wygasła.', [], 401);
|
||||
}
|
||||
|
||||
try {
|
||||
$engine = new WorkflowEngine();
|
||||
$success = false;
|
||||
$engine = new WorkflowEngine();
|
||||
|
||||
if ($transitionId === 'note') {
|
||||
// Special case: Just add a note, don't change state.
|
||||
$message = $payload['message'] ?? '';
|
||||
if (!empty($message)) {
|
||||
$success = $engine->addNote($instanceId, $message, $userId);
|
||||
if ($success) {
|
||||
$response['message'] = 'Notatka została dodana.';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Standard transition logic
|
||||
$success = $engine->applyTransition($instanceId, $transitionId, $payload, $userId);
|
||||
if ($success) {
|
||||
$response['message'] = 'Akcja została wykonana pomyślnie.';
|
||||
}
|
||||
if ($transitionId === 'note') {
|
||||
$message = $payload['message'] ?? '';
|
||||
if (empty($message)) {
|
||||
throw new WorkflowNotAllowedException('Treść notatki nie może być pusta.');
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
$response['success'] = true;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error applying transition: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
$response['message'] = 'Wystąpił krytyczny błąd: ' . $e->getMessage();
|
||||
$engine->addNote($instanceId, $message, $userId);
|
||||
$response = ['success' => true, 'message' => 'Notatka została dodana.'];
|
||||
} else {
|
||||
$result = $engine->applyTransition($instanceId, $transitionId, $payload, $userId);
|
||||
$response = ['success' => true, 'message' => 'Akcja została wykonana pomyślnie.', 'data' => $result];
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
@ -1,53 +1,50 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Unauthorized');
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$person_ids = json_decode($_POST['person_ids'] ?? '[]');
|
||||
$process_id = $_POST['process_id'] ?? null;
|
||||
$message = $_POST['description'] ?? null; // The form sends 'description'
|
||||
$message = $_POST['description'] ?? null;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (empty($person_ids) || !$process_id || !$message) {
|
||||
http_response_code(400);
|
||||
die('Missing parameters');
|
||||
throw new WorkflowRuleFailedException('Missing parameters: person_ids, process_id, and description are required.');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$placeholders = implode(',', array_fill(0, count($person_ids), '?'));
|
||||
|
||||
// Get all relevant instance IDs
|
||||
$stmt = $pdo->prepare("SELECT id FROM process_instances WHERE processDefinitionId = ? AND personId IN ($placeholders)");
|
||||
$stmt = $pdo->prepare("SELECT id FROM process_instances WHERE process_definition_id = ? AND person_id IN ($placeholders)");
|
||||
$params = array_merge([$process_id], $person_ids);
|
||||
$stmt->execute($params);
|
||||
$instance_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (!empty($instance_ids)) {
|
||||
$instance_placeholders = implode(',', array_fill(0, count($instance_ids), '?'));
|
||||
|
||||
// Update last activity
|
||||
$stmt_update = $pdo->prepare("UPDATE process_instances SET lastActivityAt = NOW() WHERE id IN ($instance_placeholders)");
|
||||
$stmt_update->execute($instance_ids);
|
||||
|
||||
// Bulk insert events
|
||||
$event_sql = "INSERT INTO process_events (processInstanceId, event_type, message, createdById) VALUES ";
|
||||
$event_rows = [];
|
||||
$event_params = [];
|
||||
foreach($instance_ids as $instance_id) {
|
||||
$event_rows[] = "(?, 'note', ?, ?)";
|
||||
$event_params[] = $instance_id;
|
||||
$event_params[] = $message;
|
||||
$event_params[] = $userId;
|
||||
}
|
||||
$event_sql .= implode(', ', $event_rows);
|
||||
$stmt_event = $pdo->prepare($event_sql);
|
||||
$stmt_event->execute($event_params);
|
||||
if (empty($instance_ids)) {
|
||||
$_SESSION['flash_message'] = "No instances found for the selected people and process.";
|
||||
header('Location: ' . $_SERVER['HTTP_REFERER']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$_SESSION['flash_message'] = "Bulk event addition completed.";
|
||||
header('Location: process_dashboard.php');
|
||||
$notes = [];
|
||||
foreach ($instance_ids as $instance_id) {
|
||||
$notes[] = [
|
||||
'instance_id' => $instance_id,
|
||||
'message' => $message,
|
||||
'user_id' => $userId
|
||||
];
|
||||
}
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$results = $workflowEngine->bulkAddNotes($notes);
|
||||
|
||||
$_SESSION['flash_message'] = "Bulk note addition completed.";
|
||||
$_SESSION['bulk_results'] = $results;
|
||||
|
||||
header('Location: ' . $_SERVER['HTTP_REFERER']);
|
||||
exit;
|
||||
|
||||
@ -1,33 +1,56 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'lib/WorkflowExceptions.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Unauthorized');
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$person_ids = json_decode($_POST['person_ids'] ?? '[]');
|
||||
$userId = $_SESSION['user_id'];
|
||||
$personIds = $_POST['personIds'] ?? '[]';
|
||||
if (is_string($personIds)) {
|
||||
$personIds = json_decode($personIds, true);
|
||||
}
|
||||
$process_id = $_POST['process_id'] ?? null;
|
||||
|
||||
if (empty($person_ids) || !$process_id) {
|
||||
http_response_code(400);
|
||||
die('Missing parameters');
|
||||
if (empty($personIds) || !$process_id) {
|
||||
throw new WorkflowRuleFailedException('Missing parameters');
|
||||
}
|
||||
|
||||
$sql = "INSERT IGNORE INTO process_instances (personId, processDefinitionId, current_status) VALUES ";
|
||||
$rows = [];
|
||||
$params = [];
|
||||
foreach($person_ids as $person_id) {
|
||||
$rows[] = "(?, ?, 'none')";
|
||||
$params[] = $person_id;
|
||||
$params[] = $process_id;
|
||||
}
|
||||
$sql .= implode(', ', $rows);
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$engine = new WorkflowEngine();
|
||||
$results = [
|
||||
'success' => [],
|
||||
'failed' => [],
|
||||
];
|
||||
|
||||
$_SESSION['flash_message'] = "Bulk initialization completed.";
|
||||
header('Location: process_dashboard.php');
|
||||
exit;
|
||||
foreach ($personIds as $personId) {
|
||||
try {
|
||||
$instance = $engine->getOrCreateInstanceByDefId($personId, $process_id, $userId);
|
||||
if ($instance) {
|
||||
$results['success'][] = $personId;
|
||||
} else {
|
||||
$results['failed'][] = $personId;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$results['failed'][] = $personId;
|
||||
// Optionally log the error
|
||||
error_log("Failed to initialize process for person $personId: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$message = "Bulk initialization completed. Success: " . count($results['success']) . ", Failed: " . count($results['failed']);
|
||||
|
||||
if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'message' => $message,
|
||||
'results' => $results
|
||||
]);
|
||||
} else {
|
||||
$_SESSION['success_message'] = $message;
|
||||
header('Location: index.php');
|
||||
}
|
||||
exit();
|
||||
@ -1,54 +1,52 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Unauthorized');
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$person_ids = json_decode($_POST['person_ids'] ?? '[]');
|
||||
$process_id = $_POST['process_id'] ?? null;
|
||||
$status = $_POST['status'] ?? null;
|
||||
$reason = $_POST['reason'] ?? '';
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (empty($person_ids) || !$process_id || !$status) {
|
||||
http_response_code(400);
|
||||
die('Missing parameters');
|
||||
throw new WorkflowRuleFailedException('Missing parameters: person_ids, process_id, and status are required.');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$placeholders = implode(',', array_fill(0, count($person_ids), '?'));
|
||||
|
||||
// Get all relevant instance IDs
|
||||
$stmt = $pdo->prepare("SELECT id FROM process_instances WHERE processDefinitionId = ? AND personId IN ($placeholders)");
|
||||
$stmt = $pdo->prepare("SELECT id FROM process_instances WHERE process_definition_id = ? AND person_id IN ($placeholders)");
|
||||
$params = array_merge([$process_id], $person_ids);
|
||||
$stmt->execute($params);
|
||||
$instance_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (!empty($instance_ids)) {
|
||||
$instance_placeholders = implode(',', array_fill(0, count($instance_ids), '?'));
|
||||
|
||||
// Update statuses
|
||||
$stmt_update = $pdo->prepare("UPDATE process_instances SET current_status = ?, lastActivityAt = NOW() WHERE id IN ($instance_placeholders)");
|
||||
$stmt_update->execute(array_merge([$status], $instance_ids));
|
||||
|
||||
// Bulk insert events
|
||||
$event_sql = "INSERT INTO process_events (processInstanceId, event_type, message, createdById) VALUES ";
|
||||
$event_rows = [];
|
||||
$event_params = [];
|
||||
$message = "Status changed to $status";
|
||||
foreach($instance_ids as $instance_id) {
|
||||
$event_rows[] = "(?, 'status_change', ?, ?)";
|
||||
$event_params[] = $instance_id;
|
||||
$event_params[] = $message;
|
||||
$event_params[] = $userId;
|
||||
}
|
||||
$event_sql .= implode(', ', $event_rows);
|
||||
$stmt_event = $pdo->prepare($event_sql);
|
||||
$stmt_event->execute($event_params);
|
||||
if (empty($instance_ids)) {
|
||||
$_SESSION['flash_message'] = "No instances found for the selected people and process.";
|
||||
header('Location: ' . $_SERVER['HTTP_REFERER']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$statuses = [];
|
||||
foreach ($instance_ids as $instance_id) {
|
||||
$statuses[] = [
|
||||
'instance_id' => $instance_id,
|
||||
'status' => $status,
|
||||
'reason' => $reason,
|
||||
'user_id' => $userId
|
||||
];
|
||||
}
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$results = $workflowEngine->bulkManualStatus($statuses);
|
||||
|
||||
$_SESSION['flash_message'] = "Bulk status update completed.";
|
||||
header('Location: process_dashboard.php');
|
||||
$_SESSION['bulk_results'] = $results;
|
||||
|
||||
header('Location: ' . $_SERVER['HTTP_REFERER']);
|
||||
exit;
|
||||
|
||||
@ -3,11 +3,11 @@ require_once 'db/config.php';
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$firstName = $_POST['firstName'];
|
||||
$lastName = $_POST['lastName'];
|
||||
$first_name = $_POST['first_name'];
|
||||
$last_name = $_POST['last_name'];
|
||||
$email = $_POST['email'];
|
||||
$password = $_POST['password'];
|
||||
$companyName = $_POST['companyName'] ?? null;
|
||||
$company_name = $_POST['company_name'] ?? null;
|
||||
$phone = $_POST['phone'] ?? null;
|
||||
$role = $_POST['role'] ?? 'guest';
|
||||
$functions = isset($_POST['functions']) ? $_POST['functions'] : [];
|
||||
@ -35,9 +35,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Insert person details first
|
||||
$sql = 'INSERT INTO people (firstName, lastName, email, password, companyName, phone, role, bni_group_id, nip, industry, company_size_revenue, business_description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$sql = 'INSERT INTO people (first_name, last_name, email, password, company_name, phone, role, bni_group_id, nip, industry, company_size_revenue, business_description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$firstName, $lastName, $email, password_hash($password, PASSWORD_DEFAULT), $companyName, $phone, $role, $bni_group_id, $nip, $industry, $company_size_revenue, $business_description]);
|
||||
$stmt->execute([$first_name, $last_name, $email, password_hash($password, PASSWORD_DEFAULT), $company_name, $phone, $role, $bni_group_id, $nip, $industry, $company_size_revenue, $business_description]);
|
||||
$personId = $pdo->lastInsertId();
|
||||
|
||||
// Handle file uploads now that we have a personId
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
<?php
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'lib/WorkflowExceptions.php';
|
||||
|
||||
session_start();
|
||||
|
||||
// Security check
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Brak autoryzacji');
|
||||
throw new WorkflowNotAllowedException('Brak autoryzacji');
|
||||
}
|
||||
|
||||
$personId = $_GET['personId'] ?? null;
|
||||
$processDefinitionId = $_GET['processId'] ?? null;
|
||||
$person_id = $_GET['person_id'] ?? null;
|
||||
$process_definition_id = $_GET['process_id'] ?? null;
|
||||
|
||||
if (!$personId || !$processDefinitionId) {
|
||||
http_response_code(400);
|
||||
die('Brakujące parametry');
|
||||
if (!$person_id || !$process_definition_id) {
|
||||
throw new WorkflowRuleFailedException('Brakujące parametry');
|
||||
}
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
@ -21,20 +22,19 @@ $engine = new WorkflowEngine();
|
||||
$pdo = db();
|
||||
|
||||
// 1. Get or create instance
|
||||
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
|
||||
$instance = $engine->getOrCreateInstanceByDefId($person_id, $process_definition_id, $userId);
|
||||
if (!$instance) {
|
||||
http_response_code(500);
|
||||
die("Nie można pobrać lub utworzyć instancji procesu.");
|
||||
throw new WorkflowNotFoundException("Nie można pobrać lub utworzyć instancji procesu.");
|
||||
}
|
||||
$instanceId = $instance['id'];
|
||||
|
||||
// 2. Fetch all related data
|
||||
$stmt_person = $pdo->prepare("SELECT firstName, lastName FROM people WHERE id = ?");
|
||||
$stmt_person->execute([$personId]);
|
||||
$stmt_person = $pdo->prepare("SELECT first_name, last_name FROM people WHERE id = ?");
|
||||
$stmt_person->execute([$person_id]);
|
||||
$person = $stmt_person->fetch();
|
||||
|
||||
$stmt_process = $pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
|
||||
$stmt_process->execute([$processDefinitionId]);
|
||||
$stmt_process->execute([$process_definition_id]);
|
||||
$process = $stmt_process->fetch();
|
||||
$definition = $process && $process['definition_json'] ? json_decode($process['definition_json'], true) : null;
|
||||
$isChecklist = ($definition && isset($definition['type']) && $definition['type'] === 'checklist');
|
||||
@ -45,7 +45,7 @@ $events = $engine->getEvents($instanceId);
|
||||
|
||||
<!-- Title for the modal, to be grabbed by JS -->
|
||||
<div id="instance-modal-title" class="d-none">
|
||||
<?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?> - <?= htmlspecialchars($process['name']) ?>
|
||||
<?= htmlspecialchars($person['first_name'] . ' ' . $person['last_name']) ?> - <?= htmlspecialchars($process['name']) ?>
|
||||
</div>
|
||||
|
||||
<?php if ($isChecklist): ?>
|
||||
@ -73,7 +73,7 @@ $events = $engine->getEvents($instanceId);
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$currentNodeId = $instance['current_node_id'];
|
||||
$all_nodes = $engine->getProcessDefinitionNodes($processDefinitionId);
|
||||
$all_nodes = $engine->getProcessDefinitionNodes($process_definition_id);
|
||||
$availableTransitions = $engine->getAvailableTransitions($instanceId);
|
||||
|
||||
$available_target_node_ids = array_map(function($t) { return $t['to']; }, $availableTransitions);
|
||||
@ -164,7 +164,7 @@ HTML;
|
||||
echo '<p class="mb-1 text-muted fst-italic">' . htmlspecialchars($message) . '</p>';
|
||||
}
|
||||
?>
|
||||
<small class="text-muted">Przez <?= htmlspecialchars($event['firstName'] . ' ' . $event['lastName']) ?> dnia <?= date('d.m.Y, H:i', strtotime($event['created_at'])) ?></small>
|
||||
<small class="text-muted">Przez <?= htmlspecialchars($event['first_name'] . ' ' . $event['last_name']) ?> dnia <?= date('d.m.Y, H:i', strtotime($event['created_at'])) ?></small>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
@ -35,7 +35,7 @@ try {
|
||||
}
|
||||
|
||||
// 2. Get all instances for this process
|
||||
$stmt_instances = $pdo->prepare("SELECT * FROM process_instances WHERE processDefinitionId = ?");
|
||||
$stmt_instances = $pdo->prepare("SELECT * FROM process_instances WHERE process_definition_id = ?");
|
||||
$stmt_instances->execute([$process_id]);
|
||||
$instances = $stmt_instances->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
@ -45,29 +45,29 @@ try {
|
||||
$events = [];
|
||||
if (!empty($instance_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($instance_ids), '?'));
|
||||
$stmt_events = $pdo->prepare("SELECT * FROM process_events WHERE processInstanceId IN ($placeholders) ORDER BY createdAt, id");
|
||||
$stmt_events = $pdo->prepare("SELECT * FROM process_events WHERE process_instance_id IN ($placeholders) ORDER BY created_at, id");
|
||||
$stmt_events->execute($instance_ids);
|
||||
$all_events = $stmt_events->fetchAll(PDO::FETCH_ASSOC);
|
||||
// Group events by instance_id
|
||||
foreach ($all_events as $event) {
|
||||
$events[$event['processInstanceId']][] = $event;
|
||||
$events[$event['process_instance_id']][] = $event;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Get People details
|
||||
$people_ids = array_unique(array_column($instances, 'personId'));
|
||||
$people_ids = array_unique(array_column($instances, 'person_id'));
|
||||
$people = [];
|
||||
if (!empty($people_ids)) {
|
||||
$valid_people_ids = array_filter($people_ids, 'is_numeric');
|
||||
|
||||
if (!empty($valid_people_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($valid_people_ids), '?'));
|
||||
$stmt_people = $pdo->prepare("SELECT id, firstName, lastName FROM people WHERE id IN ($placeholders)");
|
||||
$stmt_people = $pdo->prepare("SELECT id, first_name, last_name FROM people WHERE id IN ($placeholders)");
|
||||
$stmt_people->execute(array_values($valid_people_ids));
|
||||
$people_results = $stmt_people->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($people_results as $person) {
|
||||
$people[$person['id']] = $person;
|
||||
$people[$person['id']]['name'] = trim($person['firstName'] . ' ' . $person['lastName']);
|
||||
$people[$person['id']]['name'] = trim($person['first_name'] . ' ' . $person['last_name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/lib/ErrorHandler.php';
|
||||
register_error_handler();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
|
||||
@ -19,12 +19,12 @@ $stmt_processes = $pdo->prepare("SELECT id FROM process_definitions WHERE is_act
|
||||
$stmt_processes->execute();
|
||||
$processes = $stmt_processes->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$insert_stmt = $pdo->prepare("INSERT IGNORE INTO process_instances (personId, processDefinitionId, current_status) VALUES (?, ?, 'none')");
|
||||
$insert_stmt = $pdo->prepare("INSERT IGNORE INTO process_instances (person_id, process_definition_id, current_status) VALUES (?, ?, 'none')");
|
||||
|
||||
$count = 0;
|
||||
foreach ($people as $personId) {
|
||||
foreach ($processes as $processId) {
|
||||
$insert_stmt->execute([$personId, $processId]);
|
||||
foreach ($people as $person_id) {
|
||||
foreach ($processes as $process_id) {
|
||||
$insert_stmt->execute([$person_id, $process_id]);
|
||||
if ($insert_stmt->rowCount() > 0) {
|
||||
$count++;
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'lib/WorkflowExceptions.php';
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
$_SESSION['error_message'] = "Authentication required.";
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
throw new WorkflowNotAllowedException('Authentication required.');
|
||||
}
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
@ -13,9 +13,7 @@ $personId = $_GET['personId'] ?? null;
|
||||
$processDefinitionId = $_GET['processId'] ?? null;
|
||||
|
||||
if (!$personId || !$processDefinitionId) {
|
||||
$_SESSION['error_message'] = "Missing parameters for process initialization.";
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
throw new WorkflowRuleFailedException('Missing parameters for process initialization.');
|
||||
}
|
||||
|
||||
$engine = new WorkflowEngine();
|
||||
|
||||
@ -1,49 +1,49 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$processId = $_POST['process_id'] ?? null;
|
||||
$name = $_POST['name'];
|
||||
$definition_json = $_POST['definition_json'];
|
||||
$name = $_POST['name'] ?? '';
|
||||
$definition_json = $_POST['definition_json'] ?? '';
|
||||
|
||||
if (empty($name)) {
|
||||
die('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) {
|
||||
die('Invalid JSON format in definition.');
|
||||
throw new WorkflowRuleFailedException('Invalid JSON format in definition.');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$pdo = db();
|
||||
|
||||
if (empty($processId)) {
|
||||
// Create new process
|
||||
$sql = 'INSERT INTO process_definitions (name, definition_json) VALUES (?, ?)';
|
||||
$params = [$name, $definition_json];
|
||||
$message = 'Process created successfully.';
|
||||
} else {
|
||||
// Update existing process
|
||||
$sql = 'UPDATE process_definitions SET name = ?, definition_json = ? WHERE id = ?';
|
||||
$params = [$name, $definition_json, $processId];
|
||||
$message = 'Process updated successfully.';
|
||||
}
|
||||
if (empty($processId)) {
|
||||
// Create new process
|
||||
$sql = 'INSERT INTO process_definitions (name, definition_json, is_active) VALUES (?, ?, 1)';
|
||||
$params = [$name, $definition_json, 1];
|
||||
$message = 'Process created successfully.';
|
||||
} else {
|
||||
// Update existing process
|
||||
$sql = 'UPDATE process_definitions SET name = ?, definition_json = ? WHERE id = ?';
|
||||
$params = [$name, $definition_json, $processId];
|
||||
$message = 'Process updated successfully.';
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
session_start();
|
||||
if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['message' => $message]);
|
||||
} else {
|
||||
$_SESSION['success_message'] = $message;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log('Save process definition failed: ' . $e->getMessage());
|
||||
die('Save process definition failed. Please check the logs.');
|
||||
header('Location: process_definitions.php');
|
||||
}
|
||||
|
||||
header('Location: process_definitions.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'lib/WorkflowExceptions.php';
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit();
|
||||
throw new WorkflowNotAllowedException('Authentication required.');
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$event_id = $_POST['event_id'] ?? null;
|
||||
$title = $_POST['title'] ?? '';
|
||||
@ -19,8 +19,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$group_ids = $_POST['group_ids'] ?? [];
|
||||
|
||||
if (empty($event_id) || empty($title) || empty($event_type_id) || !is_array($group_ids)) {
|
||||
header("Location: calendar.php?error=empty_fields");
|
||||
exit();
|
||||
throw new WorkflowRuleFailedException('Empty fields');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
@ -77,7 +76,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
error_log("Error updating event: " . $e->getMessage());
|
||||
header("Location: calendar.php?error=db_error");
|
||||
exit();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,30 +1,29 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Unauthorized');
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$instanceId = $_POST['instanceId'] ?? null;
|
||||
$instanceId = $_POST['instance_id'] ?? null;
|
||||
$status = $_POST['status'] ?? null;
|
||||
$reason = $_POST['reason'] ?? '';
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (!$instanceId || !$status) {
|
||||
http_response_code(400);
|
||||
die('Missing parameters');
|
||||
throw new WorkflowRuleFailedException('Missing parameters: instance_id and status are required.');
|
||||
}
|
||||
|
||||
// Update instance status and last activity time
|
||||
$stmt = $pdo->prepare("UPDATE process_instances SET status = ?, lastActivityAt = NOW() WHERE id = ?");
|
||||
$stmt->execute([$status, $instanceId]);
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$workflowEngine->applyManualStatus((int)$instanceId, $status, $reason, (int)$userId);
|
||||
|
||||
// Create a status change event
|
||||
$stmt_event = $pdo->prepare("INSERT INTO process_events (processInstanceId, eventType, description, createdBy) VALUES (?, 'status_change', ?, ?)");
|
||||
$stmt_event->execute([$instanceId, "Status changed to $status", $userId]);
|
||||
|
||||
// Redirect back to the dashboard
|
||||
header("Location: process_dashboard.php");
|
||||
if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['message' => 'Status updated successfully.']);
|
||||
} else {
|
||||
header('Location: index.php');
|
||||
}
|
||||
exit;
|
||||
|
||||
@ -4,10 +4,10 @@ session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$personId = $_POST['id'];
|
||||
$firstName = $_POST['firstName'];
|
||||
$lastName = $_POST['lastName'];
|
||||
$first_name = $_POST['first_name'];
|
||||
$last_name = $_POST['last_name'];
|
||||
$email = $_POST['email'];
|
||||
$companyName = $_POST['companyName'];
|
||||
$company_name = $_POST['company_name'];
|
||||
$phone = $_POST['phone'];
|
||||
$role = $_POST['role'] ?? 'guest';
|
||||
$functions = isset($_POST['functions']) ? $_POST['functions'] : [];
|
||||
@ -61,12 +61,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
// Prepare SQL for updating person details
|
||||
$sql_parts = [
|
||||
'firstName = ?', 'lastName = ?', 'email = ?', 'companyName = ?', 'phone = ?',
|
||||
'first_name = ?', 'last_name = ?', 'email = ?', 'company_name = ?', 'phone = ?',
|
||||
'role = ?', 'bni_group_id = ?', 'nip = ?', 'industry = ?', 'company_size_revenue = ?',
|
||||
'business_description = ?'
|
||||
];
|
||||
$params = [
|
||||
$firstName, $lastName, $email, $companyName, $phone, $role, $bni_group_id,
|
||||
$first_name, $last_name, $email, $company_name, $phone, $role, $bni_group_id,
|
||||
$nip, $industry, $company_size_revenue, $business_description
|
||||
];
|
||||
|
||||
|
||||
@ -1,81 +1,36 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
session_start();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||
exit;
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method Not Allowed']);
|
||||
exit;
|
||||
throw new WorkflowNotAllowedException('Method Not Allowed');
|
||||
}
|
||||
|
||||
$inputJSON = file_get_contents('php://input');
|
||||
$input = json_decode($inputJSON, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid JSON']);
|
||||
exit;
|
||||
throw new WorkflowRuleFailedException('Invalid JSON');
|
||||
}
|
||||
|
||||
$instanceId = $input['instance_id'] ?? null;
|
||||
$taskCode = $input['task_code'] ?? null;
|
||||
$isChecked = $input['is_checked'] ?? null;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (!$instanceId || !$taskCode || $isChecked === null) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing required parameters: instance_id, task_code, is_checked']);
|
||||
exit;
|
||||
throw new WorkflowRuleFailedException('Missing required parameters: instance_id, task_code, is_checked');
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Get current data_json
|
||||
$stmt = $pdo->prepare("SELECT data_json FROM process_instances WHERE id = ?");
|
||||
$stmt->execute([$instanceId]);
|
||||
$currentDataJson = $stmt->fetchColumn();
|
||||
|
||||
$data = $currentDataJson ? json_decode($currentDataJson, true) : [];
|
||||
|
||||
// Update the specific task status
|
||||
$data[$taskCode] = (bool)$isChecked;
|
||||
$newDataJson = json_encode($data);
|
||||
|
||||
// Save new data_json and update timestamp
|
||||
$stmt = $pdo->prepare("UPDATE process_instances SET data_json = ?, lastActivityAt = CURRENT_TIMESTAMP WHERE id = ?");
|
||||
$success = $stmt->execute([$newDataJson, $instanceId]);
|
||||
|
||||
if ($success) {
|
||||
// Calculate progress
|
||||
$stmt = $pdo->prepare("SELECT pd.definition_json FROM process_definitions pd JOIN process_instances pi ON pd.id = pi.process_definition_id WHERE pi.id = ?");
|
||||
$stmt->execute([$instanceId]);
|
||||
$definitionJson = $stmt->fetchColumn();
|
||||
$definition = json_decode($definitionJson, true);
|
||||
$totalTasks = count($definition['tasks'] ?? []);
|
||||
$completedTasks = count(array_filter($data));
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Status updated successfully.',
|
||||
'progress' => [
|
||||
'completed' => $completedTasks,
|
||||
'total' => $totalTasks
|
||||
],
|
||||
'lastActivityAt' => date('d/m/y')
|
||||
]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Failed to update status.']);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$result = $workflowEngine->updateChecklistStatus((int)$instanceId, $taskCode, (bool)$isChecked, (int)$userId);
|
||||
echo json_encode($result);
|
||||
exit;
|
||||
157
assets/js/process_definitions.js
Normal file
157
assets/js/process_definitions.js
Normal file
@ -0,0 +1,157 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const createProcessModal = document.getElementById('createProcessModal');
|
||||
const modalTitle = createProcessModal.querySelector('.modal-title');
|
||||
const form = createProcessModal.querySelector('#createProcessForm');
|
||||
const processIdInput = createProcessModal.querySelector('#processId');
|
||||
const processNameInput = createProcessModal.querySelector('#processName');
|
||||
const definitionJsonTextarea = createProcessModal.querySelector('#definitionJson');
|
||||
const eligibilityRulesJsonTextarea = createProcessModal.querySelector('#eligibilityRulesJson');
|
||||
|
||||
let definition = {};
|
||||
|
||||
function render() {
|
||||
const statusesList = document.getElementById('statuses-list');
|
||||
const transitionsList = document.getElementById('transitions-list');
|
||||
const initialStatusSelect = document.getElementById('initialStatus');
|
||||
const fromStatusSelect = document.getElementById('fromStatusSelect');
|
||||
const toStatusSelect = document.getElementById('toStatusSelect');
|
||||
|
||||
statusesList.innerHTML = '';
|
||||
transitionsList.innerHTML = '';
|
||||
initialStatusSelect.innerHTML = '';
|
||||
fromStatusSelect.innerHTML = '';
|
||||
toStatusSelect.innerHTML = '';
|
||||
|
||||
if (definition.nodes) {
|
||||
for (const nodeId in definition.nodes) {
|
||||
const node = definition.nodes[nodeId];
|
||||
const statusItem = document.createElement('div');
|
||||
statusItem.textContent = node.name;
|
||||
statusesList.appendChild(statusItem);
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = node.id;
|
||||
option.textContent = node.name;
|
||||
initialStatusSelect.appendChild(option.cloneNode(true));
|
||||
fromStatusSelect.appendChild(option.cloneNode(true));
|
||||
toStatusSelect.appendChild(option.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.start_node_id) {
|
||||
initialStatusSelect.value = definition.start_node_id;
|
||||
}
|
||||
|
||||
if (definition.transitions) {
|
||||
definition.transitions.forEach(transition => {
|
||||
const transitionItem = document.createElement('div');
|
||||
const fromNode = definition.nodes[transition.from] ? definition.nodes[transition.from].name : 'N/A';
|
||||
const toNode = definition.nodes[transition.to] ? definition.nodes[transition.to].name : 'N/A';
|
||||
let actions = '';
|
||||
if(transition.actions) {
|
||||
actions = ' - Actions: ' + JSON.stringify(transition.actions);
|
||||
}
|
||||
transitionItem.textContent = `${transition.name}: ${fromNode} => ${toNode}${actions}`;
|
||||
transitionsList.appendChild(transitionItem);
|
||||
});
|
||||
}
|
||||
|
||||
if (definition.eligibility_rules) {
|
||||
eligibilityRulesJsonTextarea.value = JSON.stringify(definition.eligibility_rules, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('addStatusBtn').addEventListener('click', function () {
|
||||
const newStatusInput = document.getElementById('newStatusInput');
|
||||
const newStatusName = newStatusInput.value.trim();
|
||||
if (newStatusName) {
|
||||
const newNodeId = (Object.keys(definition.nodes || {}).length + 1).toString();
|
||||
if (!definition.nodes) {
|
||||
definition.nodes = {};
|
||||
}
|
||||
definition.nodes[newNodeId] = { id: newNodeId, name: newStatusName };
|
||||
newStatusInput.value = '';
|
||||
render();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('addTransitionBtn').addEventListener('click', function () {
|
||||
const fromStatus = document.getElementById('fromStatusSelect').value;
|
||||
const toStatus = document.getElementById('toStatusSelect').value;
|
||||
const transitionActionJson = document.getElementById('transitionActionJson').value;
|
||||
|
||||
if (fromStatus && toStatus) {
|
||||
if (!definition.transitions) {
|
||||
definition.transitions = [];
|
||||
}
|
||||
const newTransition = {
|
||||
name: `Transition ${definition.transitions.length + 1}`,
|
||||
from: fromStatus,
|
||||
to: toStatus,
|
||||
};
|
||||
|
||||
if(transitionActionJson) {
|
||||
try {
|
||||
newTransition.actions = JSON.parse(transitionActionJson);
|
||||
} catch(e) {
|
||||
alert('Invalid JSON in transition actions');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
definition.transitions.push(newTransition);
|
||||
document.getElementById('transitionActionJson').value = '';
|
||||
render();
|
||||
}
|
||||
});
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
definition.start_node_id = document.getElementById('initialStatus').value;
|
||||
|
||||
try {
|
||||
const eligibilityRules = eligibilityRulesJsonTextarea.value;
|
||||
if(eligibilityRules) {
|
||||
definition.eligibility_rules = JSON.parse(eligibilityRules);
|
||||
} else {
|
||||
delete definition.eligibility_rules;
|
||||
}
|
||||
} catch(e) {
|
||||
alert('Invalid JSON in eligibility rules');
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
definitionJsonTextarea.value = JSON.stringify(definition, null, 2);
|
||||
});
|
||||
|
||||
createProcessModal.addEventListener('show.bs.modal', function (event) {
|
||||
const button = event.relatedTarget;
|
||||
const isEdit = button.classList.contains('edit-process-btn');
|
||||
|
||||
if (isEdit) {
|
||||
const processId = button.dataset.processId;
|
||||
const processName = button.dataset.processName;
|
||||
const processDefinition = button.dataset.processDefinition;
|
||||
|
||||
modalTitle.textContent = 'Edit Process';
|
||||
processIdInput.value = processId;
|
||||
processNameInput.value = processName;
|
||||
|
||||
try {
|
||||
definition = JSON.parse(processDefinition || '{}');
|
||||
} catch(e) {
|
||||
definition = {};
|
||||
}
|
||||
|
||||
} else {
|
||||
modalTitle.textContent = 'Create Process';
|
||||
processIdInput.value = '';
|
||||
processNameInput.value = '';
|
||||
definition = {};
|
||||
}
|
||||
|
||||
eligibilityRulesJsonTextarea.value = '';
|
||||
render();
|
||||
});
|
||||
});
|
||||
26
db/migrations/021_rename_camelcase_columns.php
Normal file
26
db/migrations/021_rename_camelcase_columns.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
echo "Starting migration 021: Rename camelCase columns to snake_case...\n";
|
||||
|
||||
// Process instances table
|
||||
$pdo->exec("ALTER TABLE `process_instances` CHANGE `lastActivityAt` `last_activity_at` TIMESTAMP NULL;");
|
||||
|
||||
echo "Columns in 'process_instances' table renamed.\n";
|
||||
|
||||
// Process events table
|
||||
$pdo->exec("ALTER TABLE `process_events` CHANGE `processInstanceId` `process_instance_id` INT(11) UNSIGNED NOT NULL;");
|
||||
$pdo->exec("ALTER TABLE `process_events` CHANGE `createdBy` `created_by` INT(11) UNSIGNED NOT NULL;");
|
||||
$pdo->exec("ALTER TABLE `process_events` CHANGE `createdAt` `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;");
|
||||
|
||||
echo "Columns in 'process_events' table renamed.\n";
|
||||
|
||||
echo "Migration 021 completed successfully.\n";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Migration 021 failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
12
db/migrations/022_add_is_active_to_process_definitions.php
Normal file
12
db/migrations/022_add_is_active_to_process_definitions.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "ALTER TABLE process_definitions ADD COLUMN is_active TINYINT(1) NOT NULL DEFAULT 1 AFTER definition_json";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration successful: is_active column added to process_definitions table.\n";
|
||||
} catch (PDOException $e) {
|
||||
die("Migration failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
30
db_setup.php
30
db_setup.php
@ -9,16 +9,16 @@ try {
|
||||
// 1. People table (unified table for users and contacts)
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `people` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`firstName` VARCHAR(255) NOT NULL,
|
||||
`lastName` VARCHAR(255) NOT NULL,
|
||||
`first_name` VARCHAR(255) NOT NULL,
|
||||
`last_name` VARCHAR(255) NOT NULL,
|
||||
`email` VARCHAR(255) NOT NULL UNIQUE,
|
||||
`password` VARCHAR(255) NULL,
|
||||
`companyName` VARCHAR(255) DEFAULT NULL,
|
||||
`company_name` VARCHAR(255) DEFAULT NULL,
|
||||
`phone` VARCHAR(50) DEFAULT NULL,
|
||||
`role` ENUM('admin', 'team_member', 'member', 'guest') NOT NULL DEFAULT 'guest',
|
||||
`is_user` BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
`active` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
echo "People table created or already exists.\n";
|
||||
|
||||
@ -28,7 +28,7 @@ try {
|
||||
if ($stmt->fetchColumn() === false) {
|
||||
$password = password_hash('password', PASSWORD_DEFAULT);
|
||||
$insert_stmt = $pdo->prepare(
|
||||
"INSERT INTO people (email, password, role, firstName, lastName, is_user, active) VALUES (?, ?, 'admin', 'Admin', 'User', TRUE, TRUE)"
|
||||
"INSERT INTO people (email, password, role, first_name, last_name, is_user, active) VALUES (?, ?, 'admin', 'Admin', 'User', TRUE, TRUE)"
|
||||
);
|
||||
$insert_stmt->execute(['admin@example.com', $password]);
|
||||
echo "Default admin user created. Email: admin@example.com, Password: password\n";
|
||||
@ -44,7 +44,7 @@ try {
|
||||
`is_active` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`start_node_id` VARCHAR(255),
|
||||
`definition_json` TEXT,
|
||||
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
echo "Process definitions table created or already exists.\n";
|
||||
|
||||
@ -74,7 +74,7 @@ try {
|
||||
`current_reason` TEXT,
|
||||
`suggested_next_step` TEXT,
|
||||
`data_json` TEXT,
|
||||
`lastActivityAt` TIMESTAMP NULL,
|
||||
`last_activity_at` TIMESTAMP NULL,
|
||||
FOREIGN KEY (person_id) REFERENCES people(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (process_definition_id) REFERENCES process_definitions(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY `person_process` (`person_id`, `process_definition_id`)
|
||||
@ -84,15 +84,15 @@ try {
|
||||
// 4. Process Events table (updated FK)
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `process_events` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`processInstanceId` INT(11) UNSIGNED NOT NULL,
|
||||
`process_instance_id` INT(11) UNSIGNED NOT NULL,
|
||||
`event_type` VARCHAR(50) NOT NULL,
|
||||
`message` TEXT,
|
||||
`node_id` VARCHAR(255),
|
||||
`payload_json` TEXT,
|
||||
`createdBy` INT(11) UNSIGNED NOT NULL,
|
||||
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (processInstanceId) REFERENCES process_instances(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (createdBy) REFERENCES people(id) ON DELETE CASCADE
|
||||
`created_by` INT(11) UNSIGNED NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (process_instance_id) REFERENCES process_instances(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (created_by) REFERENCES people(id) ON DELETE CASCADE
|
||||
)");
|
||||
echo "Process events table created or already exists.\n";
|
||||
|
||||
@ -106,8 +106,8 @@ try {
|
||||
if ($checkFk) {
|
||||
$pdo->exec("ALTER TABLE `process_instances` DROP FOREIGN KEY `{$checkFk['CONSTRAINT_NAME']}`;");
|
||||
}
|
||||
$pdo->exec("ALTER TABLE `process_instances` CHANGE `processId` `processDefinitionId` INT(11) UNSIGNED NOT NULL;");
|
||||
$pdo->exec("ALTER TABLE `process_instances` ADD FOREIGN KEY (`processDefinitionId`) REFERENCES `process_definitions`(`id`) ON DELETE CASCADE;");
|
||||
$pdo->exec("ALTER TABLE `process_instances` CHANGE `processId` `process_definition_id` INT(11) UNSIGNED NOT NULL;");
|
||||
$pdo->exec("ALTER TABLE `process_instances` ADD FOREIGN KEY (`process_definition_id`) REFERENCES `process_definitions`(`id`) ON DELETE CASCADE;");
|
||||
echo "Migrated process_instances: processId -> processDefinitionId.\n";
|
||||
}
|
||||
|
||||
@ -118,7 +118,7 @@ try {
|
||||
if ($checkFk) {
|
||||
$pdo->exec("ALTER TABLE `process_instances` DROP FOREIGN KEY `{$checkFk['CONSTRAINT_NAME']}`;");
|
||||
}
|
||||
$pdo->exec("ALTER TABLE `process_instances` CHANGE `contactId` `personId` INT(11) UNSIGNED NOT NULL;");
|
||||
$pdo->exec("ALTER TABLE `process_instances` CHANGE `contactId` `person_id` INT(11) UNSIGNED NOT NULL;");
|
||||
echo "Migrated process_instances: contactId -> personId.\n";
|
||||
}
|
||||
|
||||
|
||||
165
index.php
165
index.php
@ -1,35 +1,25 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Fetch people with their group names
|
||||
$stmt = $pdo->query("
|
||||
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.lastName, p.firstName
|
||||
");
|
||||
$people = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$matrix = $workflowEngine->getDashboardMatrix();
|
||||
|
||||
$people = $matrix['people'];
|
||||
$instances = $matrix['instances'];
|
||||
$all_functions = $matrix['all_functions'];
|
||||
$person_functions_map = $matrix['person_functions_map'];
|
||||
$bni_groups = $matrix['bni_groups'];
|
||||
|
||||
// Filter out specific process definitions
|
||||
$processes = array_filter($matrix['definitions'], function($process) {
|
||||
return !in_array($process['name'], ['Obsluga goscia', 'Przygotowanie spotkania grupy']);
|
||||
});
|
||||
|
||||
// Fetch process definitions
|
||||
$stmt = $pdo->prepare("SELECT * FROM process_definitions WHERE name NOT IN (?, ?)"
|
||||
. " ORDER BY name");
|
||||
$stmt->execute(['Obsluga goscia', 'Przygotowanie spotkania grupy']);
|
||||
$processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch all instances
|
||||
$stmt = $pdo->query("SELECT * FROM process_instances");
|
||||
$all_instances = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Organize instances by person and process
|
||||
$instances = [];
|
||||
foreach ($all_instances as $instance) {
|
||||
$instances[$instance['person_id']][$instance['process_definition_id']] = $instance;
|
||||
}
|
||||
|
||||
|
||||
$status_colors = [
|
||||
@ -43,23 +33,7 @@ $status_colors = [
|
||||
'inactive' => '#808080',
|
||||
];
|
||||
|
||||
$pdo = db();
|
||||
$stmt_functions = $pdo->query("SELECT * FROM functions ORDER BY display_order");
|
||||
$all_functions = $stmt_functions->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$functions_by_id = [];
|
||||
foreach ($all_functions as $function) {
|
||||
$functions_by_id[$function['id']] = $function['name'];
|
||||
}
|
||||
|
||||
$stmt_person_functions = $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 = $pdo->query("SELECT * FROM bni_groups ORDER BY name");
|
||||
$bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
?>
|
||||
@ -140,11 +114,14 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
<div class="table-responsive">
|
||||
<?php
|
||||
// Fetch ID for the meeting process to make headers clickable
|
||||
$stmt_meeting_process = $pdo->prepare("SELECT id FROM process_definitions WHERE name = ?");
|
||||
$stmt_meeting_process->execute(['Przygotowanie spotkania grupy']);
|
||||
$meeting_process = $stmt_meeting_process->fetch(PDO::FETCH_ASSOC);
|
||||
$meeting_process_id = $meeting_process ? $meeting_process['id'] : 'null';
|
||||
// Find the meeting process ID from the already-fetched definitions
|
||||
$meeting_process_id = 'null';
|
||||
foreach ($matrix['definitions'] as $definition) {
|
||||
if ($definition['name'] === 'Przygotowanie spotkania grupy') {
|
||||
$meeting_process_id = $definition['id'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead class="table-light">
|
||||
@ -181,9 +158,9 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
<td class="text-center align-middle"><input type="checkbox" class="person-checkbox" name="personIds[]" value="<?= $person['id'] ?>"></td>
|
||||
<td class="person-cell">
|
||||
<div class="person-main">
|
||||
<div class="person-name"><?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?></div>
|
||||
<div class="person-name"><?= htmlspecialchars($person['first_name'] . ' ' . $person['last_name']) ?></div>
|
||||
<div class="person-details">
|
||||
<span class="d-block"><?= htmlspecialchars($person['companyName']) ?></span>
|
||||
<span class="d-block"><?= htmlspecialchars($person['company_name']) ?></span>
|
||||
<span class="d-block"><?= htmlspecialchars($person['industry'] ?? '') ?></span>
|
||||
<span><?= htmlspecialchars(ucfirst($person['role'])) ?></span>
|
||||
<?php if ($person['role'] === 'member' && !empty($person['bni_group_name'])): ?>
|
||||
@ -201,7 +178,7 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
</div>
|
||||
<button class="btn btn-sm btn-secondary edit-btn" data-bs-toggle="modal" data-bs-target="#editPersonModal"
|
||||
data-person-id="<?= $person['id'] ?>"
|
||||
data-person-name="<?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?>">
|
||||
data-person-name="<?= htmlspecialchars($person['first_name'] . ' ' . $person['last_name']) ?>">
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
@ -227,51 +204,43 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
<?php // Inne Procesy Columns ?>
|
||||
<?php foreach ($inne_procesy_cols as $process):
|
||||
$instance = $instances[$person['id']][$process['id']] ?? null;
|
||||
$definition = isset($process['definition_json']) ? json_decode($process['definition_json'], true) : null;
|
||||
$isChecklist = ($definition && isset($definition['type']) && $definition['type'] === 'checklist');
|
||||
$lastActivity = $instance && $instance['lastActivityAt'] ? date('d/m/y', strtotime($instance['lastActivityAt'])) : '';
|
||||
$lastActivity = $instance && isset($instance['last_activity_at']) ? date('d/m/y', strtotime($instance['last_activity_at'])) : '';
|
||||
|
||||
$color = $status_colors['inactive'];
|
||||
$title = 'Inactive';
|
||||
$is_eligible = $instance ? ($instance['is_eligible'] ?? true) : false;
|
||||
|
||||
if ($isChecklist) {
|
||||
$status = 'inactive';
|
||||
if ($instance) {
|
||||
$tasks = $definition['tasks'] ?? [];
|
||||
$instanceData = $instance['data_json'] ? json_decode($instance['data_json'], true) : [];
|
||||
$totalTasks = count($tasks);
|
||||
$completedTasks = 0;
|
||||
foreach ($tasks as $task) {
|
||||
if (!empty($instanceData[$task['code']])) {
|
||||
$completedTasks++;
|
||||
}
|
||||
}
|
||||
|
||||
$title = "$completedTasks/$totalTasks completed";
|
||||
if ($totalTasks > 0 && $completedTasks === $totalTasks) {
|
||||
$status = 'completed';
|
||||
} elseif ($completedTasks > 0) {
|
||||
$status = 'in_progress';
|
||||
} else {
|
||||
$status = 'inactive'; // Initialized but no progress
|
||||
}
|
||||
}
|
||||
$color = $status_colors[$status] ?? $status_colors['inactive'];
|
||||
?>
|
||||
<td class="align-middle checklist-cell text-center" style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#instanceModal" data-person-id="<?= $person['id'] ?>" data-process-id="<?= $process['id'] ?>" data-instance-id="<?= $instance['id'] ?? '' ?>">
|
||||
<span title="<?= $title ?>" style="height: 20px; width: 20px; background-color: <?= $color ?>; border-radius: 50%; display: inline-block;"></span>
|
||||
<small class="text-muted d-block last-activity-date mt-1"><?= $lastActivity ?></small>
|
||||
</td>
|
||||
<?php } else {
|
||||
$status = $instance ? $instance['current_status'] : 'inactive';
|
||||
if ($instance && isset($instance['id'])) { // Existing instance
|
||||
$status = $instance['computed_status'];
|
||||
$color = $status_colors[$status] ?? $status_colors['inactive'];
|
||||
$title = ucfirst($status);
|
||||
?>
|
||||
<td class="text-center align-middle" style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#instanceModal" data-person-id="<?= $person['id'] ?>" data-process-id="<?= $process['id'] ?>">
|
||||
<span title="<?= $title ?>" 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>
|
||||
</td>
|
||||
<?php }
|
||||
if (!empty($instance['computed_reason'])) {
|
||||
$title = $instance['computed_reason'];
|
||||
}
|
||||
$modal_target = '#instanceModal';
|
||||
} else { // No instance
|
||||
if ($is_eligible) {
|
||||
$status = 'not_started';
|
||||
$color = $status_colors[$status];
|
||||
$title = 'Not Started';
|
||||
$modal_target = '#bulkInitModal';
|
||||
} else {
|
||||
$status = 'ineligible';
|
||||
$color = '#e9ecef'; // A light gray color
|
||||
$title = 'Not eligible';
|
||||
$modal_target = ''; // Prevent modal
|
||||
}
|
||||
}
|
||||
?>
|
||||
<td class="text-center align-middle"
|
||||
style="cursor: <?= $modal_target ? 'pointer' : 'not-allowed' ?>;"
|
||||
<?= $modal_target ? 'data-bs-toggle="modal"' : '' ?>
|
||||
data-bs-target="<?= $modal_target ?>"
|
||||
data-person-id="<?= $person['id'] ?>"
|
||||
data-process-id="<?= $process['id'] ?>"
|
||||
title="<?= htmlspecialchars($title) ?>">
|
||||
<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>
|
||||
</td>
|
||||
<?php
|
||||
endforeach; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@ -301,11 +270,11 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="editFirstName" class="form-label">Imię</label>
|
||||
<input type="text" class="form-control" id="editFirstName" name="firstName" required>
|
||||
<input type="text" class="form-control" id="editFirstName" name="first_name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editLastName" class="form-label">Nazwisko</label>
|
||||
<input type="text" class="form-control" id="editLastName" name="lastName" required>
|
||||
<input type="text" class="form-control" id="editLastName" name="last_name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editPhone" class="form-label">Numer telefonu</label>
|
||||
@ -348,7 +317,7 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editCompanyName" class="form-label">Nazwa firmy</label>
|
||||
<input type="text" class="form-control" id="editCompanyName" name="companyName">
|
||||
<input type="text" class="form-control" id="editCompanyName" name="company_name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
@ -429,11 +398,11 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<label for="createFirstName" class="form-label">First Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="createFirstName" name="firstName" required>
|
||||
<input type="text" class="form-control" id="createFirstName" name="first_name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="createLastName" class="form-label">Last Name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="createLastName" name="lastName" required>
|
||||
<input type="text" class="form-control" id="createLastName" name="last_name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="createPhone" class="form-label">Phone Number</label>
|
||||
@ -477,7 +446,7 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="createCompanyName" class="form-label">Company Name</label>
|
||||
<input type="text" class="form-control" id="createCompanyName" name="companyName">
|
||||
<input type="text" class="form-control" id="createCompanyName" name="company_name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
@ -641,7 +610,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const modalBody = instanceModal.querySelector('.modal-body');
|
||||
const modalTitle = instanceModal.querySelector('.modal-title');
|
||||
|
||||
fetch(`_get_instance_details.php?personId=${personId}&processId=${processId}`)
|
||||
fetch(`_get_instance_details.php?person_id=${personId}&process_id=${processId}`)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
modalBody.innerHTML = html;
|
||||
@ -902,12 +871,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
tableHtml += '<tbody>';
|
||||
instances.forEach(instance => {
|
||||
const person = peopleData.find(p => p.id == instance.personId);
|
||||
const person = peopleData.find(p => p.id == instance.person_id);
|
||||
if (!person) return;
|
||||
|
||||
tableHtml += `<tr><td class="person-cell">
|
||||
<div class="person-main">
|
||||
<div class="person-name">${htmlspecialchars(person.firstName + ' ' + person.lastName)}</div>
|
||||
<div class="person-name">${htmlspecialchars(person.first_name + ' ' + person.last_name)}</div>
|
||||
</div>
|
||||
</td>`;
|
||||
|
||||
|
||||
49
lib/ErrorHandler.php
Normal file
49
lib/ErrorHandler.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/WorkflowExceptions.php';
|
||||
|
||||
function generate_correlation_id() {
|
||||
return uniqid('corr_');
|
||||
}
|
||||
|
||||
function handle_exception(Throwable $e, $correlation_id) {
|
||||
if ($e instanceof WorkflowException) {
|
||||
http_response_code($e->getHttpCode());
|
||||
$response = [
|
||||
'error' => [
|
||||
'code' => $e->getCode(),
|
||||
'message' => $e->getMessage(),
|
||||
'details' => $e->getDetails(),
|
||||
],
|
||||
'correlation_id' => $correlation_id,
|
||||
];
|
||||
} else {
|
||||
http_response_code(500);
|
||||
error_log("Correlation ID: $correlation_id, Uncaught Exception: " . $e->getMessage() . "\n" . $e->getTraceAsString());
|
||||
$response = [
|
||||
'error' => [
|
||||
'code' => 500,
|
||||
'message' => 'Internal Server Error',
|
||||
],
|
||||
'correlation_id' => $correlation_id,
|
||||
];
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
function register_error_handler() {
|
||||
$GLOBALS['correlation_id'] = generate_correlation_id();
|
||||
set_exception_handler(function(Throwable $e) {
|
||||
handle_exception($e, $GLOBALS['correlation_id']);
|
||||
});
|
||||
// You can also set a general error handler for non-exception errors
|
||||
set_error_handler(function($severity, $message, $file, $line) {
|
||||
if (!(error_reporting() & $severity)) {
|
||||
return;
|
||||
}
|
||||
error_log("Correlation ID: {$GLOBALS['correlation_id']}, Error: [$severity] $message in $file on line $line");
|
||||
// Don't output anything to the user for non-fatal errors, just log them
|
||||
});
|
||||
}
|
||||
44
lib/WorkflowExceptions.php
Normal file
44
lib/WorkflowExceptions.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
class WorkflowException extends Exception {
|
||||
protected $httpCode;
|
||||
protected $details;
|
||||
|
||||
public function __construct($message = "", $code = 0, $httpCode = 500, $details = [], Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->httpCode = $httpCode;
|
||||
$this->details = $details;
|
||||
}
|
||||
|
||||
public function getHttpCode() {
|
||||
return $this->httpCode;
|
||||
}
|
||||
|
||||
public function getDetails() {
|
||||
return $this->details;
|
||||
}
|
||||
}
|
||||
|
||||
class WorkflowNotFoundException extends WorkflowException {
|
||||
public function __construct($message = "Not Found", $details = [], Throwable $previous = null) {
|
||||
parent::__construct($message, 404, 404, $details, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkflowNotAllowedException extends WorkflowException {
|
||||
public function __construct($message = "Bad Request", $details = [], Throwable $previous = null) {
|
||||
parent::__construct($message, 400, 400, $details, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkflowRuleFailedException extends WorkflowException {
|
||||
public function __construct($message = "Unprocessable Entity", $details = [], Throwable $previous = null) {
|
||||
parent::__construct($message, 422, 422, $details, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkflowConflictException extends WorkflowException {
|
||||
public function __construct($message = "Conflict", $details = [], Throwable $previous = null) {
|
||||
parent::__construct($message, 409, 409, $details, $previous);
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$_SESSION['user_id'] = $person['id'];
|
||||
$_SESSION['user_email'] = $person['email'];
|
||||
$_SESSION['user_role'] = $person['role'];
|
||||
$_SESSION['user_name'] = $person['firstName'] . ' ' . $person['lastName'];
|
||||
$_SESSION['user_name'] = $person['first_name'] . ' ' . $person['last_name'];
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
} else {
|
||||
|
||||
@ -101,11 +101,22 @@ $processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<div class="col-5">
|
||||
<select id="toStatusSelect" class="form-select"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="transitionActionJson" class="form-label">Actions (JSON)</label>
|
||||
<textarea class="form-control" id="transitionActionJson" rows="2"></textarea>
|
||||
</div>
|
||||
<div class="d-grid gap-2 mt-3">
|
||||
<button class="btn btn-outline-secondary" type="button" id="addTransitionBtn">Add Transition</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h5>Eligibility Rules (JSON)</h5>
|
||||
<div class="mb-3">
|
||||
<textarea class="form-control" id="eligibilityRulesJson" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<textarea name="definition_json" id="definitionJson" class="d-none"></textarea>
|
||||
|
||||
<hr class="mt-4">
|
||||
@ -119,97 +130,4 @@ $processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
<?php include '_footer.php'; ?>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const createProcessModal = document.getElementById('createProcessModal');
|
||||
const modalTitle = createProcessModal.querySelector('.modal-title');
|
||||
const form = createProcessModal.querySelector('#createProcessForm');
|
||||
const processIdInput = createProcessModal.querySelector('#processId');
|
||||
const processNameInput = createProcessModal.querySelector('#processName');
|
||||
const definitionJsonTextarea = createProcessModal.querySelector('#definitionJson');
|
||||
|
||||
function renderProcessDefinition(definition) {
|
||||
const statusesList = document.getElementById('statuses-list');
|
||||
const transitionsList = document.getElementById('transitions-list');
|
||||
const initialStatusSelect = document.getElementById('initialStatus');
|
||||
const fromStatusSelect = document.getElementById('fromStatusSelect');
|
||||
const toStatusSelect = document.getElementById('toStatusSelect');
|
||||
|
||||
statusesList.innerHTML = '';
|
||||
transitionsList.innerHTML = '';
|
||||
initialStatusSelect.innerHTML = '';
|
||||
fromStatusSelect.innerHTML = '';
|
||||
toStatusSelect.innerHTML = '';
|
||||
|
||||
if (!definition) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const def = JSON.parse(definition);
|
||||
|
||||
// Populate statuses
|
||||
if (def.nodes) {
|
||||
for (const nodeId in def.nodes) {
|
||||
const node = def.nodes[nodeId];
|
||||
const statusItem = document.createElement('div');
|
||||
statusItem.textContent = node.name;
|
||||
statusesList.appendChild(statusItem);
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = node.id;
|
||||
option.textContent = node.name;
|
||||
initialStatusSelect.appendChild(option.cloneNode(true));
|
||||
fromStatusSelect.appendChild(option.cloneNode(true));
|
||||
toStatusSelect.appendChild(option.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
// Set initial status
|
||||
if (def.start_node_id) {
|
||||
initialStatusSelect.value = def.start_node_id;
|
||||
}
|
||||
|
||||
// Populate transitions
|
||||
if (def.transitions) {
|
||||
def.transitions.forEach(transition => {
|
||||
const transitionItem = document.createElement('div');
|
||||
const fromNode = def.nodes[transition.from] ? def.nodes[transition.from].name : 'N/A';
|
||||
const toNode = def.nodes[transition.to] ? def.nodes[transition.to].name : 'N/A';
|
||||
transitionItem.textContent = `${transition.name}: ${fromNode} => ${toNode}`;
|
||||
transitionsList.appendChild(transitionItem);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing process definition:', e);
|
||||
// Optionally, display an error message to the user
|
||||
}
|
||||
}
|
||||
|
||||
createProcessModal.addEventListener('show.bs.modal', function (event) {
|
||||
const button = event.relatedTarget;
|
||||
const isEdit = button.classList.contains('edit-process-btn');
|
||||
|
||||
if (isEdit) {
|
||||
const processId = button.dataset.processId;
|
||||
const processName = button.dataset.processName;
|
||||
const processDefinition = button.dataset.processDefinition;
|
||||
|
||||
modalTitle.textContent = 'Edit Process';
|
||||
form.action = '_save_process_definition.php';
|
||||
processIdInput.value = processId;
|
||||
processNameInput.value = processName;
|
||||
definitionJsonTextarea.value = processDefinition;
|
||||
|
||||
renderProcessDefinition(processDefinition);
|
||||
} else {
|
||||
modalTitle.textContent = 'Create Process';
|
||||
form.action = '_save_process_definition.php';
|
||||
processIdInput.value = '';
|
||||
processNameInput.value = '';
|
||||
definitionJsonTextarea.value = '';
|
||||
renderProcessDefinition(''); // Clear the form
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script src="assets/js/process_definitions.js?v=<?php echo time(); ?>"></script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user