Pierwszy działający proces

This commit is contained in:
Flatlogic Bot 2026-01-10 17:22:42 +00:00
parent ee89357279
commit 524c7007ab
11 changed files with 1103 additions and 282 deletions

View File

@ -1,4 +1,5 @@
<?php
// Cache-buster: 1720638682
require_once __DIR__ . '/db/config.php';
class WorkflowEngine {
@ -27,7 +28,7 @@ class WorkflowEngine {
$instances = [];
foreach ($instances_data as $instance) {
$instances[$instance['personId']][$instance['processDefinitionId']] = $instance;
$instances[$instance['person_id']][$instance['process_definition_id']] = $instance;
}
return [
@ -38,33 +39,45 @@ class WorkflowEngine {
}
public function startProcess(string $processCode, int $personId, int $userId): ?int {
error_log("startProcess: processCode=$processCode, personId=$personId, userId=$userId");
$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->execute([$processCode]);
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
error_log("startProcess: definition=" . print_r($definition, true));
if (!$definition) {
throw new Exception("Active process definition with code '$processCode' not found.");
// If no process definition is found, check if there is a definition for a checklist
$stmt_def = $this->pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
$stmt_def->execute([$processCode]);
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
if (!$definition) {
throw new Exception("Process definition with code or id '$processCode' not found.");
}
$definition_json = json_decode($definition['definition_json'], true);
if (!$definition_json || !isset($definition_json['start_node_id'])) {
$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.");
}
// For checklists, there's no start_node_id, so we can proceed with instance creation
$startNodeId = null;
} else {
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : [];
if (empty($definition_json) || !isset($definition_json['start_node_id'])) {
throw new Exception("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 (personId, processDefinitionId, current_node_id, current_status, lastActivityAt) VALUES (?, ?, ?, 'in_progress', NOW())"
"INSERT INTO process_instances (person_id, process_definition_id, current_node_id, current_status, lastActivityAt) VALUES (?, ?, ?, 'in_progress', NOW())"
);
$stmt_insert->execute([$personId, $definition['id'], $startNodeId]);
error_log("startProcess: affected rows=" . $stmt_insert->rowCount());
$instanceId = $this->pdo->lastInsertId();
error_log("startProcess: instanceId=$instanceId");
// 3. Create a system event for process start.
$this->addEvent($instanceId, 'system', 'Process started.', $startNodeId, [], $userId);
@ -88,9 +101,9 @@ class WorkflowEngine {
}
$stmt_def = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
$stmt_def->execute([$instance['processDefinitionId']]);
$stmt_def->execute([$instance['process_definition_id']]);
$definition_json = $stmt_def->fetchColumn();
$definition = json_decode($definition_json, true);
$definition = !empty($definition_json) ? json_decode($definition_json, true) : [];
$currentNodeId = $instance['current_node_id'];
$nodeInfo = $definition['nodes'][$currentNodeId] ?? null;
@ -157,22 +170,45 @@ class WorkflowEngine {
}
}
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;
}
$currentNodeId = $state['instance']['current_node_id'];
$payload = ['message' => $message];
$this->addEvent($instanceId, 'note', $message, $currentNodeId, $payload, $userId);
return true;
}
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, eventType, message, node_id, payload_json, createdById) VALUES (?, ?, ?, ?, ?, ?)"
"INSERT INTO process_events (processInstanceId, event_type, message, node_id, payload_json, createdBy, createdAt) VALUES (?, ?, ?, ?, ?, ?, NOW())"
);
$stmt->execute([$instanceId, $eventType, $message, $nodeId, json_encode($payload), $userId]);
}
public function getOrCreateInstanceByDefId(int $personId, int $processDefinitionId, int $userId): ?array {
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE personId = ? AND processDefinitionId = ?");
$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->execute([$processDefinitionId]);
$definition_json = $stmt_def->fetchColumn();
$definition = !empty($definition_json) ? json_decode($definition_json, true) : [];
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($processCode) {
$instanceId = $this->startProcess($processCode, $personId, $userId);
@ -187,7 +223,7 @@ class WorkflowEngine {
}
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.createdById = p.id WHERE pe.processInstanceId = ? ORDER BY pe.createdAt DESC");
$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->execute([$instanceId]);
return $stmt_events->fetchAll(PDO::FETCH_ASSOC);
}
@ -212,4 +248,17 @@ class WorkflowEngine {
return $transitions;
}
public function getProcessDefinitionNodes(int $processDefinitionId): array {
$stmt = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
$stmt->execute([$processDefinitionId]);
$json = $stmt->fetchColumn();
if (!$json) {
return [];
}
$definition = !empty($json) ? json_decode($json, true) : [];
return $definition['nodes'] ?? [];
}
}

View File

@ -1,43 +1,67 @@
<?php
session_start();
header('Content-Type: application/json');
$response = ['success' => false, 'message' => 'Wystąpił nieoczekiwany błąd.'];
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: process_dashboard.php');
http_response_code(405);
$response['message'] = 'Invalid request method.';
echo json_encode($response);
exit;
}
require_once 'WorkflowEngine.php';
if (!isset($_POST['instanceId']) || !isset($_POST['transitionId'])) {
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Błąd: Brak wymaganych parametrów.'];
header('Location: process_dashboard.php');
http_response_code(400);
$response['message'] = 'Błąd: Brak wymaganych parametrów.';
echo json_encode($response);
exit;
}
$instanceId = (int)$_POST['instanceId'];
$transitionId = $_POST['transitionId'];
$userId = $_SESSION['user_id'] ?? null;
$payload = $_POST['payload'] ?? null;
$payload = $_POST['payload'] ?? [];
if (!$userId) {
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Błąd: Sesja wygasła.'];
header('Location: login.php');
http_response_code(401);
$response['message'] = 'Błąd: Sesja wygasła.';
echo json_encode($response);
exit;
}
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.';
}
}
} else {
// Standard transition logic
$success = $engine->applyTransition($instanceId, $transitionId, $payload, $userId);
if ($success) {
$response['message'] = 'Akcja została wykonana pomyślnie.';
}
}
if ($success) {
$_SESSION['flash_message'] = ['type' => 'success', 'message' => 'Akcja została wykonana pomyślnie.'];
} else {
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Błąd: Nie udało się wykonać akcji.'];
$response['success'] = true;
}
} catch (Exception $e) {
error_log("Error applying transition: " . $e->getMessage());
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Wystąpił krytyczny błąd: ' . $e->getMessage()];
http_response_code(500);
$response['message'] = 'Wystąpił krytyczny błąd: ' . $e->getMessage();
}
header('Location: process_dashboard.php');
echo json_encode($response);
exit;

View File

@ -1,17 +1,15 @@
<?php
require_once 'WorkflowEngine.php';
session_start();
// Security check
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
die('Brak autoryzacji');
}
$personId = $_GET['personId'] ?? null;
$processDefinitionId = $_GET['processId'] ?? null; // Pulpit wysyła processId, który jest ID definicji
$processDefinitionId = $_GET['processId'] ?? null;
if (!$personId || !$processDefinitionId) {
http_response_code(400);
@ -20,73 +18,158 @@ if (!$personId || !$processDefinitionId) {
$userId = $_SESSION['user_id'];
$engine = new WorkflowEngine();
$pdo = db();
// 1. Pobierz lub utwórz instancję
// 1. Get or create instance
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
if (!$instance) {
http_response_code(500);
die("Nie można pobrać lub utworzyć instancji procesu. personId: $personId, processDefinitionId: $processDefinitionId, userId: $userId");
die("Nie można pobrać lub utworzyć instancji procesu.");
}
$instanceId = $instance['id'];
// 2. Pobierz powiązane dane przez silnik
$events = $engine->getEvents($instanceId);
$availableTransitions = $engine->getAvailableTransitions($instanceId);
// 3. Pobierz nazwy do wyświetlenia
$pdo = db();
// 2. Fetch all related data
$stmt_person = $pdo->prepare("SELECT firstName, lastName FROM people WHERE id = ?");
$stmt_person->execute([$personId]);
$person = $stmt_person->fetch();
$stmt_process = $pdo->prepare("SELECT name FROM process_definitions WHERE id = ?");
$stmt_process = $pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
$stmt_process->execute([$processDefinitionId]);
$process = $stmt_process->fetch();
$definition = $process && $process['definition_json'] ? json_decode($process['definition_json'], true) : null;
$isChecklist = ($definition && isset($definition['type']) && $definition['type'] === 'checklist');
$events = $engine->getEvents($instanceId);
?>
<h4><?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?> - <?= htmlspecialchars($process['name']) ?></h4>
<p>Status: <span class="badge bg-secondary"><?= htmlspecialchars($instance['current_status']) ?></span></p>
<hr>
<!-- 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']) ?>
</div>
<h5>Wykonaj akcję</h5>
<form action="_apply_transition.php" method="post">
<input type="hidden" name="instanceId" value="<?= $instanceId ?>">
<div class="mb-3">
<label for="transitionSelect" class="form-label">Akcja</label>
<select name="transitionId" id="transitionSelect" class="form-select" required>
<option value="" disabled selected>-- Wybierz akcję --</option>
<?php foreach ($availableTransitions as $transition): ?>
<option value="<?= $transition['id'] ?>"><?= htmlspecialchars($transition['name']) ?></option>
<?php if ($isChecklist): ?>
<?php
$tasks = $definition['tasks'] ?? [];
$instanceData = $instance && $instance['data_json'] ? json_decode($instance['data_json'], true) : [];
?>
<div class="checklist-modal-container">
<h5>Zadania do wykonania</h5>
<div class="checklist-container">
<?php foreach ($tasks as $task):
$isChecked = !empty($instanceData[$task['code']]);
?>
<div class="form-check">
<input class="form-check-input task-checkbox-modal" type="checkbox" value=""
data-task-code="<?= $task['code'] ?>" <?= $isChecked ? 'checked' : '' ?>>
<label class="form-check-label" title="<?= htmlspecialchars($task['name']) ?>">
<?= htmlspecialchars($task['name']) ?>
</label>
</div>
<?php endforeach; ?>
<option value="note">Dodaj notatkę</option> <!-- Specjalny przypadek -->
</select>
</div>
<div class="mb-3">
<label for="payloadMessage" class="form-label">Notatka / Wiadomość (opcjonalnie)</label>
<textarea name="payload[message]" id="payloadMessage" class="form-control" rows="3"></textarea>
</div>
<button type="submit" class="btn btn-primary">Zatwierdź</button>
</form>
<?php else: ?>
<?php
$currentNodeId = $instance['current_node_id'];
$all_nodes = $engine->getProcessDefinitionNodes($processDefinitionId);
$availableTransitions = $engine->getAvailableTransitions($instanceId);
$available_target_node_ids = array_map(function($t) { return $t['to']; }, $availableTransitions);
$available_transitions_map = [];
foreach ($availableTransitions as $t) {
$available_transitions_map[$t['to']] = $t;
}
$visited_nodes = [];
foreach ($events as $event) {
if ($event['node_id']) {
$visited_nodes[$event['node_id']] = true;
}
}
?>
<div class="process-steps-container">
<h5>Kroki procesu</h5>
<ul class="list-group">
<?php foreach ($all_nodes as $nodeId => $node): ?>
<?php
$is_current = ($currentNodeId === $nodeId);
$is_completed = isset($visited_nodes[$nodeId]) && !$is_current;
$is_available = in_array($nodeId, $available_target_node_ids);
$status_icon = '';
$li_class = '';
$button = '';
if ($is_current) {
$li_class = 'list-group-item-primary';
$status_icon = '<i class="bi bi-arrow-right-circle-fill text-primary me-2"></i>';
} elseif ($is_completed) {
$li_class = 'list-group-item-success';
$status_icon = '<i class="bi bi-check-circle-fill text-success me-2"></i>';
} else {
$li_class = 'text-muted';
$status_icon = '<i class="bi bi-lock-fill me-2"></i>';
}
if ($is_available) {
$transition = $available_transitions_map[$nodeId];
$button = <<<HTML
<button class="btn btn-sm btn-primary apply-transition-btn"
data-instance-id="{$instanceId}"
data-transition-id="{$transition['id']}">
{$transition['name']}
</button>
HTML;
}
?>
<li class="list-group-item d-flex justify-content-between align-items-center <?= $li_class ?>">
<div>
<?= $status_icon ?>
<strong><?= htmlspecialchars($node['name']) ?></strong>
</div>
<?= $button ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<hr>
<h5>Historia</h5>
<?php if (empty($events)): ?>
<div class="add-note-container">
<h5>Dodaj notatkę</h5>
<div class="mb-3">
<textarea id="noteMessage" class="form-control" rows="2" placeholder="Wpisz treść notatki..."></textarea>
</div>
<button id="addNoteBtn" class="btn btn-secondary" data-instance-id="<?= $instanceId ?>">Dodaj notatkę</button>
</div>
<hr>
<div class="history-container">
<h5>Historia</h5>
<?php if (empty($events)): ?>
<p>Brak zdarzeń.</p>
<?php else: ?>
<?php else: ?>
<ul class="list-group">
<?php foreach ($events as $event): ?>
<li class="list-group-item">
<strong><?= htmlspecialchars(ucfirst(str_replace('_', ' ', $event['event_type']))) ?></strong>
<?php if (!empty($event['message'])):
<?php
if (!empty($event['message'])) {
$payload = json_decode($event['payload_json'], true);
$message = $payload['message'] ?? $event['message'];
echo '<p class="mb-1 text-muted fst-italic">' . htmlspecialchars($message) . '</p>';
}
?>
<p class="mb-1"><?= htmlspecialchars($message) ?></p>
<?php endif; ?>
<small class="text-muted">Przez <?= htmlspecialchars($event['firstName'] . ' ' . $event['lastName']) ?> dnia <?= date('d.m.Y, H:i', strtotime($event['createdAt'])) ?></small>
<small class="text-muted">Przez <?= htmlspecialchars($event['firstName'] . ' ' . $event['lastName']) ?> dnia <?= date('d.m.Y, H:i', strtotime($event['created_at'])) ?></small>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php endif; ?>
</div>

View File

@ -0,0 +1,96 @@
<?php
require_once 'db/config.php';
header('Content-Type: application/json');
if (!isset($_GET['process_id'])) {
http_response_code(400);
echo json_encode(['error' => 'Process ID is required.']);
exit;
}
$process_id = $_GET['process_id'];
try {
$pdo = db();
// 1. Get process definition details
$stmt_def = $pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
$stmt_def->execute([$process_id]);
$process_definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
if (!$process_definition) {
http_response_code(404);
echo json_encode(['error' => 'Process definition not found.']);
exit;
}
if (!empty($process_definition['definition_json'])) {
$process_definition['definition_json'] = json_decode($process_definition['definition_json'], true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception("Failed to decode process definition JSON. Error: " . json_last_error_msg());
}
} else {
$process_definition['definition_json'] = [];
}
// 2. Get all instances for this process
$stmt_instances = $pdo->prepare("SELECT * FROM process_instances WHERE processDefinitionId = ?");
$stmt_instances->execute([$process_id]);
$instances = $stmt_instances->fetchAll(PDO::FETCH_ASSOC);
$instance_ids = array_map(function($i) { return $i['id']; }, $instances);
// 3. Get all events for these instances
$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->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;
}
}
// 4. Get People details
$people_ids = array_unique(array_column($instances, 'personId'));
$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->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']);
}
}
}
// Assemble the response
// Ensure steps are available, even if the JSON is empty or malformed.
$steps = !empty($process_definition['definition_json']['steps']) ? $process_definition['definition_json']['steps'] : [];
$response = [
'process' => $process_definition,
'steps' => $steps,
'instances' => $instances,
'events' => $events,
'people' => $people
];
echo json_encode($response);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'A database error occurred.', 'details' => $e->getMessage()]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'A general error occurred.', 'details' => $e->getMessage()]);
}
?>

31
_init_single_instance.php Normal file
View File

@ -0,0 +1,31 @@
<?php
session_start();
require_once 'WorkflowEngine.php';
if (!isset($_SESSION['user_id'])) {
$_SESSION['error_message'] = "Authentication required.";
header('Location: login.php');
exit();
}
$userId = $_SESSION['user_id'];
$personId = $_GET['personId'] ?? null;
$processDefinitionId = $_GET['processId'] ?? null;
if (!$personId || !$processDefinitionId) {
$_SESSION['error_message'] = "Missing parameters for process initialization.";
header('Location: index.php');
exit();
}
$engine = new WorkflowEngine();
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
if ($instance) {
$_SESSION['success_message'] = "Process initialized successfully.";
} else {
$_SESSION['error_message'] = "Failed to initialize process.";
}
header('Location: index.php');
exit();

View File

@ -0,0 +1,81 @@
<?php
require_once 'db/config.php';
session_start();
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Method Not Allowed']);
exit;
}
$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;
}
$instanceId = $input['instance_id'] ?? null;
$taskCode = $input['task_code'] ?? null;
$isChecked = $input['is_checked'] ?? null;
if (!$instanceId || !$taskCode || $isChecked === null) {
http_response_code(400);
echo json_encode(['error' => 'Missing required parameters: instance_id, task_code, is_checked']);
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

@ -10,3 +10,151 @@ document.addEventListener('DOMContentLoaded', function () {
});
}
});
$(document).ready(function() {
// Handler for showing the edit person modal
$('#editPersonModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var personId = button.data('person-id'); // Extract info from data-* attributes
var modal = $(this);
// Clear previous data
modal.find('form').trigger('reset');
modal.find('#editPersonId').val('');
modal.find('#editRoles').empty();
// Clear file paths
modal.find('#editCompanyLogoPath, #editPersonPhotoPath, #editGainsSheetPath, #editTopWantedPath, #editTopOwnedPath').text('');
if (personId) {
// AJAX request to get person details
$.ajax({
url: '_get_person_details.php',
type: 'GET',
data: { id: personId },
dataType: 'json',
success: function(response) {
if (response.error) {
alert('Error fetching person details: ' + response.error);
return;
}
var person = response.person;
var all_functions = response.all_functions;
var person_functions = response.person_functions;
if (!person) {
alert('Could not find person data.');
return;
}
// Populate the form fields
modal.find('#editPersonId').val(person.id);
modal.find('#editFirstName').val(person.firstName);
modal.find('#editLastName').val(person.lastName);
modal.find('#editPhone').val(person.phone);
modal.find('#editEmail').val(person.email);
modal.find('#editRole').val(person.role);
modal.find('#editBniGroup').val(person.bni_group_id);
modal.find('#editCompanyName').val(person.companyName);
modal.find('#editNip').val(person.nip);
modal.find('#editIndustry').val(person.industry);
modal.find('#editCompanySize').val(person.company_size_revenue);
modal.find('#editBusinessDescription').val(person.business_description);
// Populate file paths
if (person.company_logo_path) {
modal.find('#editCompanyLogoPath').text('Current file: ' + person.company_logo_path.split('/').pop());
}
if (person.person_photo_path) {
modal.find('#editPersonPhotoPath').text('Current file: ' + person.person_photo_path.split('/').pop());
}
if (person.gains_sheet_path) {
modal.find('#editGainsSheetPath').text('Current file: ' + person.gains_sheet_path.split('/').pop());
}
if (person.top_wanted_contacts_path) {
modal.find('#editTopWantedPath').text('Current file: ' + person.top_wanted_contacts_path.split('/').pop());
}
if (person.top_owned_contacts_path) {
modal.find('#editTopOwnedPath').text('Current file: ' + person.top_owned_contacts_path.split('/').pop());
}
// Populate functions/roles dropdown and select assigned ones
var rolesSelect = modal.find('#editRoles');
rolesSelect.empty(); // Clear existing options
if (all_functions && all_functions.length > 0) {
const groupedFunctions = all_functions.reduce((acc, func) => {
const groupName = func.group_name || 'General';
if (!acc[groupName]) {
acc[groupName] = [];
}
acc[groupName].push(func);
return acc;
}, {});
for (const groupName in groupedFunctions) {
const optgroup = $('<optgroup>').attr('label', groupName);
groupedFunctions[groupName].forEach(function(func) {
var option = $('<option></option>').val(func.id).text(func.name);
if (person_functions && person_functions.includes(String(func.id))) {
option.prop('selected', true);
}
optgroup.append(option);
});
rolesSelect.append(optgroup);
}
}
// Trigger change to show/hide conditional fields
modal.find('#editRole').trigger('change');
// Also set up the delete button
$('#deleteUserBtn').data('person-id', person.id);
$('#personNameToDelete').text(person.firstName + ' ' + person.lastName);
},
error: function(xhr, status, error) {
alert('An error occurred while fetching person data. Please try again.');
console.error("AJAX Error:", status, error);
}
});
}
});
// Show/hide group selection based on role for both Edit and Create modals
$(document).on('change', '#editRole, #createRole', function() {
const role = $(this).val();
const isMember = role === 'member';
// Find the correct context (modal) for the elements
const modal = $(this).closest('.modal-content');
modal.find('.member-only-fields').toggle(isMember);
modal.find('#edit-group-selection-div, #create-group-selection-div').toggle(isMember);
});
// Handle Delete Person confirmation
$('#confirmDeleteBtn').on('click', function() {
var personId = $('#deleteUserBtn').data('person-id');
if (personId) {
// Use a form submission to perform the delete
var form = $('<form></form>');
form.attr("method", "post");
form.attr("action", "_delete_person.php");
var field = $('<input></input>');
field.attr("type", "hidden");
field.attr("name", "id");
field.attr("value", personId);
form.append(field);
$(document.body).append(form);
form.submit();
}
});
// Set initial state for create form
$('#createPersonModal').on('show.bs.modal', function () {
$('#createRole').trigger('change');
});
});

View File

@ -2,4 +2,4 @@
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
localhost FALSE / FALSE 0 PHPSESSID u19ekrhqoemk4c5avca3umanfb
127.0.0.1 FALSE / FALSE 0 PHPSESSID abf9a8g0sidv8idojcrr65jetp

View File

@ -67,17 +67,17 @@ try {
// 3. Process Instances table (updated FK)
$pdo->exec("CREATE TABLE IF NOT EXISTS `process_instances` (
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`personId` INT(11) UNSIGNED NOT NULL,
`processDefinitionId` INT(11) UNSIGNED NOT NULL,
`person_id` INT(11) UNSIGNED NOT NULL,
`process_definition_id` INT(11) UNSIGNED NOT NULL,
`current_status` VARCHAR(255) NOT NULL DEFAULT 'none',
`current_node_id` VARCHAR(255),
`current_reason` TEXT,
`suggested_next_step` TEXT,
`data_json` TEXT,
`lastActivityAt` TIMESTAMP NULL,
FOREIGN KEY (personId) REFERENCES people(id) ON DELETE CASCADE,
FOREIGN KEY (processDefinitionId) REFERENCES process_definitions(id) ON DELETE CASCADE,
UNIQUE KEY `person_process` (`personId`, `processDefinitionId`)
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`)
)");
echo "Process instances table created or already exists.\n";
@ -122,6 +122,7 @@ try {
echo "Migrated process_instances: contactId -> personId.\n";
}
// Drop old tables if they exist
$pdo->exec("DROP TABLE IF EXISTS `users`, `contacts`;");
echo "Dropped old 'users' and 'contacts' tables.\n";

316
index.php
View File

@ -1,4 +1,5 @@
<?php
session_start();
require_once 'WorkflowEngine.php';
$workflowEngine = new WorkflowEngine();
@ -15,7 +16,8 @@ $stmt = $pdo->query("
$people = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch process definitions
$stmt = $pdo->prepare("SELECT * FROM process_definitions WHERE name NOT IN (?, ?) ORDER BY name");
$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);
@ -31,10 +33,14 @@ foreach ($all_instances as $instance) {
$status_colors = [
'none' => 'secondary',
'negative' => 'danger',
'in_progress' => 'warning',
'positive' => 'success',
'completed' => '#28a745',
'positive' => '#28a745',
'in_progress' => '#fd7e14',
'negative' => '#dc3545',
'error' => '#dc3545',
'none' => '#808080',
'not_started' => '#808080',
'inactive' => '#808080',
];
$pdo = db();
@ -65,6 +71,7 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
<?php include '_sidebar.php'; ?>
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div id="main-dashboard-view">
<?php if (isset($_SESSION['success_message'])): ?>
<div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
<?= $_SESSION['success_message']; ?>
@ -104,7 +111,7 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
// Define process groups
// Show all processes from the DB directly.
$inne_procesy_cols = $processes;
$inne_procesy_cols[] = ['id' => 'temp_szkolenia', 'name' => 'Szkolenia dla nowego członka'];
// --- Spotkania Columns ---
// Fetch upcoming meetings for each group
@ -132,6 +139,13 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
</div>
<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';
?>
<table class="table table-bordered table-sm">
<thead class="table-light">
<tr class="text-center">
@ -146,7 +160,7 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
</tr>
<tr class="text-center" id="processes-header-row">
<?php foreach ($spotkania_cols as $index => $col): ?>
<th data-group-id="<?= $col['group_id'] ?>" data-col-index="<?= $index ?>">
<th class="process-header-clickable" data-process-id="<?= $meeting_process_id ?>" style="cursor: pointer;" data-group-id="<?= $col['group_id'] ?>" data-col-index="<?= $index ?>">
<?= htmlspecialchars($col['group_name']) ?><br>
<small>
<?= $col['next_meeting_date'] ? date('d.m.Y', strtotime($col['next_meeting_date'])) : 'Brak' ?>
@ -157,14 +171,14 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
</th>
<?php endforeach; ?>
<?php foreach ($inne_procesy_cols as $col): ?>
<th><?= htmlspecialchars($col['name']) ?></th>
<th class="process-header-clickable" data-process-id="<?= $col['id'] ?>" style="cursor: pointer;"><?= htmlspecialchars($col['name']) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($people as $person): ?>
<tr data-group-id="<?= $person['bni_group_id'] ?>">
<td class="text-center align-middle"><input type="checkbox" class="person-checkbox" name="person_ids[]" value="<?= $person['id'] ?>"></td>
<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>
@ -201,8 +215,8 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
// Display icon only if the person belongs to the group for that column.
if ($person['bni_group_id'] == $col['group_id']) {
$status = 'none'; // Default/placeholder status
$color = $status_colors[$status];
echo "<span class=\"badge rounded-circle bg-$color\" style=\"width: 20px; height: 20px; display: inline-block;\" title=\"Status nieokreślony\"></span>";
$color = $status_colors[$status] ?? '#808080';
echo "<span style='width: 20px; height: 20px; display: inline-block; border-radius: 50%; background-color: $color;' title='Status nieokreślony'></span>";
} else {
echo ''; // Empty cell if person is not in this group
}
@ -213,20 +227,61 @@ $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;
$status = $instance ? $instance['current_status'] : 'none';
$color = $status_colors[$status];
$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'])) : '';
$color = $status_colors['inactive'];
$title = 'Inactive';
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="text-center align-middle" data-bs-toggle="modal" data-bs-target="#instanceModal" data-person-id="<?= $person['id'] ?>" data-process-id="<?= $process['id'] ?>">
<span class="badge rounded-circle bg-<?= $color ?>" style="width: 20px; height: 20px; display: inline-block;" title="<?= ucfirst($status) ?>">&nbsp;</span>
<small class="text-muted d-block"><?= $lastActivity ?></small>
<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 endforeach; ?>
<?php } else {
$status = $instance ? $instance['current_status'] : 'inactive';
$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 }
endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div id="process-detail-view" style="display: none;">
<!-- This will be populated dynamically -->
</div>
</main>
</div>
</div>
@ -523,30 +578,77 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
<script>
document.addEventListener('DOMContentLoaded', function () {
// --- Injected PHP Data ---
const peopleData = <?= json_encode($people) ?>;
// --- STATE MANAGEMENT ---
const meetingsState = {};
// --- MODAL LOGIC ---
const instanceModal = document.getElementById('instanceModal');
let currentPersonId = null;
let currentProcessId = null;
if (instanceModal) {
// Event listener for when the modal is about to be shown
instanceModal.addEventListener('show.bs.modal', function (event) {
const button = event.relatedTarget;
const personId = button.getAttribute('data-person-id');
const processId = button.getAttribute('data-process-id');
currentPersonId = button.getAttribute('data-person-id');
currentProcessId = button.getAttribute('data-process-id');
const modalBody = instanceModal.querySelector('.modal-body');
const modalTitle = instanceModal.querySelector('.modal-title');
modalBody.innerHTML = '<div class="d-flex justify-content-center"><div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div></div>';
modalTitle.textContent = 'Ładowanie...';
fetch(`_get_instance_details.php?person_id=${personId}&process_id=${processId}`)
// Fetch and display the initial modal content
fetchAndRenderModalContent(currentPersonId, currentProcessId);
});
// Event listener for when the modal has been hidden
instanceModal.addEventListener('hidden.bs.modal', function () {
location.reload(); // Reload the main page to reflect any changes
});
// Delegated event listener for all actions within the modal
instanceModal.addEventListener('click', function(event) {
const transitionBtn = event.target.closest('.apply-transition-btn');
if (transitionBtn) {
handleTransition(transitionBtn);
return;
}
const noteBtn = event.target.closest('#addNoteBtn');
if (noteBtn) {
handleAddNote(noteBtn);
return;
}
});
instanceModal.addEventListener('change', function(event) {
const checkbox = event.target.closest('.task-checkbox-modal');
if (checkbox) {
handleCheckboxChange(checkbox);
return;
}
});
}
// --- HELPER FUNCTIONS FOR MODAL ---
function fetchAndRenderModalContent(personId, processId) {
const modalBody = instanceModal.querySelector('.modal-body');
const modalTitle = instanceModal.querySelector('.modal-title');
fetch(`_get_instance_details.php?personId=${personId}&processId=${processId}`)
.then(response => response.text())
.then(html => {
modalBody.innerHTML = html;
const newTitle = modalBody.querySelector('#instance-modal-title');
if (newTitle) {
modalTitle.innerHTML = newTitle.innerHTML;
newTitle.remove();
const newTitleEl = modalBody.querySelector('#instance-modal-title');
if (newTitleEl) {
modalTitle.innerHTML = newTitleEl.innerHTML;
newTitleEl.remove();
} else {
modalTitle.textContent = 'Szczegóły procesu';
}
@ -556,9 +658,98 @@ document.addEventListener('DOMContentLoaded', function () {
modalBody.innerHTML = '<p class="text-danger">Wystąpił błąd podczas ładowania danych.</p>';
modalTitle.textContent = 'Błąd';
});
}
function handleCheckboxChange(checkbox) {
const taskCode = checkbox.dataset.taskCode;
const isChecked = checkbox.checked;
const instanceId = checkbox.closest('.modal-body').querySelector('[data-instance-id]').dataset.instanceId;
fetch('_update_training_checklist_status.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({
instance_id: instanceId,
task_code: taskCode,
is_checked: isChecked
})
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (!data.success) {
console.error('Failed to update checklist status:', data.error);
checkbox.checked = !isChecked; // Revert on failure
alert('Error updating status: ' + data.error);
}
// No reload on success, keep modal open
})
.catch(error => {
console.error('Network or server error during checklist update:', error);
checkbox.checked = !isChecked; // Revert on network error
alert('A network error occurred. Please try again.');
});
}
function handleTransition(button) {
const instanceId = button.dataset.instanceId;
const transitionId = button.dataset.transitionId;
if (!confirm(`Czy na pewno chcesz wykonać akcję \"'''${button.textContent.trim()}'''\"?`)) {
return;
}
const formData = new FormData();
formData.append('instanceId', instanceId);
formData.append('transitionId', transitionId);
submitRequestAndReloadModal('_apply_transition.php', formData);
}
function handleAddNote(button) {
const instanceId = button.dataset.instanceId;
const message = document.getElementById('noteMessage').value;
if (!message.trim()) {
alert('Proszę wpisać treść notatki.');
return;
}
const formData = new FormData();
formData.append('instanceId', instanceId);
formData.append('transitionId', 'note'); // Special transitionId
formData.append('payload[message]', message);
submitRequestAndReloadModal('_apply_transition.php', formData);
}
function submitRequestAndReloadModal(url, formData) {
const modalBody = instanceModal.querySelector('.modal-body');
showLoading(modalBody);
fetch(url, {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
// Reload modal content after successful submission
fetchAndRenderModalContent(currentPersonId, currentProcessId);
})
.catch(error => {
console.error('Error submitting request:', error);
modalBody.innerHTML = `<div class="alert alert-danger">Wystąpił błąd sieciowy.</div>`;
});
}
function showLoading(element) {
element.innerHTML = '<div class="d-flex justify-content-center align-items-center" style="min-height: 200px;"><div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div></div>';
}
// --- GROUP FILTER LOGIC ---
const groupFilter = document.getElementById('groupFilter');
if (groupFilter) {
@ -578,6 +769,8 @@ document.addEventListener('DOMContentLoaded', function () {
const expandBtn = event.target.closest('.expand-meeting');
if (!expandBtn) return;
event.stopPropagation(); // Prevent the click from bubbling up to the process-header-clickable listener
const groupId = expandBtn.dataset.groupId;
expandBtn.style.display = 'none';
@ -614,7 +807,6 @@ document.addEventListener('DOMContentLoaded', function () {
const meetingDate = new Date(meeting.start_datetime);
const formattedDate = meetingDate.toLocaleDateString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric' });
// In a real scenario, you'd get this info from the backend
const isLastInBatch = false;
const isLastInGroup = false;
@ -670,6 +862,8 @@ document.addEventListener('DOMContentLoaded', function () {
});
}
function findLastElementForGroup(parent, selector, groupId, findFirst = false) {
const elements = parent.querySelectorAll(`${selector}[data-group-id="${groupId}"]`);
if (elements.length === 0) return null;
@ -678,14 +872,74 @@ document.addEventListener('DOMContentLoaded', function () {
function htmlspecialchars(str) {
if (typeof str !== 'string') return '';
return str.replace(/[&<>"']/g, match => ({
return str.replace(/[&<>"]/g, match => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
'"': '&quot;'
}[match]));
}
// --- PROCESS BULK VIEW LOGIC ---
const processDetailView = document.getElementById('process-detail-view');
function renderProcessDetailView(data) {
const { process, steps, instances } = data;
let tableHtml = `<div class="d-flex justify-content-between align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Process: ${htmlspecialchars(process.name)}</h1>
<button class="btn btn-secondary" id="back-to-dashboard">Back to Dashboard</button>
</div>`;
tableHtml += '<div class="table-responsive"><table class="table table-bordered table-sm">';
tableHtml += '<thead class="table-light"><tr><th>Person</th>';
steps.forEach(step => {
tableHtml += `<th>${htmlspecialchars(step.name)}</th>`;
});
tableHtml += '</tr></thead>';
tableHtml += '<tbody>';
instances.forEach(instance => {
const person = peopleData.find(p => p.id == instance.personId);
if (!person) return;
tableHtml += `<tr><td class="person-cell">
<div class="person-main">
<div class="person-name">${htmlspecialchars(person.firstName + ' ' + person.lastName)}</div>
</div>
</td>`;
steps.forEach(step => {
const stepState = instance.steps.find(s => s.step_id == step.id);
const status = stepState ? stepState.status : 'pending';
const statusColors = {
pending: 'secondary',
in_progress: 'warning',
completed: 'success',
skipped: 'light',
failed: 'danger'
};
const color = statusColors[status] || 'secondary';
tableHtml += `<td class="text-center align-middle">
<span class="badge rounded-circle bg-${color}" style="width: 20px; height: 20px; display: inline-block;" title="${status}">&nbsp;</span>
</td>`;
});
tableHtml += '</tr>';
});
tableHtml += '</tbody></table></div>';
processDetailView.innerHTML = tableHtml;
document.getElementById('back-to-dashboard').addEventListener('click', () => {
processDetailView.style.display = 'none';
mainDashboardView.style.display = 'block';
processDetailView.innerHTML = '';
});
}
});
</script>
@ -700,7 +954,7 @@ document.addEventListener('DOMContentLoaded', function () {
</div>
<div class="modal-body">
<form action="_bulk_update_status.php" method="post">
<input type="hidden" name="person_ids" id="bulkStatusPersonIds">
<input type="hidden" name="personIds" id="bulkStatusPersonIds">
<div class="mb-3">
<label class="form-label">Process</label>
<select name="process_id" class="form-select" required>
@ -735,7 +989,7 @@ document.addEventListener('DOMContentLoaded', function () {
</div>
<div class="modal-body">
<form action="_bulk_add_event.php" method="post">
<input type="hidden" name="person_ids" id="bulkEventPersonIds">
<input type="hidden" name="personIds" id="bulkEventPersonIds">
<div class="mb-3">
<label class="form-label">Process</label>
<select name="process_id" class="form-select" required>
@ -765,7 +1019,7 @@ document.addEventListener('DOMContentLoaded', function () {
</div>
<div class="modal-body">
<form action="_bulk_init_instances.php" method="post">
<input type="hidden" name="person_ids" id="bulkInitPersonIds">
<input type="hidden" name="personIds" id="bulkInitPersonIds">
<div class="mb-3">
<label class="form-label">Process</label> <select name="process_id" class="form-select" required>
<?php foreach($processes as $process): ?>

54
test_workflow.php Normal file
View File

@ -0,0 +1,54 @@
<?php
// test_workflow.php
error_reporting(E_ALL);
ini_set('display_errors', 1);
require_once 'WorkflowEngine.php';
require_once 'db/config.php'; // Include the database configuration
echo "Testing WorkflowEngine...<br>";
// Establish a direct database connection for setup
$pdo = db();
// 1. Get the first person from the database
$stmt = $pdo->query("SELECT id FROM people LIMIT 1");
$person = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$person) {
die("<b>Error:</b> No people found in the database. Please run `db_setup.php` or ensure the database is seeded correctly.<br>");
}
$personId = $person['id'];
echo "Prerequisite check: Using person with ID $personId.<br>";
// 2. Get the first process definition from the database
$stmt = $pdo->query("SELECT id FROM process_definitions LIMIT 1");
$process = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$process) {
die("<b>Error:</b> No process definitions found in the database. Please run `db_setup.php` or ensure the database is seeded correctly.<br>");
}
$processDefinitionId = $process['id'];
echo "Prerequisite check: Using process definition with ID $processDefinitionId.<br>";
$engine = new WorkflowEngine();
$userId = $personId;
echo "Attempting to get or create instance with personId: $personId and processDefinitionId: $processDefinitionId<br>";
try {
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
if ($instance) {
echo "<b>Success!</b> Successfully got or created instance:<br>";
echo "<pre>";
print_r($instance);
echo "</pre>";
} else {
echo "<b>Error:</b> Failed to get or create instance, but no exception was thrown.<br>";
echo "This might happen if the process definition exists, but `startProcess` fails internally.<br>";
echo "Check PHP error logs for more details.<br>";
}
} catch (Exception $e) {
echo "An exception occurred: " . $e->getMessage() . "<br>";
echo "Stack trace:<br><pre>" . $e->getTraceAsString() . "</pre>";
}