Pierwszy działający proces
This commit is contained in:
parent
ee89357279
commit
524c7007ab
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
// Cache-buster: 1720638682
|
||||||
require_once __DIR__ . '/db/config.php';
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
|
||||||
class WorkflowEngine {
|
class WorkflowEngine {
|
||||||
@ -27,7 +28,7 @@ class WorkflowEngine {
|
|||||||
|
|
||||||
$instances = [];
|
$instances = [];
|
||||||
foreach ($instances_data as $instance) {
|
foreach ($instances_data as $instance) {
|
||||||
$instances[$instance['personId']][$instance['processDefinitionId']] = $instance;
|
$instances[$instance['person_id']][$instance['process_definition_id']] = $instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -38,33 +39,45 @@ class WorkflowEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function startProcess(string $processCode, int $personId, int $userId): ?int {
|
public function startProcess(string $processCode, int $personId, int $userId): ?int {
|
||||||
error_log("startProcess: processCode=$processCode, personId=$personId, userId=$userId");
|
|
||||||
$this->pdo->beginTransaction();
|
$this->pdo->beginTransaction();
|
||||||
try {
|
try {
|
||||||
// 1. Find active process definition by code.
|
// 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 active = 1");
|
||||||
$stmt_def->execute([$processCode]);
|
$stmt_def->execute([$processCode]);
|
||||||
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
||||||
error_log("startProcess: definition=" . print_r($definition, true));
|
|
||||||
|
|
||||||
if (!$definition) {
|
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);
|
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : [];
|
||||||
if (!$definition_json || !isset($definition_json['start_node_id'])) {
|
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.");
|
throw new Exception("Process definition is missing start_node_id.");
|
||||||
}
|
}
|
||||||
$startNodeId = $definition_json['start_node_id'];
|
$startNodeId = $definition_json['start_node_id'];
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Create a new process instance.
|
// 2. Create a new process instance.
|
||||||
$stmt_insert = $this->pdo->prepare(
|
$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]);
|
$stmt_insert->execute([$personId, $definition['id'], $startNodeId]);
|
||||||
error_log("startProcess: affected rows=" . $stmt_insert->rowCount());
|
|
||||||
$instanceId = $this->pdo->lastInsertId();
|
$instanceId = $this->pdo->lastInsertId();
|
||||||
error_log("startProcess: instanceId=$instanceId");
|
|
||||||
|
|
||||||
// 3. Create a system event for process start.
|
// 3. Create a system event for process start.
|
||||||
$this->addEvent($instanceId, 'system', 'Process started.', $startNodeId, [], $userId);
|
$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 = $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 = $stmt_def->fetchColumn();
|
||||||
$definition = json_decode($definition_json, true);
|
$definition = !empty($definition_json) ? json_decode($definition_json, true) : [];
|
||||||
|
|
||||||
$currentNodeId = $instance['current_node_id'];
|
$currentNodeId = $instance['current_node_id'];
|
||||||
$nodeInfo = $definition['nodes'][$currentNodeId] ?? null;
|
$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 {
|
private function addEvent(int $instanceId, string $eventType, string $message, ?string $nodeId, array $payload, int $userId): void {
|
||||||
$stmt = $this->pdo->prepare(
|
$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]);
|
$stmt->execute([$instanceId, $eventType, $message, $nodeId, json_encode($payload), $userId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOrCreateInstanceByDefId(int $personId, int $processDefinitionId, int $userId): ?array {
|
public function getOrCreateInstanceByDefId(int $personId, int $processDefinitionId, int $userId): ?array {
|
||||||
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE personId = ? AND processDefinitionId = ?");
|
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE `person_id` = ? AND `process_definition_id` = ?");
|
||||||
$stmt->execute([$personId, $processDefinitionId]);
|
$stmt->execute([$personId, $processDefinitionId]);
|
||||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$instance) {
|
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 = $this->pdo->prepare("SELECT code FROM process_definitions WHERE id = ?");
|
||||||
$stmt_def->execute([$processDefinitionId]);
|
$stmt_def->execute([$processDefinitionId]);
|
||||||
$processCode = $stmt_def->fetchColumn();
|
$processCode = $stmt_def->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
if($processCode) {
|
if($processCode) {
|
||||||
$instanceId = $this->startProcess($processCode, $personId, $userId);
|
$instanceId = $this->startProcess($processCode, $personId, $userId);
|
||||||
@ -187,7 +223,7 @@ class WorkflowEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getEvents(int $instanceId): array {
|
public function getEvents(int $instanceId): array {
|
||||||
$stmt_events = $this->pdo->prepare("SELECT pe.*, p.email as user_email, p.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]);
|
$stmt_events->execute([$instanceId]);
|
||||||
return $stmt_events->fetchAll(PDO::FETCH_ASSOC);
|
return $stmt_events->fetchAll(PDO::FETCH_ASSOC);
|
||||||
}
|
}
|
||||||
@ -212,4 +248,17 @@ class WorkflowEngine {
|
|||||||
|
|
||||||
return $transitions;
|
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'] ?? [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,43 +1,67 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$response = ['success' => false, 'message' => 'Wystąpił nieoczekiwany błąd.'];
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
header('Location: process_dashboard.php');
|
http_response_code(405);
|
||||||
|
$response['message'] = 'Invalid request method.';
|
||||||
|
echo json_encode($response);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once 'WorkflowEngine.php';
|
require_once 'WorkflowEngine.php';
|
||||||
|
|
||||||
if (!isset($_POST['instanceId']) || !isset($_POST['transitionId'])) {
|
if (!isset($_POST['instanceId']) || !isset($_POST['transitionId'])) {
|
||||||
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Błąd: Brak wymaganych parametrów.'];
|
http_response_code(400);
|
||||||
header('Location: process_dashboard.php');
|
$response['message'] = 'Błąd: Brak wymaganych parametrów.';
|
||||||
|
echo json_encode($response);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$instanceId = (int)$_POST['instanceId'];
|
$instanceId = (int)$_POST['instanceId'];
|
||||||
$transitionId = $_POST['transitionId'];
|
$transitionId = $_POST['transitionId'];
|
||||||
$userId = $_SESSION['user_id'] ?? null;
|
$userId = $_SESSION['user_id'] ?? null;
|
||||||
$payload = $_POST['payload'] ?? null;
|
$payload = $_POST['payload'] ?? [];
|
||||||
|
|
||||||
if (!$userId) {
|
if (!$userId) {
|
||||||
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Błąd: Sesja wygasła.'];
|
http_response_code(401);
|
||||||
header('Location: login.php');
|
$response['message'] = 'Błąd: Sesja wygasła.';
|
||||||
|
echo json_encode($response);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$engine = new WorkflowEngine();
|
$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);
|
$success = $engine->applyTransition($instanceId, $transitionId, $payload, $userId);
|
||||||
|
if ($success) {
|
||||||
|
$response['message'] = 'Akcja została wykonana pomyślnie.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($success) {
|
if ($success) {
|
||||||
$_SESSION['flash_message'] = ['type' => 'success', 'message' => 'Akcja została wykonana pomyślnie.'];
|
$response['success'] = true;
|
||||||
} else {
|
|
||||||
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Błąd: Nie udało się wykonać akcji.'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("Error applying transition: " . $e->getMessage());
|
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;
|
exit;
|
||||||
@ -1,17 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once 'WorkflowEngine.php';
|
require_once 'WorkflowEngine.php';
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
|
// Security check
|
||||||
if (!isset($_SESSION['user_id'])) {
|
if (!isset($_SESSION['user_id'])) {
|
||||||
http_response_code(401);
|
http_response_code(401);
|
||||||
die('Brak autoryzacji');
|
die('Brak autoryzacji');
|
||||||
}
|
}
|
||||||
|
|
||||||
$personId = $_GET['personId'] ?? null;
|
$personId = $_GET['personId'] ?? null;
|
||||||
$processDefinitionId = $_GET['processId'] ?? null; // Pulpit wysyła processId, który jest ID definicji
|
$processDefinitionId = $_GET['processId'] ?? null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!$personId || !$processDefinitionId) {
|
if (!$personId || !$processDefinitionId) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
@ -20,73 +18,158 @@ if (!$personId || !$processDefinitionId) {
|
|||||||
|
|
||||||
$userId = $_SESSION['user_id'];
|
$userId = $_SESSION['user_id'];
|
||||||
$engine = new WorkflowEngine();
|
$engine = new WorkflowEngine();
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
// 1. Pobierz lub utwórz instancję
|
// 1. Get or create instance
|
||||||
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
|
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
|
||||||
|
|
||||||
if (!$instance) {
|
if (!$instance) {
|
||||||
http_response_code(500);
|
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'];
|
$instanceId = $instance['id'];
|
||||||
|
|
||||||
// 2. Pobierz powiązane dane przez silnik
|
// 2. Fetch all related data
|
||||||
$events = $engine->getEvents($instanceId);
|
|
||||||
$availableTransitions = $engine->getAvailableTransitions($instanceId);
|
|
||||||
|
|
||||||
// 3. Pobierz nazwy do wyświetlenia
|
|
||||||
$pdo = db();
|
|
||||||
$stmt_person = $pdo->prepare("SELECT firstName, lastName FROM people WHERE id = ?");
|
$stmt_person = $pdo->prepare("SELECT firstName, lastName FROM people WHERE id = ?");
|
||||||
$stmt_person->execute([$personId]);
|
$stmt_person->execute([$personId]);
|
||||||
$person = $stmt_person->fetch();
|
$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]);
|
$stmt_process->execute([$processDefinitionId]);
|
||||||
$process = $stmt_process->fetch();
|
$process = $stmt_process->fetch();
|
||||||
|
$definition = $process && $process['definition_json'] ? json_decode($process['definition_json'], true) : null;
|
||||||
|
$isChecklist = ($definition && isset($definition['type']) && $definition['type'] === 'checklist');
|
||||||
|
|
||||||
|
$events = $engine->getEvents($instanceId);
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<h4><?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?> - <?= htmlspecialchars($process['name']) ?></h4>
|
<!-- Title for the modal, to be grabbed by JS -->
|
||||||
<p>Status: <span class="badge bg-secondary"><?= htmlspecialchars($instance['current_status']) ?></span></p>
|
<div id="instance-modal-title" class="d-none">
|
||||||
<hr>
|
<?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?> - <?= htmlspecialchars($process['name']) ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h5>Wykonaj akcję</h5>
|
<?php if ($isChecklist): ?>
|
||||||
<form action="_apply_transition.php" method="post">
|
<?php
|
||||||
<input type="hidden" name="instanceId" value="<?= $instanceId ?>">
|
$tasks = $definition['tasks'] ?? [];
|
||||||
<div class="mb-3">
|
$instanceData = $instance && $instance['data_json'] ? json_decode($instance['data_json'], true) : [];
|
||||||
<label for="transitionSelect" class="form-label">Akcja</label>
|
?>
|
||||||
<select name="transitionId" id="transitionSelect" class="form-select" required>
|
<div class="checklist-modal-container">
|
||||||
<option value="" disabled selected>-- Wybierz akcję --</option>
|
<h5>Zadania do wykonania</h5>
|
||||||
<?php foreach ($availableTransitions as $transition): ?>
|
<div class="checklist-container">
|
||||||
<option value="<?= $transition['id'] ?>"><?= htmlspecialchars($transition['name']) ?></option>
|
<?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; ?>
|
<?php endforeach; ?>
|
||||||
<option value="note">Dodaj notatkę</option> <!-- Specjalny przypadek -->
|
|
||||||
</select>
|
|
||||||
</div>
|
</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>
|
</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>
|
<hr>
|
||||||
|
|
||||||
<h5>Historia</h5>
|
<div class="add-note-container">
|
||||||
<?php if (empty($events)): ?>
|
<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>
|
<p>Brak zdarzeń.</p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<?php foreach ($events as $event): ?>
|
<?php foreach ($events as $event): ?>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<strong><?= htmlspecialchars(ucfirst(str_replace('_', ' ', $event['event_type']))) ?></strong>
|
<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);
|
$payload = json_decode($event['payload_json'], true);
|
||||||
$message = $payload['message'] ?? $event['message'];
|
$message = $payload['message'] ?? $event['message'];
|
||||||
|
echo '<p class="mb-1 text-muted fst-italic">' . htmlspecialchars($message) . '</p>';
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<p class="mb-1"><?= 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>
|
||||||
<?php endif; ?>
|
|
||||||
<small class="text-muted">Przez <?= htmlspecialchars($event['firstName'] . ' ' . $event['lastName']) ?> dnia <?= date('d.m.Y, H:i', strtotime($event['createdAt'])) ?></small>
|
|
||||||
</li>
|
</li>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</ul>
|
</ul>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
96
_get_process_bulk_details.php
Normal file
96
_get_process_bulk_details.php
Normal 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
31
_init_single_instance.php
Normal 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();
|
||||||
81
_update_training_checklist_status.php
Normal file
81
_update_training_checklist_status.php
Normal 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()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -2,4 +2,4 @@
|
|||||||
# https://curl.se/docs/http-cookies.html
|
# https://curl.se/docs/http-cookies.html
|
||||||
# This file was generated by libcurl! Edit at your own risk.
|
# 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
|
||||||
|
|||||||
11
db_setup.php
11
db_setup.php
@ -67,17 +67,17 @@ try {
|
|||||||
// 3. Process Instances table (updated FK)
|
// 3. Process Instances table (updated FK)
|
||||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `process_instances` (
|
$pdo->exec("CREATE TABLE IF NOT EXISTS `process_instances` (
|
||||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
`personId` INT(11) UNSIGNED NOT NULL,
|
`person_id` INT(11) UNSIGNED NOT NULL,
|
||||||
`processDefinitionId` INT(11) UNSIGNED NOT NULL,
|
`process_definition_id` INT(11) UNSIGNED NOT NULL,
|
||||||
`current_status` VARCHAR(255) NOT NULL DEFAULT 'none',
|
`current_status` VARCHAR(255) NOT NULL DEFAULT 'none',
|
||||||
`current_node_id` VARCHAR(255),
|
`current_node_id` VARCHAR(255),
|
||||||
`current_reason` TEXT,
|
`current_reason` TEXT,
|
||||||
`suggested_next_step` TEXT,
|
`suggested_next_step` TEXT,
|
||||||
`data_json` TEXT,
|
`data_json` TEXT,
|
||||||
`lastActivityAt` TIMESTAMP NULL,
|
`lastActivityAt` TIMESTAMP NULL,
|
||||||
FOREIGN KEY (personId) REFERENCES people(id) ON DELETE CASCADE,
|
FOREIGN KEY (person_id) REFERENCES people(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (processDefinitionId) REFERENCES process_definitions(id) ON DELETE CASCADE,
|
FOREIGN KEY (process_definition_id) REFERENCES process_definitions(id) ON DELETE CASCADE,
|
||||||
UNIQUE KEY `person_process` (`personId`, `processDefinitionId`)
|
UNIQUE KEY `person_process` (`person_id`, `process_definition_id`)
|
||||||
)");
|
)");
|
||||||
echo "Process instances table created or already exists.\n";
|
echo "Process instances table created or already exists.\n";
|
||||||
|
|
||||||
@ -122,6 +122,7 @@ try {
|
|||||||
echo "Migrated process_instances: contactId -> personId.\n";
|
echo "Migrated process_instances: contactId -> personId.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Drop old tables if they exist
|
// Drop old tables if they exist
|
||||||
$pdo->exec("DROP TABLE IF EXISTS `users`, `contacts`;");
|
$pdo->exec("DROP TABLE IF EXISTS `users`, `contacts`;");
|
||||||
echo "Dropped old 'users' and 'contacts' tables.\n";
|
echo "Dropped old 'users' and 'contacts' tables.\n";
|
||||||
|
|||||||
316
index.php
316
index.php
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
session_start();
|
||||||
require_once 'WorkflowEngine.php';
|
require_once 'WorkflowEngine.php';
|
||||||
|
|
||||||
$workflowEngine = new WorkflowEngine();
|
$workflowEngine = new WorkflowEngine();
|
||||||
@ -15,7 +16,8 @@ $stmt = $pdo->query("
|
|||||||
$people = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$people = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
// Fetch process definitions
|
// 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']);
|
$stmt->execute(['Obsluga goscia', 'Przygotowanie spotkania grupy']);
|
||||||
$processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
@ -31,10 +33,14 @@ foreach ($all_instances as $instance) {
|
|||||||
|
|
||||||
|
|
||||||
$status_colors = [
|
$status_colors = [
|
||||||
'none' => 'secondary',
|
'completed' => '#28a745',
|
||||||
'negative' => 'danger',
|
'positive' => '#28a745',
|
||||||
'in_progress' => 'warning',
|
'in_progress' => '#fd7e14',
|
||||||
'positive' => 'success',
|
'negative' => '#dc3545',
|
||||||
|
'error' => '#dc3545',
|
||||||
|
'none' => '#808080',
|
||||||
|
'not_started' => '#808080',
|
||||||
|
'inactive' => '#808080',
|
||||||
];
|
];
|
||||||
|
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
@ -65,6 +71,7 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<?php include '_sidebar.php'; ?>
|
<?php include '_sidebar.php'; ?>
|
||||||
|
|
||||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
<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'])): ?>
|
<?php if (isset($_SESSION['success_message'])): ?>
|
||||||
<div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
|
<div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
|
||||||
<?= $_SESSION['success_message']; ?>
|
<?= $_SESSION['success_message']; ?>
|
||||||
@ -104,7 +111,7 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
// Define process groups
|
// Define process groups
|
||||||
// Show all processes from the DB directly.
|
// Show all processes from the DB directly.
|
||||||
$inne_procesy_cols = $processes;
|
$inne_procesy_cols = $processes;
|
||||||
$inne_procesy_cols[] = ['id' => 'temp_szkolenia', 'name' => 'Szkolenia dla nowego członka'];
|
|
||||||
|
|
||||||
// --- Spotkania Columns ---
|
// --- Spotkania Columns ---
|
||||||
// Fetch upcoming meetings for each group
|
// Fetch upcoming meetings for each group
|
||||||
@ -132,6 +139,13 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<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">
|
<table class="table table-bordered table-sm">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr class="text-center">
|
<tr class="text-center">
|
||||||
@ -146,7 +160,7 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
</tr>
|
</tr>
|
||||||
<tr class="text-center" id="processes-header-row">
|
<tr class="text-center" id="processes-header-row">
|
||||||
<?php foreach ($spotkania_cols as $index => $col): ?>
|
<?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>
|
<?= htmlspecialchars($col['group_name']) ?><br>
|
||||||
<small>
|
<small>
|
||||||
<?= $col['next_meeting_date'] ? date('d.m.Y', strtotime($col['next_meeting_date'])) : 'Brak' ?>
|
<?= $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>
|
</th>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php foreach ($inne_procesy_cols as $col): ?>
|
<?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; ?>
|
<?php endforeach; ?>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($people as $person): ?>
|
<?php foreach ($people as $person): ?>
|
||||||
<tr data-group-id="<?= $person['bni_group_id'] ?>">
|
<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">
|
<td class="person-cell">
|
||||||
<div class="person-main">
|
<div class="person-main">
|
||||||
<div class="person-name"><?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?></div>
|
<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.
|
// Display icon only if the person belongs to the group for that column.
|
||||||
if ($person['bni_group_id'] == $col['group_id']) {
|
if ($person['bni_group_id'] == $col['group_id']) {
|
||||||
$status = 'none'; // Default/placeholder status
|
$status = 'none'; // Default/placeholder status
|
||||||
$color = $status_colors[$status];
|
$color = $status_colors[$status] ?? '#808080';
|
||||||
echo "<span class=\"badge rounded-circle bg-$color\" style=\"width: 20px; height: 20px; display: inline-block;\" title=\"Status nieokreślony\"></span>";
|
echo "<span style='width: 20px; height: 20px; display: inline-block; border-radius: 50%; background-color: $color;' title='Status nieokreślony'></span>";
|
||||||
} else {
|
} else {
|
||||||
echo ''; // Empty cell if person is not in this group
|
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 // Inne Procesy Columns ?>
|
||||||
<?php foreach ($inne_procesy_cols as $process):
|
<?php foreach ($inne_procesy_cols as $process):
|
||||||
$instance = $instances[$person['id']][$process['id']] ?? null;
|
$instance = $instances[$person['id']][$process['id']] ?? null;
|
||||||
$status = $instance ? $instance['current_status'] : 'none';
|
$definition = isset($process['definition_json']) ? json_decode($process['definition_json'], true) : null;
|
||||||
$color = $status_colors[$status];
|
$isChecklist = ($definition && isset($definition['type']) && $definition['type'] === 'checklist');
|
||||||
$lastActivity = $instance && $instance['lastActivityAt'] ? date('d/m/y', strtotime($instance['lastActivityAt'])) : '';
|
$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'] ?>">
|
<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 class="badge rounded-circle bg-<?= $color ?>" style="width: 20px; height: 20px; display: inline-block;" title="<?= ucfirst($status) ?>"> </span>
|
<span title="<?= $title ?>" style="height: 20px; width: 20px; background-color: <?= $color ?>; border-radius: 50%; display: inline-block;"></span>
|
||||||
<small class="text-muted d-block"><?= $lastActivity ?></small>
|
<small class="text-muted d-block last-activity-date mt-1"><?= $lastActivity ?></small>
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="process-detail-view" style="display: none;">
|
||||||
|
<!-- This will be populated dynamically -->
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -523,30 +578,77 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
// --- Injected PHP Data ---
|
||||||
|
const peopleData = <?= json_encode($people) ?>;
|
||||||
|
|
||||||
// --- STATE MANAGEMENT ---
|
// --- STATE MANAGEMENT ---
|
||||||
const meetingsState = {};
|
const meetingsState = {};
|
||||||
|
|
||||||
// --- MODAL LOGIC ---
|
// --- MODAL LOGIC ---
|
||||||
const instanceModal = document.getElementById('instanceModal');
|
const instanceModal = document.getElementById('instanceModal');
|
||||||
|
let currentPersonId = null;
|
||||||
|
let currentProcessId = null;
|
||||||
|
|
||||||
if (instanceModal) {
|
if (instanceModal) {
|
||||||
|
// Event listener for when the modal is about to be shown
|
||||||
instanceModal.addEventListener('show.bs.modal', function (event) {
|
instanceModal.addEventListener('show.bs.modal', function (event) {
|
||||||
const button = event.relatedTarget;
|
const button = event.relatedTarget;
|
||||||
const personId = button.getAttribute('data-person-id');
|
currentPersonId = button.getAttribute('data-person-id');
|
||||||
const processId = button.getAttribute('data-process-id');
|
currentProcessId = button.getAttribute('data-process-id');
|
||||||
|
|
||||||
const modalBody = instanceModal.querySelector('.modal-body');
|
const modalBody = instanceModal.querySelector('.modal-body');
|
||||||
const modalTitle = instanceModal.querySelector('.modal-title');
|
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>';
|
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...';
|
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(response => response.text())
|
||||||
.then(html => {
|
.then(html => {
|
||||||
modalBody.innerHTML = html;
|
modalBody.innerHTML = html;
|
||||||
const newTitle = modalBody.querySelector('#instance-modal-title');
|
const newTitleEl = modalBody.querySelector('#instance-modal-title');
|
||||||
if (newTitle) {
|
if (newTitleEl) {
|
||||||
modalTitle.innerHTML = newTitle.innerHTML;
|
modalTitle.innerHTML = newTitleEl.innerHTML;
|
||||||
newTitle.remove();
|
newTitleEl.remove();
|
||||||
} else {
|
} else {
|
||||||
modalTitle.textContent = 'Szczegóły procesu';
|
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>';
|
modalBody.innerHTML = '<p class="text-danger">Wystąpił błąd podczas ładowania danych.</p>';
|
||||||
modalTitle.textContent = 'Błąd';
|
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 ---
|
// --- GROUP FILTER LOGIC ---
|
||||||
const groupFilter = document.getElementById('groupFilter');
|
const groupFilter = document.getElementById('groupFilter');
|
||||||
if (groupFilter) {
|
if (groupFilter) {
|
||||||
@ -578,6 +769,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
const expandBtn = event.target.closest('.expand-meeting');
|
const expandBtn = event.target.closest('.expand-meeting');
|
||||||
if (!expandBtn) return;
|
if (!expandBtn) return;
|
||||||
|
|
||||||
|
event.stopPropagation(); // Prevent the click from bubbling up to the process-header-clickable listener
|
||||||
|
|
||||||
const groupId = expandBtn.dataset.groupId;
|
const groupId = expandBtn.dataset.groupId;
|
||||||
expandBtn.style.display = 'none';
|
expandBtn.style.display = 'none';
|
||||||
|
|
||||||
@ -614,7 +807,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
const meetingDate = new Date(meeting.start_datetime);
|
const meetingDate = new Date(meeting.start_datetime);
|
||||||
const formattedDate = meetingDate.toLocaleDateString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
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 isLastInBatch = false;
|
||||||
const isLastInGroup = false;
|
const isLastInGroup = false;
|
||||||
|
|
||||||
@ -670,6 +862,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function findLastElementForGroup(parent, selector, groupId, findFirst = false) {
|
function findLastElementForGroup(parent, selector, groupId, findFirst = false) {
|
||||||
const elements = parent.querySelectorAll(`${selector}[data-group-id="${groupId}"]`);
|
const elements = parent.querySelectorAll(`${selector}[data-group-id="${groupId}"]`);
|
||||||
if (elements.length === 0) return null;
|
if (elements.length === 0) return null;
|
||||||
@ -678,14 +872,74 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
|
|
||||||
function htmlspecialchars(str) {
|
function htmlspecialchars(str) {
|
||||||
if (typeof str !== 'string') return '';
|
if (typeof str !== 'string') return '';
|
||||||
return str.replace(/[&<>"']/g, match => ({
|
return str.replace(/[&<>"]/g, match => ({
|
||||||
'&': '&',
|
'&': '&',
|
||||||
'<': '<',
|
'<': '<',
|
||||||
'>': '>',
|
'>': '>',
|
||||||
'"': '"',
|
'"': '"'
|
||||||
"'": '''
|
|
||||||
}[match]));
|
}[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}"> </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>
|
</script>
|
||||||
|
|
||||||
@ -700,7 +954,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form action="_bulk_update_status.php" method="post">
|
<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">
|
<div class="mb-3">
|
||||||
<label class="form-label">Process</label>
|
<label class="form-label">Process</label>
|
||||||
<select name="process_id" class="form-select" required>
|
<select name="process_id" class="form-select" required>
|
||||||
@ -735,7 +989,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form action="_bulk_add_event.php" method="post">
|
<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">
|
<div class="mb-3">
|
||||||
<label class="form-label">Process</label>
|
<label class="form-label">Process</label>
|
||||||
<select name="process_id" class="form-select" required>
|
<select name="process_id" class="form-select" required>
|
||||||
@ -765,7 +1019,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form action="_bulk_init_instances.php" method="post">
|
<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">
|
<div class="mb-3">
|
||||||
<label class="form-label">Process</label> <select name="process_id" class="form-select" required>
|
<label class="form-label">Process</label> <select name="process_id" class="form-select" required>
|
||||||
<?php foreach($processes as $process): ?>
|
<?php foreach($processes as $process): ?>
|
||||||
|
|||||||
54
test_workflow.php
Normal file
54
test_workflow.php
Normal 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>";
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user