Pierwsze procesy

This commit is contained in:
Flatlogic Bot 2026-01-10 21:10:13 +00:00
parent 3b1a26adc9
commit 4674e7458b
4 changed files with 123 additions and 62 deletions

View File

@ -404,13 +404,20 @@ class WorkflowEngine {
$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 {
if (!is_int($processDefinitionId) || $processDefinitionId <= 0) {
throw new InvalidArgumentException("processDefinitionId must be a positive integer.");
}
if (!is_int($personId) || $personId <= 0) {
throw new InvalidArgumentException("personId must be a positive integer.");
}
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE `person_id` = ? AND `process_definition_id` = ?"); $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) {
$stmt_def = $this->pdo->prepare("SELECT definition_json, code FROM process_definitions WHERE id = ?"); $stmt_def = $this->pdo->prepare("SELECT definition_json, code, is_active FROM process_definitions WHERE id = ?");
$stmt_def->execute([$processDefinitionId]); $stmt_def->execute([$processDefinitionId]);
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC); $definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
@ -418,17 +425,24 @@ class WorkflowEngine {
throw new WorkflowNotFoundException("Process definition #$processDefinitionId not found."); throw new WorkflowNotFoundException("Process definition #$processDefinitionId not found.");
} }
$this->checkEligibility($personId, $definition); if (empty($definition['is_active'])) {
throw new WorkflowNotAllowedException("Process is not active and cannot be started.");
}
$eligibility = $this->checkEligibility($personId, $processDefinitionId);
if (!$eligibility['is_eligible']) {
throw new WorkflowEligibilityException("Person is not eligible to start this process.", $eligibility['reasons']);
}
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : []; $definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : [];
$processCode = ($definition_json && isset($definition_json['type']) && $definition_json['type'] === 'checklist') $processCode = ($definition_json && isset($definition_json['type']) && $definition_json['type'] === 'checklist')
? (string) $processDefinitionId ? (string) $processDefinitionId
: $definition['code']; : $definition['code'];
if($processCode) { if ($processCode) {
$instanceId = $this->startProcess($processCode, $personId, $userId); $instanceId = $this->startProcess($processCode, $personId, $userId);
if($instanceId) { if ($instanceId) {
$stmt->execute([$personId, $processDefinitionId]); $stmt->execute([$personId, $processDefinitionId]);
$instance = $stmt->fetch(PDO::FETCH_ASSOC); $instance = $stmt->fetch(PDO::FETCH_ASSOC);
} }

View File

@ -1,29 +1,44 @@
<?php <?php
session_start();
require_once 'WorkflowEngine.php';
require_once 'lib/ErrorHandler.php'; require_once 'lib/ErrorHandler.php';
require_once 'lib/WorkflowExceptions.php'; register_error_handler();
require_once 'db/config.php';
require_once 'WorkflowEngine.php';
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) { if (!isset($_SESSION['user_id'])) {
throw new WorkflowNotAllowedException('Authentication required.'); http_response_code(401);
echo json_encode(['error' => ['message' => 'Authentication required.']]);
exit;
} }
$userId = $_SESSION['user_id'];
$personId = $_GET['personId'] ?? null; $userId = $_SESSION['user_id'];
$processDefinitionId = $_GET['processId'] ?? null; $personId = filter_input(INPUT_POST, 'person_id', FILTER_VALIDATE_INT);
$processDefinitionId = filter_input(INPUT_POST, 'process_id', FILTER_VALIDATE_INT);
if (!$personId || !$processDefinitionId) { if (!$personId || !$processDefinitionId) {
throw new WorkflowRuleFailedException('Missing parameters for process initialization.'); // 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.');
} }
$engine = new WorkflowEngine(); $engine = new WorkflowEngine();
// 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.
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId); $instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
if ($instance) { if ($instance) {
$_SESSION['success_message'] = "Process initialized successfully."; echo json_encode(['success' => true, 'message' => 'Process initialized successfully.', 'instance_id' => $instance['id']]);
} else { } else {
$_SESSION['error_message'] = "Failed to initialize process."; // 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.");
} }
header('Location: index.php');
exit();

View File

@ -607,6 +607,12 @@ document.addEventListener('DOMContentLoaded', function () {
handleAddNote(noteBtn); handleAddNote(noteBtn);
return; return;
} }
const startBtn = event.target.closest('#startProcessBtn');
if (startBtn) {
handleStartProcess(startBtn);
return;
}
}); });
instanceModal.addEventListener('change', function(event) { instanceModal.addEventListener('change', function(event) {
@ -708,24 +714,76 @@ document.addEventListener('DOMContentLoaded', function () {
submitRequestAndReloadModal('_apply_transition.php', formData); submitRequestAndReloadModal('_apply_transition.php', formData);
} }
function handleStartProcess(button) {
const personId = button.dataset.personId;
const processId = button.dataset.processId;
if (!personId || !processId) {
alert('Missing data for starting process. Please close the modal and try again.');
return;
}
const formData = new FormData();
formData.append('person_id', personId);
formData.append('process_id', processId);
submitRequestAndReloadModal('_init_single_instance.php', formData);
}
function submitRequestAndReloadModal(url, formData) { function submitRequestAndReloadModal(url, formData) {
const modalBody = instanceModal.querySelector('.modal-body'); const modalBody = instanceModal.querySelector('.modal-body');
showLoading(modalBody); showLoading(modalBody);
fetch(url, { fetch(url, {
method: 'POST', method: 'POST',
body: formData body: formData
}) })
.then(response => { .then(response => {
if (!response.ok) { if (!response.ok) {
throw new Error('Network response was not ok'); // If response is not OK, it's an error. Clone the response to read it twice.
const clone = response.clone();
return response.json()
.then(json => {
// We have a JSON error body, throw a custom error with its details
const error = new Error(json.error?.message || 'An unkown error occurred.');
error.correlation_id = json.correlation_id;
error.response = response; // Attach full response
throw error;
})
.catch(() => {
// If JSON parsing fails, fall back to the text body
return clone.text().then(text => {
const error = new Error(text || 'Network response was not ok and could not parse error body.');
error.response = response;
throw error;
});
});
}
return response.json(); // On success, just parse the JSON
})
.then(data => {
if (data.success) {
// Reload modal content for the same person/process after successful submission
fetchAndRenderModalContent(currentPersonId, currentProcessId);
} else {
// Handle cases where the server returns 200 OK but with success: false
const error = new Error(data.message || 'An unknown error occurred.');
if (data.correlation_id) {
error.correlation_id = data.correlation_id;
}
throw error;
} }
// Reload modal content after successful submission
fetchAndRenderModalContent(currentPersonId, currentProcessId);
}) })
.catch(error => { .catch(error => {
console.error('Error submitting request:', error); console.error('Error submitting request:', error);
modalBody.innerHTML = `<div class="alert alert-danger">Wystąpił błąd sieciowy.</div>`;
let errorMessage = `<div class="alert alert-danger">`;
errorMessage += `<strong>Error:</strong> ${error.message}`;
if (error.correlation_id) {
errorMessage += `<br><small class="text-muted">Correlation ID: ${error.correlation_id}</small>`;
}
errorMessage += `</div>`;
modalBody.innerHTML = errorMessage;
}); });
} }

View File

@ -1,44 +1,18 @@
<?php <?php
class WorkflowException extends Exception { class WorkflowNotFoundException extends Exception {}
protected $httpCode; class WorkflowNotAllowedException extends Exception {}
protected $details; class WorkflowRuleFailedException extends Exception {}
public function __construct($message = "", $code = 0, $httpCode = 500, $details = [], Throwable $previous = null) { class WorkflowEligibilityException extends Exception {
private $reasons;
public function __construct($message = "", $reasons = [], $code = 0, Throwable $previous = null) {
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
$this->httpCode = $httpCode; $this->reasons = $reasons;
$this->details = $details;
} }
public function getHttpCode() { public function getReasons() {
return $this->httpCode; return $this->reasons;
} }
}
public function getDetails() {
return $this->details;
}
}
class WorkflowNotFoundException extends WorkflowException {
public function __construct($message = "Not Found", $details = [], Throwable $previous = null) {
parent::__construct($message, 404, 404, $details, $previous);
}
}
class WorkflowNotAllowedException extends WorkflowException {
public function __construct($message = "Bad Request", $details = [], Throwable $previous = null) {
parent::__construct($message, 400, 400, $details, $previous);
}
}
class WorkflowRuleFailedException extends WorkflowException {
public function __construct($message = "Unprocessable Entity", $details = [], Throwable $previous = null) {
parent::__construct($message, 422, 422, $details, $previous);
}
}
class WorkflowConflictException extends WorkflowException {
public function __construct($message = "Conflict", $details = [], Throwable $previous = null) {
parent::__construct($message, 409, 409, $details, $previous);
}
}