From 6460ff3ac85ea8c4ba54b6634c961b0afa6d151d Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 10 Jan 2026 22:04:53 +0000 Subject: [PATCH] =?UTF-8?q?Kolejna=20iteracja=20proces=C3=B3w=20w=20system?= =?UTF-8?q?ie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WorkflowEngine.php | 198 +++++++++++++---- _create_person.php | 210 +++++++++--------- _get_instance_details.php | 17 ++ _get_person_details.php | 26 ++- _update_person.php | 6 + assets/js/main.js | 93 +++++++- ...2_add_is_active_to_process_definitions.php | 24 +- db/migrations/023_add_new_member_process.php | 22 ++ .../024_update_follow_up_process.php | 154 +++++++++++++ ...25_update_follow_up_process_structured.php | 161 ++++++++++++++ index.php | 22 +- 11 files changed, 768 insertions(+), 165 deletions(-) create mode 100644 db/migrations/023_add_new_member_process.php create mode 100644 db/migrations/024_update_follow_up_process.php create mode 100644 db/migrations/025_update_follow_up_process_structured.php diff --git a/WorkflowEngine.php b/WorkflowEngine.php index 256de23..7e5af76 100644 --- a/WorkflowEngine.php +++ b/WorkflowEngine.php @@ -217,6 +217,8 @@ class WorkflowEngine { $transition = null; foreach ($definition['transitions'] as $t) { if ($t['from'] === $currentNodeId && $t['id'] === $transitionId) { + // For user-triggered transitions, we ignore the condition here. + // The UI should prevent showing buttons for transitions whose data-based conditions aren't met. $transition = $t; break; } @@ -226,56 +228,96 @@ class WorkflowEngine { throw new WorkflowNotAllowedException("Transition not found or not allowed from the current node."); } - // TODO: Add rule validation here + // Apply the initial, user-triggered transition + $newNodeId = $this->applySingleTransition($instanceId, $instance, $definition, $transition, $inputPayload, $userId); + $instance['current_node_id'] = $newNodeId; // Update instance state for the loop - $newNodeId = $transition['to']; - $newNodeInfo = $definition['nodes'][$newNodeId] ?? null; - - $newStatus = $newNodeInfo['ui_hints']['status'] ?? 'in_progress'; - $newReason = $newNodeInfo['ui_hints']['reason'] ?? ''; - $newNextStep = $newNodeInfo['ui_hints']['next_step'] ?? ''; - - $stmt_update = $this->pdo->prepare( - "UPDATE process_instances SET current_node_id = ?, current_status = ?, current_reason = ?, suggested_next_step = ?, last_activity_at = NOW() WHERE id = ?" - ); - $stmt_update->execute([ - $newNodeId, - $newStatus, - $newReason, - $newNextStep, - $instanceId - ]); - - $message = $inputPayload['message'] ?? $transition['name']; - $this->addEvent($instanceId, 'transition_applied', $message, $newNodeId, $inputPayload, $userId); - - if (isset($transition['actions'])) { - foreach ($transition['actions'] as $action) { - if ($action['type'] === 'start_process') { - $this->executeStartProcessAction($instance['person_id'], $action, $userId); - } elseif ($action['type'] === 'set_data') { - $this->executeSetDataAction($instanceId, $action); - } + // Loop for automatic transitions (router nodes) + for ($i = 0; $i < 10; $i++) { // Max 10 auto-steps to prevent infinite loops + $autoTransition = $this->findAutomaticTransition($instance, $definition); + if ($autoTransition) { + // Automatic transitions have no user payload + $newNodeId = $this->applySingleTransition($instanceId, $instance, $definition, $autoTransition, [], $userId); + $instance['current_node_id'] = $newNodeId; // Update for next iteration + } else { + break; // No more automatic transitions found } } - + $this->pdo->commit(); + // Refetch the final state of the instance to return the correct status + $finalState = $this->getProcessState($instanceId)['instance']; + $finalNodeInfo = $definition['nodes'][$finalState['current_node_id']] ?? null; + return [ 'instanceId' => $instanceId, - 'currentNodeId' => $newNodeId, - 'currentStatus' => $newStatus, - 'currentReason' => $newReason, - 'suggestedNextStep' => $newNextStep, - 'lastActivityAt' => date('Y-m-d H:i:s'), + 'currentNodeId' => $finalState['current_node_id'], + 'currentStatus' => $finalNodeInfo['ui_hints']['status'] ?? $finalState['current_status'], + 'currentReason' => $finalNodeInfo['ui_hints']['reason'] ?? $finalState['current_reason'], + 'suggestedNextStep' => $finalNodeInfo['ui_hints']['next_step'] ?? $finalState['suggested_next_step'], + 'lastActivityAt' => $finalState['last_activity_at'], ]; + } catch (Exception $e) { $this->pdo->rollBack(); - // Re-throw the original exception to be handled by the global error handler throw $e; } } + private function applySingleTransition(int $instanceId, array &$instance, array $definition, array $transition, array $inputPayload, int $userId): string + { + $newNodeId = $transition['to']; + $newNodeInfo = $definition['nodes'][$newNodeId] ?? null; + + $newStatus = $newNodeInfo['ui_hints']['status'] ?? 'in_progress'; + $newReason = $newNodeInfo['ui_hints']['reason'] ?? ''; + $newNextStep = $newNodeInfo['ui_hints']['next_step'] ?? ''; + + $stmt_update = $this->pdo->prepare( + "UPDATE process_instances SET current_node_id = ?, current_status = ?, current_reason = ?, suggested_next_step = ?, last_activity_at = NOW() WHERE id = ?" + ); + $stmt_update->execute([ + $newNodeId, + $newStatus, + $newReason, + $newNextStep, + $instanceId + ]); + + $message = $inputPayload['message'] ?? $transition['name']; + $this->addEvent($instanceId, 'transition_applied', $message, $newNodeId, $inputPayload, $userId); + + if (isset($transition['actions'])) { + foreach ($transition['actions'] as $action) { + if ($action['type'] === 'start_process') { + $this->executeStartProcessAction($instance['person_id'], $action, $userId); + } elseif ($action['type'] === 'set_data') { + // Pass the instance by reference to be updated with new data + $this->executeSetDataAction($instanceId, $instance, $action, $inputPayload); + } + } + } + + return $newNodeId; + } + + private function findAutomaticTransition(array $instance, array $definition): ?array + { + $currentNodeId = $instance['current_node_id']; + foreach ($definition['transitions'] as $transition) { + if ($transition['from'] === $currentNodeId) { + // An automatic transition MUST have a condition. + if (isset($transition['condition'])) { + if ($this->checkTransitionCondition($transition, $instance)) { + return $transition; + } + } + } + } + return null; + } + public function addNote(int $instanceId, string $message, int $userId): bool { $state = $this->getProcessState($instanceId); if (!$state) { @@ -404,7 +446,7 @@ class WorkflowEngine { $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 $context = []): ?array { if (!is_int($processDefinitionId) || $processDefinitionId <= 0) { throw new InvalidArgumentException("processDefinitionId must be a positive integer."); } @@ -429,7 +471,7 @@ class WorkflowEngine { throw new WorkflowNotAllowedException("Process is not active and cannot be started."); } - $eligibility = $this->checkEligibility($personId, $processDefinitionId); + $eligibility = $this->checkEligibility($personId, $processDefinitionId, $context); if (!$eligibility['is_eligible']) { throw new WorkflowEligibilityException("Person is not eligible to start this process.", $eligibility['reasons']); } @@ -499,7 +541,7 @@ class WorkflowEngine { return $definition['nodes'] ?? []; } - public function checkEligibility(int $personId, int $processDefinitionId): array { + public function checkEligibility(int $personId, int $processDefinitionId, array $context = []): array { $stmt_def = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?"); $stmt_def->execute([$processDefinitionId]); $definition_json = $stmt_def->fetchColumn(); @@ -522,6 +564,12 @@ class WorkflowEngine { case 'checkProcessDataRule': $this->checkProcessDataRule($personId, $params); break; + case 'deny_manual_start': + $this->checkDenyManualStartRule($context); + break; + case 'person_property_equals': + $this->checkPersonPropertyEqualsRule($personId, $params); + break; // Add other rule types here } } catch (WorkflowNotAllowedException $e) { @@ -532,6 +580,33 @@ class WorkflowEngine { return ['is_eligible' => empty($reasons), 'reasons' => $reasons]; } + private function checkDenyManualStartRule(array $context): void { + if (!isset($context['source']) || $context['source'] !== 'chain') { + throw new WorkflowNotAllowedException("This process can only be started automatically by another process."); + } + } + + private function checkPersonPropertyEqualsRule(int $personId, array $params): void { + $stmt = $this->pdo->prepare("SELECT * FROM people WHERE id = ?"); + $stmt->execute([$personId]); + $person = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$person) { + throw new WorkflowNotAllowedException("Person not found."); + } + + $property = $params['property']; + $expectedValue = $params['value']; + + if (!isset($person[$property])) { + throw new WorkflowNotAllowedException("Property '{$property}' not found on person."); + } + + if ($person[$property] !== $expectedValue) { + throw new WorkflowNotAllowedException("Person's property '{$property}' is not '{$expectedValue}'."); + } + } + private function checkProcessCompletedRule(int $personId, array $params): void { $stmt = $this->pdo->prepare("\n SELECT pi.id\n FROM process_instances pi\n JOIN process_definitions pd ON pi.process_definition_id = pd.id\n WHERE pi.person_id = ? AND pd.code = ? AND pi.current_status = ?\n ORDER BY pi.last_activity_at DESC\n LIMIT 1\n "); $stmt->execute([$personId, $params['process_code'], $params['expected_status']]); @@ -570,30 +645,57 @@ class WorkflowEngine { } } + private function checkTransitionCondition(array $transition, array $instanceData): bool + { + if (!isset($transition['condition'])) { + // A transition without a condition is not automatic, but it is valid to pass through. + // The calling context (findAutomaticTransition) will decide if this is an error. + return true; + } + + $condition = $transition['condition']; + $data = isset($instanceData['data_json']) ? json_decode($instanceData['data_json'], true) : []; + + $field = $condition['field'] ?? null; + $expectedValue = $condition['value'] ?? null; + + if ($field === null || $expectedValue === null) { + // Malformed condition + return false; + } + + return isset($data[$field]) && $data[$field] === $expectedValue; + } + private function executeStartProcessAction(int $personId, array $action, int $userId): void { $stmt = $this->pdo->prepare("SELECT id FROM process_definitions WHERE code = ?"); $stmt->execute([$action['process_code']]); $processDefinitionId = $stmt->fetchColumn(); if ($processDefinitionId) { - $this->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId); + $this->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId, ['source' => 'chain']); } } - private function executeSetDataAction(int $instanceId, array $action): void { - $stmt = $this->pdo->prepare("SELECT data_json FROM process_instances WHERE id = ?"); - $stmt->execute([$instanceId]); - $dataJson = $stmt->fetchColumn(); - + private function executeSetDataAction(int $instanceId, array &$instance, array $action, array $payload): void { + $dataJson = $instance['data_json']; $data = $dataJson ? json_decode($dataJson, true) : []; - $key = $action['params']['key']; - $value = $action['params']['value']; - $data[$key] = $value; + if (isset($action['params']['keys']) && is_array($action['params']['keys'])) { + foreach ($action['params']['keys'] as $key) { + if (array_key_exists($key, $payload)) { + $data[$key] = $payload[$key]; + } + } + } $newDataJson = json_encode($data); + // Update the database $stmt_update = $this->pdo->prepare("UPDATE process_instances SET data_json = ? WHERE id = ?"); $stmt_update->execute([$newDataJson, $instanceId]); + + // Also update the in-memory instance for the next step in the chain + $instance['data_json'] = $newDataJson; } } \ No newline at end of file diff --git a/_create_person.php b/_create_person.php index df36c3b..821ff8b 100644 --- a/_create_person.php +++ b/_create_person.php @@ -1,117 +1,119 @@ ['message' => 'Method not allowed.'], 'correlation_id' => uniqid()]); + exit; +} - if (empty($firstName) || empty($lastName) || empty($email) || empty($password)) { - $_SESSION['error_message'] = 'Imię, nazwisko, email i hasło są wymagane.'; - header('Location: index.php'); - exit; +$first_name = $_POST['first_name'] ?? ''; +$last_name = $_POST['last_name'] ?? ''; +$email = $_POST['email'] ?? ''; +$password = $_POST['password'] ?? ''; +$company_name = $_POST['company_name'] ?? null; +$phone = $_POST['phone'] ?? null; +$role = $_POST['role'] ?? 'guest'; +$functions = isset($_POST['functions']) ? (array)$_POST['functions'] : []; +$bni_group_id = isset($_POST['bni_group_id']) && !empty($_POST['bni_group_id']) ? $_POST['bni_group_id'] : null; + +$nip = $_POST['nip'] ?? null; +$industry = $_POST['industry'] ?? null; +$company_size_revenue = $_POST['company_size_revenue'] ?? null; +$business_description = $_POST['business_description'] ?? null; + +if (empty($first_name) || empty($last_name) || empty($email) || empty($password)) { + http_response_code(422); + echo json_encode(['error' => ['message' => 'First name, last name, email, and password are required.'], 'correlation_id' => uniqid()]); + exit; +} + +if ($role !== 'member') { + $bni_group_id = null; +} + +$pdo = db(); +try { + $pdo->beginTransaction(); + + $sql = 'INSERT INTO people (first_name, last_name, email, password, company_name, phone, role, bni_group_id, nip, industry, company_size_revenue, business_description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$first_name, $last_name, $email, password_hash($password, PASSWORD_DEFAULT), $company_name, $phone, $role, $bni_group_id, $nip, $industry, $company_size_revenue, $business_description]); + $personId = $pdo->lastInsertId(); + + $upload_dir = 'uploads/people/' . $personId . '/'; + if (!is_dir($upload_dir)) { + if (!mkdir($upload_dir, 0777, true) && !is_dir($upload_dir)) { + throw new RuntimeException(sprintf('Directory "%s" was not created', $upload_dir)); + } } - // Only members can be in a group - if ($role !== 'member') { - $bni_group_id = null; + $file_fields = [ + 'company_logo' => 'company_logo_path', + 'person_photo' => 'person_photo_path', + 'gains_sheet' => 'gains_sheet_path', + 'top_wanted_contacts' => 'top_wanted_contacts_path', + 'top_owned_contacts' => 'top_owned_contacts_path' + ]; + $file_paths_to_update = []; + + foreach ($file_fields as $form_field_name => $db_column_name) { + if (isset($_FILES[$form_field_name]) && $_FILES[$form_field_name]['error'] == UPLOAD_ERR_OK) { + $tmp_name = $_FILES[$form_field_name]['tmp_name']; + $original_name = basename($_FILES[$form_field_name]['name']); + $file_ext = pathinfo($original_name, PATHINFO_EXTENSION); + $new_filename = uniqid($form_field_name . '_', true) . '.' . $file_ext; + $destination = $upload_dir . $new_filename; + + if (move_uploaded_file($tmp_name, $destination)) { + $file_paths_to_update[$db_column_name] = $destination; + } else { + throw new RuntimeException("Failed to move uploaded file for {$form_field_name}."); + } + } } - $pdo = db(); - try { - $pdo->beginTransaction(); - - // Insert person details first - $sql = 'INSERT INTO people (first_name, last_name, email, password, company_name, phone, role, bni_group_id, nip, industry, company_size_revenue, business_description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + if (!empty($file_paths_to_update)) { + $sql_parts = []; + $params = []; + foreach ($file_paths_to_update as $column => $path) { + $sql_parts[] = "$column = ?"; + $params[] = $path; + } + $params[] = $personId; + $sql = "UPDATE people SET " . implode(', ', $sql_parts) . " WHERE id = ?"; $stmt = $pdo->prepare($sql); - $stmt->execute([$first_name, $last_name, $email, password_hash($password, PASSWORD_DEFAULT), $company_name, $phone, $role, $bni_group_id, $nip, $industry, $company_size_revenue, $business_description]); - $personId = $pdo->lastInsertId(); - - // Handle file uploads now that we have a personId - $upload_dir = 'uploads/people/' . $personId . '/'; - if (!is_dir($upload_dir)) { - mkdir($upload_dir, 0777, true); - } - - $file_fields = [ - 'company_logo' => 'company_logo_path', - 'person_photo' => 'person_photo_path', - 'gains_sheet' => 'gains_sheet_path', - 'top_wanted_contacts' => 'top_wanted_contacts_path', - 'top_owned_contacts' => 'top_owned_contacts_path' - ]; - - $file_paths_to_update = []; - - foreach ($file_fields as $form_field_name => $db_column_name) { - if (isset($_FILES[$form_field_name]) && $_FILES[$form_field_name]['error'] == UPLOAD_ERR_OK) { - $tmp_name = $_FILES[$form_field_name]['tmp_name']; - $original_name = basename($_FILES[$form_field_name]['name']); - $file_ext = pathinfo($original_name, PATHINFO_EXTENSION); - $new_filename = uniqid($form_field_name . '_', true) . '.' . $file_ext; - $destination = $upload_dir . $new_filename; - - if (move_uploaded_file($tmp_name, $destination)) { - $file_paths_to_update[$db_column_name] = $destination; - } - } - } - - // If there are files, update the newly created person record - if (!empty($file_paths_to_update)) { - $sql_parts = []; - $params = []; - foreach ($file_paths_to_update as $column => $path) { - $sql_parts[] = "$column = ?"; - $params[] = $path; - } - $params[] = $personId; - $sql = "UPDATE people SET " . implode(', ', $sql_parts) . " WHERE id = ?"; - $stmt = $pdo->prepare($sql); - $stmt->execute($params); - } - - // Assign functions - if (!empty($functions)) { - $sql = "INSERT INTO user_functions (user_id, function_id) VALUES (?, ?)"; - $stmt = $pdo->prepare($sql); - foreach ($functions as $functionId) { - $stmt->execute([$personId, $functionId]); - } - } - - $pdo->commit(); - $_SESSION['success_message'] = 'Osoba dodana pomyślnie.'; - - } catch (PDOException $e) { - $pdo->rollBack(); - error_log('Create failed: ' . $e->getMessage()); - if ($e->errorInfo[1] == 1062) { - $_SESSION['error_message'] = 'Błąd: Konto z tym adresem email już istnieje.'; - } else { - $_SESSION['error_message'] = 'Błąd podczas dodawania osoby: ' . $e->getMessage(); - } - } catch (Exception $e) { - if ($pdo->inTransaction()) { - $pdo->rollBack(); - } - error_log('File upload or other error: ' . $e->getMessage()); - $_SESSION['error_message'] = 'Błąd: ' . $e->getMessage(); + $stmt->execute($params); } - header('Location: index.php'); - exit(); + if (!empty($functions)) { + $sql = "INSERT INTO user_functions (user_id, function_id) VALUES (?, ?)"; + $stmt = $pdo->prepare($sql); + foreach ($functions as $functionId) { + $stmt->execute([$personId, $functionId]); + } + } + + $pdo->commit(); + + echo json_encode(['success' => true, 'person_id' => $personId, 'message' => 'Person created successfully.']); + +} catch (PDOException $e) { + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } + if ($e->errorInfo[1] == 1062) { + http_response_code(409); // Conflict + echo json_encode(['error' => ['message' => 'An account with this email address already exists.'], 'correlation_id' => uniqid()]); + } else { + throw $e; // Re-throw to be caught by the global error handler + } +} catch (Exception $e) { + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } + throw $e; // Re-throw to be caught by the global error handler } \ No newline at end of file diff --git a/_get_instance_details.php b/_get_instance_details.php index 07a6dec..965bdd2 100644 --- a/_get_instance_details.php +++ b/_get_instance_details.php @@ -129,6 +129,23 @@ $instance = $engine->getInstanceByDefId($person_id, $process_definition_id);
Available Actions
+ +
+ +
+ + + + + + +
+ +
+

No actions available.

diff --git a/_get_person_details.php b/_get_person_details.php index f3bd925..696f18d 100644 --- a/_get_person_details.php +++ b/_get_person_details.php @@ -26,10 +26,34 @@ if (isset($_GET['id'])) { $stmt->execute([$person_id]); $person_functions = $stmt->fetchAll(PDO::FETCH_COLUMN, 0); + // --- Fetch Follow-up Process Summary --- + $follow_up_summary = null; + $stmt_def = $pdo->prepare("SELECT id FROM process_definitions WHERE code = 'guest_handling' LIMIT 1"); + $stmt_def->execute(); + $follow_up_def_id = $stmt_def->fetchColumn(); + + if ($follow_up_def_id) { + $stmt_inst = $pdo->prepare("SELECT * FROM process_instances WHERE person_id = ? AND process_definition_id = ? ORDER BY id DESC LIMIT 1"); + $stmt_inst->execute([$person_id, $follow_up_def_id]); + $instance = $stmt_inst->fetch(PDO::FETCH_ASSOC); + + if ($instance) { + $data = $instance['data_json'] ? json_decode($instance['data_json'], true) : []; + $follow_up_summary = [ + 'last_call_outcome' => $data['outcome_status'] ?? null, + 'last_call_date' => $data['call_date'] ?? null, + 'next_contact_date' => $data['next_contact_date'] ?? null, + 'final_outcome' => $instance['current_status'], // e.g., completed, terminated + 'reason' => $instance['current_reason'] + ]; + } + } + $response = [ 'person' => $person, 'all_functions' => $all_functions, - 'person_functions' => $person_functions + 'person_functions' => $person_functions, + 'follow_up_summary' => $follow_up_summary ]; echo json_encode($response); diff --git a/_update_person.php b/_update_person.php index a8b7a3a..ec39c8a 100644 --- a/_update_person.php +++ b/_update_person.php @@ -20,6 +20,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $company_size_revenue = $_POST['company_size_revenue'] ?? null; $business_description = $_POST['business_description'] ?? null; + if (empty($first_name) || empty($last_name) || empty($email)) { + http_response_code(422); + echo json_encode(['error' => ['message' => 'First name, last name, and email are required.'], 'correlation_id' => uniqid()]); + exit; + } + // Only members can be in a group if ($role !== 'member') { $bni_group_id = null; diff --git a/assets/js/main.js b/assets/js/main.js index 9011386..35ba94f 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -22,6 +22,7 @@ $(document).ready(function() { modal.find('form').trigger('reset'); modal.find('#editPersonId').val(''); modal.find('#editRoles').empty(); + modal.find('#followUpSummaryContainer').empty(); // Clear summary container // Clear file paths modal.find('#editCompanyLogoPath, #editPersonPhotoPath, #editGainsSheetPath, #editTopWantedPath, #editTopOwnedPath').text(''); @@ -41,21 +42,47 @@ $(document).ready(function() { var person = response.person; var all_functions = response.all_functions; var person_functions = response.person_functions; + var followUpSummary = response.follow_up_summary; if (!person) { alert('Could not find person data.'); return; } + // Populate the Follow-up Summary + var summaryContainer = modal.find('#followUpSummaryContainer'); + if (followUpSummary) { + let summaryHtml = '
Follow-up Process Summary
'; + summaryHtml += '
'; + + if (followUpSummary.last_call_outcome) { + summaryHtml += `
Last Call Outcome
${followUpSummary.last_call_outcome.replace(/_/g, ' ')}
`; + } + if (followUpSummary.last_call_date) { + summaryHtml += `
Last Call Date
${new Date(followUpSummary.last_call_date).toLocaleString()}
`; + } + if (followUpSummary.next_contact_date) { + summaryHtml += `
Next Contact Date
${new Date(followUpSummary.next_contact_date).toLocaleString()}
`; + } + if (followUpSummary.final_outcome) { + summaryHtml += `
Final Status
${followUpSummary.final_outcome} (${followUpSummary.reason || 'N/A'})
`; + } + + summaryHtml += '
'; + summaryContainer.html(summaryHtml); + } else { + summaryContainer.html('

No Follow-up process data found for this person.

'); + } + // Populate the form fields modal.find('#editPersonId').val(person.id); - modal.find('#editFirstName').val(person.firstName); - modal.find('#editLastName').val(person.lastName); + modal.find('#editFirstName').val(person.first_name); + modal.find('#editLastName').val(person.last_name); 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('#editCompanyName').val(person.company_name); modal.find('#editNip').val(person.nip); modal.find('#editIndustry').val(person.industry); modal.find('#editCompanySize').val(person.company_size_revenue); @@ -157,4 +184,64 @@ $(document).ready(function() { $('#createPersonModal').on('show.bs.modal', function () { $('#createRole').trigger('change'); }); + + function handleFormSubmit(form, errorContainer, successCallback) { + event.preventDefault(); + const formData = new FormData(form); + const errorDiv = $(errorContainer).hide(); + + fetch(form.action, { + method: 'POST', + body: formData + }) + .then(response => { + // Clone the response so we can read it twice (once as JSON, once as text if needed) + const responseClone = response.clone(); + return response.json() + .then(data => ({ status: response.status, ok: response.ok, body: data })) + .catch(() => responseClone.text().then(text => ({ status: response.status, ok: response.ok, body: text, isText: true }))); + }) + .then(res => { + const { status, ok, body, isText } = res; + + if (!ok) { + if (isText) { + throw new Error(`Server Error: ${status}. Response: ${body}`); + } + throw new Error(body.error?.message || `An unknown server error occurred (Status: ${status})`); + } + + if (isText) { + console.error("Received non-JSON response:", body); + throw new Error("The server sent an invalid response that could not be parsed. See console for details."); + } + + if (body.success) { + if (successCallback) { + successCallback(body); + } else { + // Default success behavior: close modal and reload + $(form).closest('.modal').modal('hide'); + window.location.reload(); + } + } else { + throw new Error(body.error?.message || 'An operation error occurred.'); + } + }) + .catch(error => { + errorDiv.text(error.message).show(); + }); + } + + $('#createPersonForm').on('submit', function(event) { + handleFormSubmit(this, '#createPersonError'); + }); + + $('#editPersonForm').on('submit', function(event) { + handleFormSubmit(this, '#editPersonError', function(data) { + // close modal and reload page + $('#editPersonModal').modal('hide'); + window.location.reload(); + }); + }); }); \ No newline at end of file diff --git a/db/migrations/022_add_is_active_to_process_definitions.php b/db/migrations/022_add_is_active_to_process_definitions.php index 4265d1e..988103a 100644 --- a/db/migrations/022_add_is_active_to_process_definitions.php +++ b/db/migrations/022_add_is_active_to_process_definitions.php @@ -1,12 +1,22 @@ exec($sql); - echo "Migration successful: is_active column added to process_definitions table.\n"; -} catch (PDOException $e) { - die("Migration failed: " . $e->getMessage() . "\n"); +function migrate_022($pdo) { + try { + $sql = "ALTER TABLE process_definitions ADD COLUMN is_active TINYINT(1) NOT NULL DEFAULT 1 AFTER definition_json"; + $pdo->exec($sql); + echo "Migration successful: is_active column added to process_definitions table.\n"; + } catch (PDOException $e) { + // Ignore if column already exists + if ($e->getCode() !== '42S21') { + die("Migration failed: " . $e->getMessage() . "\n"); + } + } } +// If called directly, run the migration +if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) { + require_once __DIR__ . '/../../db/config.php'; + $pdo = db(); + migrate_022($pdo); +} \ No newline at end of file diff --git a/db/migrations/023_add_new_member_process.php b/db/migrations/023_add_new_member_process.php new file mode 100644 index 0000000..d94bbda --- /dev/null +++ b/db/migrations/023_add_new_member_process.php @@ -0,0 +1,22 @@ +prepare("INSERT INTO process_definitions (name, code, definition_json, is_active) VALUES (?, ?, ?, ?)"); + $stmt->execute([ + 'Obsługa przyjęcia nowego członka', + 'obsluga-przyjecia-nowego-czlonka', + $json_definition, + 1 + ]); +} + +// If called directly, run the migration +if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) { + require_once __DIR__ . '/../../db/config.php'; + $pdo = db(); + migrate_023($pdo); + echo "Migration 023 executed successfully.\n"; +} \ No newline at end of file diff --git a/db/migrations/024_update_follow_up_process.php b/db/migrations/024_update_follow_up_process.php new file mode 100644 index 0000000..0b29c72 --- /dev/null +++ b/db/migrations/024_update_follow_up_process.php @@ -0,0 +1,154 @@ +prepare("UPDATE process_definitions SET definition_json = :json, start_node_id = 'awaiting_call' WHERE code = :code"); + $stmt->execute([ + ':json' => $json_definition, + ':code' => $process_code, + ]); + + echo "Migration 024 executed successfully: Updated 'guest_handling' process definition.\n"; +} + +// Direct execution guard +if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) { + try { + migrate_024(); + } catch (Exception $e) { + echo "Migration 24 failed: " . $e->getMessage() . "\n"; + } +} diff --git a/db/migrations/025_update_follow_up_process_structured.php b/db/migrations/025_update_follow_up_process_structured.php new file mode 100644 index 0000000..6e5ca5b --- /dev/null +++ b/db/migrations/025_update_follow_up_process_structured.php @@ -0,0 +1,161 @@ +prepare("UPDATE process_definitions SET definition_json = :json, start_node_id = 'awaiting_call' WHERE code = :code"); + $stmt->execute([ + ':json' => $json_definition, + ':code' => $process_code, + ]); + + echo "Migration 025 executed successfully: Updated 'guest_handling' process definition with structured data and router node."; +} + +// Direct execution guard +if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) +{ + try { + migrate_025(); + } catch (Exception $e) { + echo "Migration 25 failed: " . $e->getMessage() . "\n"; + } +} \ No newline at end of file diff --git a/index.php b/index.php index 5223afc..6ce4853 100644 --- a/index.php +++ b/index.php @@ -279,6 +279,7 @@ $status_colors = [
+
+