diff --git a/WorkflowEngine.php b/WorkflowEngine.php index 3a541f9..a1c5e48 100644 --- a/WorkflowEngine.php +++ b/WorkflowEngine.php @@ -63,7 +63,9 @@ class WorkflowEngine { $definitions = []; $definition_map = []; + $codeToIdMap = []; foreach ($process_definitions_raw as $def) { + $codeToIdMap[$def['code']] = $def['id']; $definitions[$def['id']] = [ 'id' => $def['id'], 'code' => $def['code'], @@ -78,7 +80,7 @@ class WorkflowEngine { $person_ids = array_column($people, 'id'); if (!empty($person_ids)) { $placeholders = implode(',', array_fill(0, count($person_ids), '?')); - $stmt_instances = $this->pdo->prepare("SELECT * FROM process_instances WHERE person_id IN ($placeholders)"); + $stmt_instances = $this->pdo->prepare("SELECT pi.*, pd.code as process_code, pd.definition_json as pd_definition_json FROM process_instances pi JOIN process_definitions pd ON pi.process_definition_id = pd.id WHERE pi.person_id IN ($placeholders) ORDER BY pi.last_activity_at DESC"); $stmt_instances->execute($person_ids); $instances_data = $stmt_instances->fetchAll(PDO::FETCH_ASSOC); @@ -87,7 +89,7 @@ class WorkflowEngine { $def_id = $instance['process_definition_id']; $node_id = $instance['current_node_id']; - $definition = $definition_map[$def_id] ?? null; + $definition = !empty($instance['pd_definition_json']) ? json_decode($instance['pd_definition_json'], true) : ($definition_map[$def_id] ?? null); if ($definition && isset($definition['type']) && $definition['type'] === 'checklist') { $tasks = $definition['tasks'] ?? []; diff --git a/_get_instance_details.php b/_get_instance_details.php index 68eed9c..cf8d738 100644 --- a/_get_instance_details.php +++ b/_get_instance_details.php @@ -15,6 +15,15 @@ if (!isset($_SESSION['user_id'])) { $person_id = $_GET['person_id'] ?? null; $process_definition_id = $_GET['process_id'] ?? null; +$process_code = $_GET['process_code'] ?? null; + +$pdo = db(); + +if (!$process_definition_id && $process_code) { + $stmt = $pdo->prepare("SELECT id FROM process_definitions WHERE code = ? AND is_latest = 1 LIMIT 1"); + $stmt->execute([$process_code]); + $process_definition_id = $stmt->fetchColumn(); +} if (!$person_id || !$process_definition_id) { http_response_code(400); @@ -24,7 +33,6 @@ if (!$person_id || !$process_definition_id) { $userId = $_SESSION['user_id']; $engine = new WorkflowEngine(); -$pdo = db(); // Fetch Person and Process Definition details first $stmt_person = $pdo->prepare("SELECT first_name, last_name FROM people WHERE id = ?"); diff --git a/_init_single_instance.php b/_init_single_instance.php index 3305205..ba9e914 100644 --- a/_init_single_instance.php +++ b/_init_single_instance.php @@ -20,11 +20,20 @@ if (!isset($_SESSION['user_id'])) { $userId = $_SESSION['user_id']; $personId = filter_input(INPUT_POST, 'person_id', FILTER_VALIDATE_INT); $processDefinitionId = filter_input(INPUT_POST, 'process_id', FILTER_VALIDATE_INT); +$processCode = filter_input(INPUT_POST, 'process_code', FILTER_SANITIZE_STRING); $deleteExisting = filter_input(INPUT_POST, 'delete_existing'); +$mode = filter_input(INPUT_POST, 'mode', FILTER_SANITIZE_STRING); + +$pdo = db(); + +if (!$processDefinitionId && $processCode) { + $stmt = $pdo->prepare("SELECT id FROM process_definitions WHERE code = ? AND is_latest = 1 LIMIT 1"); + $stmt->execute([$processCode]); + $processDefinitionId = $stmt->fetchColumn(); +} if (!$personId || !$processDefinitionId) { - // InvalidArgumentException will be caught by the handler and result in a 400 Bad Request - throw new InvalidArgumentException('Invalid or missing person_id or process_id.'); + throw new InvalidArgumentException('Invalid or missing person_id or process_id/process_code.'); } $engine = new WorkflowEngine(); @@ -36,22 +45,14 @@ if($deleteExisting === '1') { } } -// The getOrCreateInstanceByDefId method is now responsible for all checks: -// 1. Validating the process definition exists. -// 2. Checking if the process is active. -// 3. Checking if the person is eligible. -// 4. Creating the instance if it doesn't exist. -// It will throw specific exceptions (WorkflowNotFoundException, WorkflowNotAllowedException, WorkflowEligibilityException) which our ErrorHandler will turn into 404, 409, and 422 responses. - = filter_input(INPUT_POST, 'force', FILTER_VALIDATE_INT); -if ( === 1) { - = ->createNewInstance(, , ); +if ($mode === 'create_new_run' || filter_input(INPUT_POST, 'force', FILTER_VALIDATE_INT) === 1) { + $instance = $engine->createNewInstance($personId, $processDefinitionId, $userId); } else { - = ->getOrCreateInstanceByDefId(, , ); + $instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId); } if ($instance) { echo json_encode(['success' => true, 'message' => 'Process initialized successfully.', 'instance_id' => $instance['id']]); } else { - // This case should not be reached if the engine works as expected, as failures should throw exceptions. throw new Exception("Failed to initialize process for an unknown reason."); -} \ No newline at end of file +} diff --git a/cookie.txt b/cookie.txt index abe37fb..7a0e70d 100644 --- a/cookie.txt +++ b/cookie.txt @@ -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 rfo6k0p8l4tpnmgek7dkpkopbl +127.0.0.1 FALSE / FALSE 0 PHPSESSID np7q4kbboaogr1lb2gg3753mdu diff --git a/index.php b/index.php index 4081cff..5500967 100644 --- a/index.php +++ b/index.php @@ -1245,6 +1245,76 @@ document.addEventListener('DOMContentLoaded', function () { // --- Actions inside Instance Modal --- if (instanceModalElement) { instanceModalElement.addEventListener('click', function(event) { + const restartBtn = event.target.closest('#restartProcessBtn'); + if (restartBtn) { + event.preventDefault(); + event.stopPropagation(); + + const personId = restartBtn.dataset.personId; + const processCode = restartBtn.dataset.processCode; + const mode = restartBtn.dataset.mode; + + if (!personId || !processCode) { + alert('Brak ID osoby lub kodu procesu'); + return; + } + + const originalText = restartBtn.innerHTML; + restartBtn.disabled = true; + restartBtn.innerHTML = '...'; + + const formData = new FormData(); + formData.append('person_id', personId); + formData.append('process_code', processCode); + formData.append('mode', mode || 'create_new_run'); + + fetch('_init_single_instance.php', { + method: 'POST', + body: formData + }) + .then(response => { + if (!response.ok) { + return response.text().then(text => { + throw new Error(`HTTP ${response.status}: ${text.substring(0, 100)}`); + }); + } + return response.json(); + }) + .then(data => { + if (data.success) { + window.matrixNeedsRefresh = true; + const modalBody = instanceModalElement.querySelector('.modal-body'); + modalBody.innerHTML = '

Ładowanie...

'; + + // Use process_id if we have it from dataset, otherwise fetch using code. + // The original _get_instance_details endpoint seems to accept process_id or process_code? Let's check. + // Actually let's use the instanceModalElement.dataset.lastProcessId if it exists. + const fetchUrl = `_get_instance_details.php?person_id=${personId}&process_code=${processCode}`; + + fetch(fetchUrl) + .then(r => r.text()) + .then(html => { + modalBody.innerHTML = html; + }); + } else { + let errMsg = data.error?.message || data.error || 'Nieoczekiwany błąd'; + if (data.correlation_id) { + errMsg += ' (ID: ' + data.correlation_id + ')'; + } + alert('Błąd: ' + errMsg); + restartBtn.disabled = false; + restartBtn.innerHTML = originalText; + } + }) + .catch(err => { + console.error(err); + alert('Błąd: ' + err.message); + restartBtn.disabled = false; + restartBtn.innerHTML = originalText; + }); + return; + } + // Apply Transition Button if (event.target.classList.contains('apply-transition-btn')) { event.preventDefault(); diff --git a/patch_index.py b/patch_index.py deleted file mode 100644 index 816506f..0000000 --- a/patch_index.py +++ /dev/null @@ -1,22 +0,0 @@ -with open('index.php', 'r', encoding='utf-8') as f: - content = f.read() - -start_str = " const instanceModalElement = document.getElementById('instanceModal');\n if (instanceModalElement) {\n instanceModalElement.addEventListener('click', function(event) {\n const btn = event.target.closest('#startProcessBtn');\n if (!btn) return;" -end_str = " });\n }" - -start_idx = content.find(start_str) -if start_idx != -1: - end_idx = content.find(end_str, start_idx) - if end_idx != -1: - end_idx += len(end_str) - - replacement = " const instanceModalElement = document.getElementById('instanceModal');\n if (instanceModalElement) {\n instanceModalElement.addEventListener('click', function(event) {\n const startBtn = event.target.closest('#startProcessBtn');\n const restartBtn = event.target.closest('#restartProcessBtn');\n const btn = startBtn || restartBtn;\n if (!btn) return;\n \n event.preventDefault();\n event.stopPropagation();\n \n const isRestart = !!restartBtn;\n \n const personId = btn.dataset.personId;\n const processId = btn.dataset.processId;\n const processCode = btn.dataset.processCode;\n \n if (!personId) {\n alert('Brak ID osoby');\n return;\n }\n if (isRestart && !processCode) {\n alert('Brak kodu procesu');\n return;\n }\n if (!isRestart && !processId) {\n alert('Brak ID procesu');\n return;\n }\n \n const originalText = btn.innerHTML;\n btn.disabled = true;\n btn.innerHTML = '...';\n \n const formData = new FormData();\n formData.append('person_id', personId);\n \n if (isRestart) {\n formData.append('process_code', processCode);\n formData.append('mode', 'create_new_run');\n } else {\n formData.append('process_id', processId);\n }\n \n if (instanceModalElement && processId) {\n instanceModalElement.dataset.lastPersonId = personId;\n instanceModalElement.dataset.lastProcessId = processId;\n }\n \n fetch('_init_single_instance.php', {\n method: 'POST',\n body: formData\n })\n .then(async response => {\n let data;\n try {\n data = await response.json();\n } catch (e) {\n throw new Error(`Błąd serwera (HTTP ${response.status}): Nieoczekiwany błąd (invalid JSON)`);\n }\n \n if (!response.ok || !data.success) {\n let errMsg = data?.error?.message || data?.error || 'Nieznany błąd';\n if (data?.correlation_id) {\n errMsg += ` (ID błędu: ${data.correlation_id})`;\n }\n throw new Error(errMsg);\n }\n return data;\n })\n .then(data => {\n const modalBody = instanceModalElement.querySelector('.modal-body');\n modalBody.innerHTML = '

Ładowanie...

';\n \n const refreshProcessId = data.process_definition_id || processId || instanceModalElement.dataset.lastProcessId;\n if (refreshProcessId) {\n instanceModalElement.dataset.lastProcessId = refreshProcessId;\n }\n \n fetch(`_get_instance_details.php?person_id=${personId}&process_id=${refreshProcessId}`)\n .then(r => r.text())\n .then(html => {\n modalBody.innerHTML = html;\n window.matrixNeedsRefresh = true;\n });\n })\n .catch(err => {\n console.error(err);\n alert(err.message || 'Błąd sieci');\n btn.disabled = false;\n btn.innerHTML = originalText;\n });\n });\n }" - - new_content = content[:start_idx] + replacement + content[end_idx:] - with open('index.php', 'w', encoding='utf-8') as f: - f.write(new_content) - print("Replaced index.php block successfully.") - else: - print("End string not found.") -else: - print("Start string not found.")