Wersja po posprzątaniu kodu

This commit is contained in:
Flatlogic Bot 2026-01-10 19:52:03 +00:00
parent 524c7007ab
commit a703aeb1e2
26 changed files with 919 additions and 548 deletions

View File

@ -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,9 +285,119 @@ 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]);
}
@ -196,20 +408,22 @@ class WorkflowEngine {
$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);
}
}
}

View File

@ -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;

View File

@ -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;
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.';
}
if (empty($message)) {
throw new WorkflowNotAllowedException('Treść notatki nie może być pusta.');
}
$engine->addNote($instanceId, $message, $userId);
$response = ['success' => true, '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 ($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();
$result = $engine->applyTransition($instanceId, $transitionId, $payload, $userId);
$response = ['success' => true, 'message' => 'Akcja została wykonana pomyślnie.', 'data' => $result];
}
echo json_encode($response);
exit;

View File

@ -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);
}
$_SESSION['flash_message'] = "Bulk event addition completed.";
header('Location: process_dashboard.php');
if (empty($instance_ids)) {
$_SESSION['flash_message'] = "No instances found for the selected people and process.";
header('Location: ' . $_SERVER['HTTP_REFERER']);
exit;
}
$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;

View File

@ -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();

View File

@ -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), '?'));
if (empty($instance_ids)) {
$_SESSION['flash_message'] = "No instances found for the selected people and process.";
header('Location: ' . $_SERVER['HTTP_REFERER']);
exit;
}
// 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";
$statuses = [];
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);
$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;

View File

@ -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

View File

@ -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>

View File

@ -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']);
}
}
}

View File

@ -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');

View File

@ -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++;
}

View File

@ -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();

View File

@ -1,30 +1,32 @@
<?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();
if (empty($processId)) {
// Create new process
$sql = 'INSERT INTO process_definitions (name, definition_json) VALUES (?, ?)';
$params = [$name, $definition_json];
$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
@ -36,14 +38,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$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');
}
exit();
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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
];

View File

@ -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']);
throw new WorkflowRuleFailedException('Missing required parameters: instance_id, task_code, is_checked');
}
$workflowEngine = new WorkflowEngine();
$result = $workflowEngine->updateChecklistStatus((int)$instanceId, $taskCode, (bool)$isChecked, (int)$userId);
echo json_encode($result);
exit;
}
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()]);
}
?>

View 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();
});
});

View 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());
}

View 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");
}

View File

@ -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";
}

159
index.php
View File

@ -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);
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: 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>
<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 }
<?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
View 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
});
}

View 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);
}
}

View File

@ -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 {

View File

@ -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>