37338-vm/WorkflowEngine.php
2026-01-10 07:48:27 +00:00

263 lines
11 KiB
PHP

<?php
require_once __DIR__ . '/db/config.php';
/**
* Class WorkflowEngine
*
* Centralny serwis do zarządzania logiką procesów.
* Interfejs użytkownika nie powinien bezpośrednio zmieniać statusów ani instancji; wszystkie operacje muszą przechodzić przez ten silnik.
*/
class WorkflowEngine {
private $pdo;
public function __construct() {
$this->pdo = db();
}
/**
* Pobiera wszystkie dane niezbędne dla głównej macierzy pulpitu procesów.
*
* @return array Tablica zawierająca 'people', 'definitions' i zmapowaną tablicę 'instances'.
*/
public function getDashboardMatrix(): array {
// Get all people (potential assignees)
$stmt_people = $this->pdo->prepare("SELECT id, firstName, lastName, companyName, role, email, phone FROM people ORDER BY lastName, firstName");
$stmt_people->execute();
$people = $stmt_people->fetchAll(PDO::FETCH_ASSOC);
// Fetch all process definitions
$stmt_defs = $this->pdo->prepare("SELECT id, name FROM process_definitions ORDER BY name");
$stmt_defs->execute();
$process_definitions = $stmt_defs->fetchAll(PDO::FETCH_ASSOC);
// Fetch instances
$stmt_instances = $this->pdo->prepare("SELECT * FROM process_instances");
$stmt_instances->execute();
$instances_data = $stmt_instances->fetchAll(PDO::FETCH_ASSOC);
$instances = [];
foreach ($instances_data as $instance) {
$instances[$instance['personId']][$instance['processDefinitionId']] = $instance;
}
return [
'people' => $people,
'definitions' => $process_definitions,
'instances' => $instances,
];
}
/**
* Rozpoczyna nową instancję procesu dla danej osoby.
*
* @param string $processCode Unikalny kod definicji procesu.
* @param int $personId ID osoby.
* @param int $userId ID użytkownika inicjującego akcję.
* @return int|null ID nowo utworzonej instancji lub null w przypadku niepowodzenia.
*/
public function startProcess(string $processCode, int $personId, int $userId): ?int {
// 1. Znajdź aktywną definicję procesu po kodzie.
// 2. Pobierz start_node_id z definicji.
// 3. Utwórz nową instancję procesu ze statusem 'in_progress' i current_node_id.
// 4. Utwórz zdarzenie systemowe dla rozpoczęcia procesu.
// TODO: Implementacja logiki.
return null;
}
/**
* Pobiera pojedynczą instancję procesu, tworząc ją, jeśli nie istnieje.
*
* @param int $personId
* @param int $processDefinitionId
* @param int $userId Użytkownik inicjujący utworzenie, jeśli to nastąpi.
* @return array|null Tablica asocjacyjna z danymi instancji.
*/
public function getOrCreateInstanceByDefId(int $personId, int $processDefinitionId, int $userId): ?array {
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE personId = ? AND processDefinitionId = ?");
$stmt->execute([$personId, $processDefinitionId]);
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$instance) {
// Fetch the process definition to get the initial status
$stmt_def = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
$stmt_def->execute([$processDefinitionId]);
$definition_raw = $stmt_def->fetchColumn();
$definition = $definition_raw ? json_decode($definition_raw, true) : null;
$initial_status = $definition['initial_status'] ?? 'none';
$stmt_insert = $this->pdo->prepare("INSERT INTO process_instances (personId, processDefinitionId, current_status) VALUES (?, ?, ?)");
$stmt_insert->execute([$personId, $processDefinitionId, $initial_status]);
$instanceId = $this->pdo->lastInsertId();
// Utwórz zdarzenie systemowe dla utworzenia instancji
$this->addEvent($instanceId, 'system', 'Instancja utworzona.', null, [], $userId);
// Pobierz ponownie nowo utworzoną instancję
$stmt->execute([$personId, $processDefinitionId]);
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
}
return $instance;
}
/**
* Dodaje nowe zdarzenie do historii instancji.
* To jest wewnętrzna metoda pomocnicza.
*/
private function addEvent(int $instanceId, string $eventType, string $message, ?string $nodeId, array $payload, int $userId): void {
$stmt = $this->pdo->prepare(
"INSERT INTO process_events (processInstanceId, event_type, message, node_id, payload_json, createdById) VALUES (?, ?, ?, ?, ?, ?)"
);
$stmt->execute([$instanceId, $eventType, $message, $nodeId, json_encode($payload), $userId]);
}
/**
* Pobiera historię zdarzeń dla danej instancji.
*
* @param int $instanceId
* @return 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->execute([$instanceId]);
return $stmt_events->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Pobiera listę dostępnych przejść z bieżącego węzła instancji.
*
* @param int $instanceId
* @return array Lista dostępnych przejść.
*/
public function getAvailableTransitions(int $instanceId): array {
$stmt = $this->pdo->prepare(
'SELECT pi.current_status, pd.definition_json '
. 'FROM process_instances pi '
. 'JOIN process_definitions pd ON pi.processDefinitionId = pd.id '
. 'WHERE pi.id = ?'
);
$stmt->execute([$instanceId]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$result || empty($result['definition_json'])) {
return [];
}
$definition = json_decode($result['definition_json'], true);
if (!$definition || !isset($definition['transitions'])) {
return [];
}
$current_status = $result['current_status'];
$allowed_transitions = $definition['transitions'][$current_status] ?? [];
$transitions = [];
foreach ($allowed_transitions as $target_status) {
$transitions[] = [
'id' => 'transition_' . str_replace(' ', '_', strtolower($target_status)), // e.g., transition_in_progress
'name' => 'Oznacz jako ' . $target_status, // e.g., Oznacz jako In Progress
'target_status' => $target_status
];
}
return $transitions;
}
/**
* Stosuje przejście do instancji procesu. To jest główna metoda do postępu w przepływie pracy.
*
* @param int $instanceId ID instancji procesu.
* @param string $transitionId ID przejścia do zastosowania (z definition_json).
* @param array $inputPayload Dane zebrane od użytkownika dla tego kroku.
* @param int $userId ID użytkownika wykonującego akcję.
* @return bool True w przypadku sukcesu, false w przypadku porażki.
*/
public function applyTransition(int $instanceId, string $transitionId, array $inputPayload, int $userId): bool {
$this->pdo->beginTransaction();
try {
// 1. Pobierz instancję i dostępne przejścia
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE id = ?");
$stmt->execute([$instanceId]);
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$instance) {
// Instancja nie znaleziona
$this->pdo->rollBack();
return false;
}
$availableTransitions = $this->getAvailableTransitions($instanceId);
// 2. Sprawdź, czy przejście jest dozwolone
$selectedTransition = null;
if ($transitionId === 'note') { // Specjalny przypadek dodawania notatki
$selectedTransition = ['id' => 'note', 'name' => 'Dodano notatkę', 'target_status' => $instance['current_status']];
} else {
foreach ($availableTransitions as $trans) {
if ($trans['id'] === $transitionId) {
$selectedTransition = $trans;
break;
}
}
}
if (!$selectedTransition) {
// Nieprawidłowe lub niedozwolone przejście
$this->pdo->rollBack();
return false;
}
// 3. Utwórz zdarzenie
$eventType = ($transitionId === 'note') ? 'note' : 'transition_applied';
$message = $inputPayload['message'] ?? $selectedTransition['name'];
$this->addEvent($instanceId, $eventType, $message, null, $inputPayload, $userId);
// 4. Zaktualizuj instancję
$stmt_update = $this->pdo->prepare(
"UPDATE process_instances SET current_status = ?, lastActivityAt = CURRENT_TIMESTAMP WHERE id = ?"
);
$stmt_update->execute([$selectedTransition['target_status'], $instanceId]);
$this->pdo->commit();
return true;
} catch (Exception $e) {
$this->pdo->rollBack();
error_log("Błąd w applyTransition: " . $e->getMessage());
return false;
}
}
/**
* Masowa operacja stosowania tego samego przejścia do wielu osób dla danego procesu.
*
* @param string $processCode
* @param array $personIds
* @param string $transitionId
* @param array $inputPayload
* @param int $userId
* @return array Podsumowanie wyników (np. ['success' => count, 'failed' => count]).
*/
public function bulkApplyTransition(string $processCode, array $personIds, string $transitionId, array $inputPayload, int $userId): array {
// 1. Upewnij się, że instancje istnieją dla wszystkich osób (użyj ensureInstances).
// 2. Przejdź przez pętlę personIds i wywołaj applyTransition dla każdej z nich.
// TODO: Implementacja logiki.
return ['success' => 0, 'failed' => 0];
}
/**
* Zapewnia, że instancje procesów istnieją dla danego zestawu osób i kodów procesów.
* Jeśli instancja brakuje, zostanie utworzona.
*
* @param array $personIds
* @param array $processCodes
* @param int $userId
* @return array Podsumowanie utworzonych instancji.
*/
public function ensureInstances(array $personIds, array $processCodes, int $userId): array {
// TODO: Implementacja logiki do tworzenia brakujących instancji.
return ['created' => 0];
}
}