263 lines
11 KiB
PHP
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];
|
|
}
|
|
} |