Podział na procesy indywidualne i grupowe
This commit is contained in:
parent
9134470c19
commit
9ea7b9d268
@ -173,13 +173,50 @@ class WorkflowEngine {
|
||||
$stmt_meetings->execute(['today' => $today]);
|
||||
$upcoming_meetings_flat = $stmt_meetings->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
$spotkania_cols = [];
|
||||
$meeting_ids = [];
|
||||
foreach ($upcoming_meetings_flat as $meeting) {
|
||||
$meetingId = $this->getOrCreateMeeting($meeting['group_id'], $meeting['start_datetime']);
|
||||
$meeting_ids[] = $meetingId;
|
||||
$spotkania_cols[$meeting['group_id']]['group_id'] = $meeting['group_id'];
|
||||
$spotkania_cols[$meeting['group_id']]['group_name'] = $meeting['group_name'];
|
||||
$spotkania_cols[$meeting['group_id']]['meetings'][] = $meeting['start_datetime'];
|
||||
$spotkania_cols[$meeting['group_id']]['meetings'][] = [
|
||||
'datetime' => $meeting['start_datetime'],
|
||||
'meeting_id' => $meetingId
|
||||
];
|
||||
}
|
||||
|
||||
// Fetch process states for these meetings
|
||||
$meeting_processes = [];
|
||||
if (!empty($meeting_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($meeting_ids), '?'));
|
||||
$stmt_mp = $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.subject_type = 'meeting' AND pi.subject_id IN ($placeholders)"
|
||||
);
|
||||
$stmt_mp->execute($meeting_ids);
|
||||
$mp_data = $stmt_mp->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($mp_data as $instance) {
|
||||
$def_id = $instance['process_definition_id'];
|
||||
$node_id = $instance['current_node_id'];
|
||||
$definition = !empty($instance['pd_definition_json']) ? json_decode($instance['pd_definition_json'], true) : ($definition_map[$def_id] ?? null);
|
||||
|
||||
if ($definition && isset($definition['nodes'][$node_id])) {
|
||||
$node_info = $definition['nodes'][$node_id];
|
||||
$instance['computed_status'] = $node_info['ui_hints']['status'] ?? $instance['current_status'];
|
||||
} else {
|
||||
$instance['computed_status'] = $instance['current_status'];
|
||||
}
|
||||
|
||||
$meeting_processes[$instance['subject_id']][$instance['process_code']] = $instance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return [
|
||||
'people' => $people,
|
||||
@ -188,7 +225,8 @@ class WorkflowEngine {
|
||||
'all_functions' => $all_functions,
|
||||
'person_functions_map' => $person_functions_map,
|
||||
'bni_groups' => $bni_groups,
|
||||
'spotkania_cols' => $spotkania_cols, // Add this to the return array
|
||||
'spotkania_cols' => $spotkania_cols,
|
||||
'meeting_processes' => $meeting_processes, // Add this to the return array
|
||||
];
|
||||
}
|
||||
|
||||
@ -234,9 +272,9 @@ class WorkflowEngine {
|
||||
}
|
||||
|
||||
$stmt_insert = $this->pdo->prepare(
|
||||
"INSERT INTO process_instances (person_id, process_definition_id, current_node_id, current_status, last_activity_at, data_json) VALUES (?, ?, ?, 'in_progress', NOW(), ?)"
|
||||
"INSERT INTO process_instances (person_id, subject_type, subject_id, process_definition_id, current_node_id, current_status, last_activity_at, data_json) VALUES (?, 'person', ?, ?, ?, 'in_progress', NOW(), ?)"
|
||||
);
|
||||
$stmt_insert->execute([$personId, $definition['id'], $startNodeId, $initialDataJson]);
|
||||
$stmt_insert->execute([$personId, $personId, $definition['id'], $startNodeId, $initialDataJson]);
|
||||
$instanceId = $this->pdo->lastInsertId();
|
||||
|
||||
// 3. Create a system event for process start.
|
||||
@ -556,9 +594,9 @@ class WorkflowEngine {
|
||||
$data_json = !empty($initial_data_map) ? json_encode($initial_data_map) : null;
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
"INSERT INTO process_instances (person_id, process_definition_id, current_node_id, current_status, data_json, last_activity_at) VALUES (?, ?, ?, 'in_progress', ?, NOW())"
|
||||
"INSERT INTO process_instances (person_id, subject_type, subject_id, process_definition_id, current_node_id, current_status, data_json, last_activity_at) VALUES (?, 'person', ?, ?, ?, 'in_progress', ?, NOW())"
|
||||
);
|
||||
$stmt->execute([$personId, $processDefinitionId, $start_node, $data_json]);
|
||||
$stmt->execute([$personId, $personId, $processDefinitionId, $start_node, $data_json]);
|
||||
$newInstanceId = $this->pdo->lastInsertId();
|
||||
|
||||
$this->logEvent($newInstanceId, 'process_started', 'Process started', $start_node, ['context' => $context], $userId);
|
||||
@ -990,4 +1028,129 @@ class WorkflowEngine {
|
||||
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function startProcessForSubject(string $processCode, string $subjectType, int $subjectId, int $userId, string $mode='resume_or_create', ?string $cycleKey=null): int {
|
||||
$inTransaction = $this->pdo->inTransaction();
|
||||
if (!$inTransaction) { $this->pdo->beginTransaction(); }
|
||||
try {
|
||||
$stmt_def = $this->pdo->prepare("SELECT * FROM process_definitions WHERE code = ? AND is_active = 1");
|
||||
$stmt_def->execute([$processCode]);
|
||||
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$definition) {
|
||||
// If no active definition by code, try by ID if it's a checklist fallback
|
||||
$stmt_def = $this->pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$processCode]);
|
||||
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$definition) {
|
||||
throw new WorkflowNotFoundException("Process definition '$processCode' not found.");
|
||||
}
|
||||
|
||||
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : [];
|
||||
if (empty($definition_json) || $definition_json['type'] !== 'checklist') {
|
||||
throw new WorkflowNotAllowedException("Process definition '$processCode' not found or not a checklist.");
|
||||
}
|
||||
$startNodeId = null;
|
||||
} else {
|
||||
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : [];
|
||||
if (empty($definition_json) || !isset($definition_json['start_node_id'])) {
|
||||
throw new WorkflowRuleFailedException("Process definition is missing start_node_id.");
|
||||
}
|
||||
$startNodeId = $definition_json['start_node_id'];
|
||||
}
|
||||
|
||||
$initialDataJson = null;
|
||||
if (isset($definition_json['initial_data'])) {
|
||||
$initialDataJson = json_encode($definition_json['initial_data']);
|
||||
}
|
||||
|
||||
// Map person_id for backward compatibility
|
||||
$personId = ($subjectType === 'person') ? $subjectId : 0;
|
||||
|
||||
$stmt_insert = $this->pdo->prepare(
|
||||
"INSERT INTO process_instances (person_id, subject_type, subject_id, cycle_key, process_definition_id, current_node_id, current_status, last_activity_at, data_json) VALUES (?, ?, ?, ?, ?, ?, 'in_progress', NOW(), ?)"
|
||||
);
|
||||
$stmt_insert->execute([$personId, $subjectType, $subjectId, $cycleKey, $definition['id'], $startNodeId, $initialDataJson]);
|
||||
$instanceId = $this->pdo->lastInsertId();
|
||||
|
||||
$this->addEvent($instanceId, 'system', 'Process started.', $startNodeId, [], $userId);
|
||||
|
||||
if (!$inTransaction) { $this->pdo->commit(); }
|
||||
return (int)$instanceId;
|
||||
} catch (Exception $e) {
|
||||
if (!$inTransaction) { $this->pdo->rollBack(); }
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function getOrCreateInstanceForSubject(int $processDefinitionId, string $subjectType, int $subjectId, int $userId, string $mode='resume_or_create', ?string $cycleKey=null): array {
|
||||
$stmt_def = $this->pdo->prepare("SELECT code, is_active FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$processDefinitionId]);
|
||||
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$definition) {
|
||||
throw new WorkflowNotFoundException("Process definition #$processDefinitionId not found.");
|
||||
}
|
||||
|
||||
$code = $definition['code'];
|
||||
$instance = null;
|
||||
|
||||
if ($mode !== 'create_new_run') {
|
||||
$instance = $this->getActiveInstanceForSubject($code, $subjectType, $subjectId, $cycleKey);
|
||||
}
|
||||
|
||||
if (!$instance) {
|
||||
if (empty($definition['is_active'])) {
|
||||
throw new WorkflowNotAllowedException("Process is not active and cannot be started.");
|
||||
}
|
||||
$instanceId = $this->startProcessForSubject($code, $subjectType, $subjectId, $userId, $mode, $cycleKey);
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE id = ?");
|
||||
$stmt->execute([$instanceId]);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public function getActiveInstanceForSubject(string $processCode, string $subjectType, int $subjectId, ?string $cycleKey=null): ?array {
|
||||
$sql = "
|
||||
SELECT pi.*
|
||||
FROM process_instances pi
|
||||
JOIN process_definitions pd ON pi.process_definition_id = pd.id
|
||||
WHERE pi.subject_type = ? AND pi.subject_id = ? AND pd.code = ?
|
||||
";
|
||||
$params = [$subjectType, $subjectId, $processCode];
|
||||
|
||||
if ($cycleKey !== null) {
|
||||
$sql .= " AND pi.cycle_key = ?";
|
||||
$params[] = $cycleKey;
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY pi.last_activity_at DESC, pi.id DESC LIMIT 1";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $instance ?: null;
|
||||
}
|
||||
|
||||
public function getInstancesHistoryForSubject(string $processCode, string $subjectType, int $subjectId, int $limit=5): array {
|
||||
$sql = "
|
||||
SELECT pi.*
|
||||
FROM process_instances pi
|
||||
JOIN process_definitions pd ON pi.process_definition_id = pd.id
|
||||
WHERE pi.subject_type = ? AND pi.subject_id = ? AND pd.code = ?
|
||||
ORDER BY pi.last_activity_at DESC, pi.id DESC
|
||||
LIMIT ?
|
||||
";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindValue(1, $subjectType, PDO::PARAM_STR);
|
||||
$stmt->bindValue(2, $subjectId, PDO::PARAM_INT);
|
||||
$stmt->bindValue(3, $processCode, PDO::PARAM_STR);
|
||||
$stmt->bindValue(4, $limit, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
@ -25,38 +25,51 @@ if (!$process_definition_id && $process_code) {
|
||||
$process_definition_id = $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
if (!$person_id || !$process_definition_id) {
|
||||
if (!$subject_id || !$process_definition_id) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing person_id or process_id']);
|
||||
echo json_encode(['error' => 'Missing subject_id or process_id']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
$engine = new WorkflowEngine();
|
||||
|
||||
// Fetch Person and Process Definition details first
|
||||
// Fetch Subject and Process Definition details first
|
||||
$subjectName = 'Unknown';
|
||||
if ($subject_type === 'person') {
|
||||
$stmt_person = $pdo->prepare("SELECT first_name, last_name FROM people WHERE id = ?");
|
||||
$stmt_person->execute([$person_id]);
|
||||
$stmt_person->execute([$subject_id]);
|
||||
$person = $stmt_person->fetch();
|
||||
if ($person) {
|
||||
$subjectName = $person['first_name'] . ' ' . $person['last_name'];
|
||||
}
|
||||
} elseif ($subject_type === 'meeting') {
|
||||
$stmt_meeting = $pdo->prepare("SELECT m.meeting_datetime, bg.name as group_name FROM meetings m JOIN bni_groups bg ON m.bni_group_id = bg.id WHERE m.id = ?");
|
||||
$stmt_meeting->execute([$subject_id]);
|
||||
$meeting = $stmt_meeting->fetch();
|
||||
if ($meeting) {
|
||||
$subjectName = $meeting['group_name'] . ' - ' . date('d.m.Y', strtotime($meeting['meeting_datetime']));
|
||||
}
|
||||
}
|
||||
|
||||
$stmt_process = $pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
|
||||
$stmt_process->execute([$process_definition_id]);
|
||||
$process = $stmt_process->fetch();
|
||||
|
||||
if (!$person || !$process) {
|
||||
if (!$process) {
|
||||
http_response_code(404);
|
||||
echo "<p class='text-danger'>Could not find person or process.</p>";
|
||||
echo "<p class='text-danger'>Could not find process.</p>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Try to find an existing instance
|
||||
$instance = $engine->getInstanceByDefId($person_id, $process_definition_id);
|
||||
$instance = $engine->getActiveInstanceForSubject($process['code'], $subject_type, $subject_id);
|
||||
|
||||
?>
|
||||
|
||||
<!-- Title for the modal, to be grabbed by JS -->
|
||||
<div id="instance-modal-title" class="d-none">
|
||||
<?= htmlspecialchars($person['first_name']." ".$person['last_name']) ?> - <?= htmlspecialchars($process['name']) ?>
|
||||
<?= htmlspecialchars($subjectName) ?> - <?= htmlspecialchars($process['name']) ?>
|
||||
</div>
|
||||
|
||||
<?php if ($instance): // INSTANCE EXISTS ?>
|
||||
@ -70,7 +83,7 @@ $instance = $engine->getInstanceByDefId($person_id, $process_definition_id);
|
||||
<strong><?= t('modal.process_completed', 'Proces zakończony.') ?></strong>
|
||||
Obecny status: <?= htmlspecialchars($instance['current_status']) ?>.
|
||||
</div>
|
||||
<button id="restartProcessBtn" class="btn btn-sm btn-primary" data-person-id="<?= $person_id ?>" data-process-code="<?= htmlspecialchars($process['code']) ?>" data-mode="create_new_run">
|
||||
<button id="restartProcessBtn" class="btn btn-sm btn-primary" data-subject-type="<?= $subject_type ?>" data-subject-id="<?= $subject_id ?>" data-person-id="<?= $person_id ?>" data-process-code="<?= htmlspecialchars($process['code']) ?>" data-mode="create_new_run">
|
||||
<?= t('modal.start_new_instance', 'Rozpocznij od nowa') ?>
|
||||
</button>
|
||||
</div>
|
||||
@ -266,14 +279,17 @@ $instance = $engine->getInstanceByDefId($person_id, $process_definition_id);
|
||||
|
||||
<?php else: // NO INSTANCE EXISTS ?>
|
||||
<?php
|
||||
$eligibility = $engine->checkEligibility($person_id, $process_definition_id);
|
||||
$eligibility = ['is_eligible' => true, 'reasons' => []];
|
||||
if ($subject_type === 'person') {
|
||||
$eligibility = $engine->checkEligibility($subject_id, $process_definition_id);
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="text-center">
|
||||
<?php if ($eligibility['is_eligible']): ?>
|
||||
<h4><?= t('modal.process_not_started', 'Proces nie został uruchomiony') ?></h4>
|
||||
<p>This process has not been started for this person.</p>
|
||||
<button id="startProcessBtn" class="btn btn-primary" data-person-id="<?= $person_id ?>" data-process-id="<?= $process_definition_id ?>">
|
||||
<button id="startProcessBtn" class="btn btn-primary" data-subject-type="<?= $subject_type ?>" data-subject-id="<?= $subject_id ?>" data-person-id="<?= $person_id ?>" data-process-id="<?= $process_definition_id ?>">
|
||||
<?= t('modal.start_process', 'Uruchom proces') ?>
|
||||
</button>
|
||||
<?php else: ?>
|
||||
|
||||
@ -19,6 +19,8 @@ if (!isset($_SESSION['user_id'])) {
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
$personId = filter_input(INPUT_POST, 'person_id', FILTER_VALIDATE_INT);
|
||||
$subjectType = filter_input(INPUT_POST, 'subject_type', FILTER_SANITIZE_STRING) ?: 'person';
|
||||
$subjectId = filter_input(INPUT_POST, 'subject_id', FILTER_VALIDATE_INT) ?: $personId;
|
||||
$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');
|
||||
@ -32,23 +34,32 @@ if (!$processDefinitionId && $processCode) {
|
||||
$processDefinitionId = $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
if (!$personId || !$processDefinitionId) {
|
||||
throw new InvalidArgumentException('Invalid or missing person_id or process_id/process_code.');
|
||||
if (!$subjectId || !$processDefinitionId) {
|
||||
throw new InvalidArgumentException('Invalid or missing subject_id or process_id/process_code.');
|
||||
}
|
||||
|
||||
$engine = new WorkflowEngine();
|
||||
|
||||
|
||||
$stmt_def = $pdo->prepare("SELECT code FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$processDefinitionId]);
|
||||
$code = $stmt_def->fetchColumn() ?: $processCode;
|
||||
|
||||
if($deleteExisting === '1') {
|
||||
$instance = $engine->getInstanceByDefId($personId, $processDefinitionId);
|
||||
$instance = $engine->getActiveInstanceForSubject($code, $subjectType, $subjectId);
|
||||
if ($instance) {
|
||||
$engine->deleteInstance($instance['id']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($mode === 'create_new_run' || filter_input(INPUT_POST, 'force', FILTER_VALIDATE_INT) === 1) {
|
||||
$instance = $engine->createNewInstance($personId, $processDefinitionId, $userId);
|
||||
// start new process
|
||||
$instanceId = $engine->startProcessForSubject($code, $subjectType, $subjectId, $userId, 'create_new_run');
|
||||
$stmt = $pdo->prepare("SELECT * FROM process_instances WHERE id = ?");
|
||||
$stmt->execute([$instanceId]);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} else {
|
||||
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
|
||||
$instance = $engine->getOrCreateInstanceForSubject($processDefinitionId, $subjectType, $subjectId, $userId);
|
||||
}
|
||||
|
||||
if ($instance) {
|
||||
|
||||
66
db/migrations/037_add_process_subjects.php
Normal file
66
db/migrations/037_add_process_subjects.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
function migrate_037($pdo) {
|
||||
echo "Starting migration 037 (Process Subjects)...
|
||||
";
|
||||
|
||||
// 1. process_instances: add subject_type, subject_id, cycle_key, bni_group_id
|
||||
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM process_instances LIKE 'subject_type'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD COLUMN subject_type VARCHAR(20) NOT NULL DEFAULT 'person' AFTER person_id");
|
||||
echo "Added subject_type to process_instances.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM process_instances LIKE 'subject_id'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD COLUMN subject_id INT NOT NULL DEFAULT 0 AFTER subject_type");
|
||||
echo "Added subject_id to process_instances.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM process_instances LIKE 'cycle_key'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD COLUMN cycle_key VARCHAR(64) NULL AFTER subject_id");
|
||||
echo "Added cycle_key to process_instances.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM process_instances LIKE 'bni_group_id'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD COLUMN bni_group_id INT NULL AFTER cycle_key");
|
||||
echo "Added bni_group_id to process_instances.
|
||||
";
|
||||
}
|
||||
|
||||
// 2. Data migration
|
||||
echo "Migrating existing data...
|
||||
";
|
||||
$pdo->exec("UPDATE process_instances SET subject_type = 'person', subject_id = person_id WHERE subject_id = 0");
|
||||
|
||||
// 3. Add indexes
|
||||
$stmt = $pdo->query("SHOW INDEX FROM process_instances WHERE Key_name = 'idx_subject_active'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD INDEX idx_subject_active (subject_type, subject_id, current_status)");
|
||||
echo "Added index idx_subject_active.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW INDEX FROM process_instances WHERE Key_name = 'idx_subject_cycle'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD INDEX idx_subject_cycle (subject_type, subject_id, cycle_key)");
|
||||
echo "Added index idx_subject_cycle.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW INDEX FROM process_instances WHERE Key_name = 'idx_group'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD INDEX idx_group (bni_group_id)");
|
||||
echo "Added index idx_group.
|
||||
";
|
||||
}
|
||||
|
||||
echo "Migration 037 completed.
|
||||
";
|
||||
}
|
||||
66
index.php
66
index.php
@ -120,7 +120,9 @@ $status_colors = [
|
||||
<?php
|
||||
// Define process groups
|
||||
// Show all processes from the DB directly.
|
||||
$inne_procesy_cols = $processes;
|
||||
$inne_procesy_cols = array_filter($processes, function($p) {
|
||||
return $p['code'] !== 'meeting_preparation';
|
||||
});
|
||||
|
||||
|
||||
?>
|
||||
@ -143,16 +145,25 @@ $status_colors = [
|
||||
<tr class="text-center">
|
||||
<th rowspan="2" class="align-middle"><input type="checkbox" id="selectAll"></th>
|
||||
<th rowspan="2" class="align-middle"><?= t('dashboard.person', 'Osoba') ?></th>
|
||||
<?php
|
||||
$mp_def = null;
|
||||
foreach ($definitions as $def) {
|
||||
if ($def['code'] === 'meeting_preparation') {
|
||||
$mp_def = $def;
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php if (!empty($spotkania_cols)): ?>
|
||||
<th colspan="<?= count($spotkania_cols) ?>"><?= t('dashboard.meetings', 'Spotkania') ?></th>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($inne_procesy_cols)): ?>
|
||||
<th colspan="<?= count($processes) ?>"><?= t('dashboard.other_processes', 'Inne procesy') ?></th>
|
||||
<th colspan="<?= count($inne_procesy_cols) ?>"><?= t('dashboard.other_processes', 'Inne procesy') ?></th>
|
||||
<?php endif; ?>
|
||||
</tr>
|
||||
<tr class="text-center">
|
||||
<?php foreach ($spotkania_cols as $col):
|
||||
$isMeetingFilterActive = ($meetingFilterGroupId == $col['group_id'] && $meetingFilterDatetime == ($col['meetings'][0] ?? null));
|
||||
$isMeetingFilterActive = ($meetingFilterGroupId == $col['group_id'] && $meetingFilterDatetime == ($col['meetings'][0]['datetime'] ?? null));
|
||||
?>
|
||||
<th data-group-id="<?= $col['group_id'] ?>" data-meetings='<?= json_encode($col['meetings'] ?? []) ?>' class="<?= $isMeetingFilterActive ? 'active-filter' : '' ?>">
|
||||
<a href="#" class="text-decoration-none text-dark meeting-filter-link">
|
||||
@ -161,13 +172,24 @@ $status_colors = [
|
||||
<div class="mx-2 text-center">
|
||||
<?= htmlspecialchars($col['group_name']) ?><br>
|
||||
<small class="meeting-date"></small>
|
||||
<?php if ($mp_def): ?>
|
||||
<div class="mt-1 meeting-process-container" style="display:none;">
|
||||
<span class="badge rounded-circle bg-secondary process-dot meeting-process-dot"
|
||||
style="width: 15px; height: 15px; display: inline-block; cursor: pointer;"
|
||||
data-bs-toggle="modal" data-bs-target="#instanceModal"
|
||||
data-process-id="<?= $mp_def['id'] ?>"
|
||||
data-subject-type="meeting"
|
||||
data-subject-id=""
|
||||
title="Przygotowanie spotkania"></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-secondary meeting-nav-btn meeting-next-btn"><i class="bi bi-chevron-right"></i></button>
|
||||
</div>
|
||||
</a>
|
||||
</th>
|
||||
<?php endforeach; ?>
|
||||
<?php foreach ($processes as $col): ?>
|
||||
<?php foreach ($inne_procesy_cols as $col): ?>
|
||||
<?php
|
||||
$filterParams = $_GET;
|
||||
$filterParams['active_process_id'] = $col['id'];
|
||||
@ -222,7 +244,7 @@ $status_colors = [
|
||||
|
||||
// The meeting date will be determined by JS, we will add it to the data attribute
|
||||
// The initial meeting date is the first one in the list.
|
||||
$meeting_datetime = $col['meetings'][0] ?? '';
|
||||
$meeting_datetime = $col['meetings'][0]['datetime'] ?? '';
|
||||
|
||||
$color = $status_colors[$status] ?? 'secondary';
|
||||
?>
|
||||
@ -238,7 +260,7 @@ $status_colors = [
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php // Inne Procesy Columns ?>
|
||||
<?php foreach ($processes as $process):
|
||||
<?php foreach ($inne_procesy_cols as $process):
|
||||
$instance = $instances[$person['id']][$process['id']] ?? null;
|
||||
$status = $instance ? $instance['computed_status'] : 'none';
|
||||
$color = $status_colors[$status] ?? 'secondary';
|
||||
@ -694,6 +716,10 @@ $status_colors = [
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
const meetingProcesses = <?= json_encode($meeting_processes ?? []) ?>;
|
||||
const statusColors = <?= json_encode($status_colors ?? []) ?>;
|
||||
|
||||
|
||||
const meetingModal = new bootstrap.Modal(document.getElementById('meetingAttendanceModal'));
|
||||
const meetingForm = document.getElementById('meetingAttendanceForm');
|
||||
const meetingPersonName = document.getElementById('meetingPersonName');
|
||||
@ -857,8 +883,26 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
function updateMeetingView() {
|
||||
if (meetings.length > 0) {
|
||||
const currentMeetingDate = meetings[currentIndex];
|
||||
const currentMeetingObj = meetings[currentIndex];
|
||||
const currentMeetingDate = typeof currentMeetingObj === 'object' ? currentMeetingObj.datetime : currentMeetingObj;
|
||||
const currentMeetingId = typeof currentMeetingObj === 'object' ? currentMeetingObj.meeting_id : null;
|
||||
headerCell.querySelector('.meeting-date').textContent = new Date(currentMeetingDate).toLocaleDateString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
|
||||
const processContainer = headerCell.querySelector('.meeting-process-container');
|
||||
if (processContainer && currentMeetingId) {
|
||||
processContainer.style.display = 'block';
|
||||
const dot = processContainer.querySelector('.meeting-process-dot');
|
||||
if (dot) {
|
||||
dot.dataset.subjectId = currentMeetingId;
|
||||
const instance = meetingProcesses[currentMeetingId] && meetingProcesses[currentMeetingId]['meeting_preparation'];
|
||||
const status = instance ? instance.computed_status : 'none';
|
||||
const color = statusColors[status] || 'secondary';
|
||||
dot.className = `badge rounded-circle bg-${color} process-dot meeting-process-dot`;
|
||||
dot.dataset.processId = instance ? instance.id : '';
|
||||
}
|
||||
} else if (processContainer) {
|
||||
processContainer.style.display = 'none';
|
||||
}
|
||||
fetchAndUpdateAllDotsForGroup(groupId, currentMeetingDate);
|
||||
} else {
|
||||
headerCell.querySelector('.meeting-date').textContent = 'Brak';
|
||||
@ -923,12 +967,14 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
var instanceModal = document.getElementById('instanceModal');
|
||||
instanceModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var personId = button.dataset.personId;
|
||||
var personId = button.dataset.personId || '';
|
||||
var processId = button.dataset.processId;
|
||||
var subjectType = button.dataset.subjectType || 'person';
|
||||
var subjectId = button.dataset.subjectId || personId;
|
||||
var modalBody = instanceModal.querySelector('.modal-body');
|
||||
|
||||
// Load content via AJAX
|
||||
fetch(`_get_instance_details.php?person_id=${personId}&process_id=${processId}`)
|
||||
fetch(`_get_instance_details.php?person_id=${personId}&subject_type=${subjectType}&subject_id=${subjectId}&process_id=${processId}`)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
modalBody.innerHTML = html;
|
||||
@ -1459,6 +1505,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
if(button && button.dataset) {
|
||||
if (button.dataset.personId) instanceModalElement.dataset.lastPersonId = button.dataset.personId;
|
||||
if (button.dataset.processId) instanceModalElement.dataset.lastProcessId = button.dataset.processId;
|
||||
if (button.dataset.subjectType) instanceModalElement.dataset.lastSubjectType = button.dataset.subjectType;
|
||||
if (button.dataset.subjectId) instanceModalElement.dataset.lastSubjectId = button.dataset.subjectId;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import re
|
||||
|
||||
with open('WorkflowEngine.php', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
content = content.replace(
|
||||
'SELECT id, name, definition_json, is_active FROM process_definitions WHERE is_active = 1 ORDER BY sort_order, name',
|
||||
'SELECT id, code, name, definition_json, is_active FROM process_definitions WHERE is_active = 1 AND is_latest = 1 ORDER BY sort_order, name'
|
||||
)
|
||||
|
||||
content = content.replace(
|
||||
"'name' => $def['name'],",
|
||||
"'code' => $def['code'],\n 'name' => $def['name'],"
|
||||
)
|
||||
|
||||
content = content.replace(
|
||||
'$stmt_instances = $this->pdo->prepare("SELECT * FROM process__instances WHERE person_id IN ($placeholders)");\n $stmt_instances->execute($person_ids);\n $instances_data = $stmt_instances->fetchAll(PDO::FETCH_ASSOC);',
|
||||
"""
|
||||
$stmt_instances = $this->pdo->prepare(
|
||||
SELECT pi.*, pd.code as process_code, pd.id as definition_actual_id
|
||||
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, pi.id DESC
|
||||
");
|
||||
$stmt_instances->execute($person_ids);
|
||||
$instances_data = $stmt_instances->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Map code to latest definition id
|
||||
$codeToIdMap = [];
|
||||
foreach ($definitions as $defId => $def) {
|
||||
$codeToIdMap[$def['code']] = $defId;
|
||||
}""")
|
||||
|
||||
content = content.replace(
|
||||
"$instances[$instance['person_id']][$def_id] = $enriched_instance;",
|
||||
"""
|
||||
// Use process_code to map to the latest active column
|
||||
$code = $instance['process_code'] ?? null;
|
||||
if ($code && isset($codeToIdMap[$code])) {
|
||||
$latestDefId = $codeToIdMap[$code];
|
||||
// Only keep the most recently active instance per code (since ordered by last_activity_at DESC)
|
||||
if (!isset($instances[$instance['person_id']][$latestDefId])) {
|
||||
$instances[$instance['person_id']][$latestDefId] = $enriched_instance;
|
||||
}
|
||||
}""")
|
||||
|
||||
content = re.sub(
|
||||
r"public function getInstanceByDefId\(int \$personId, int \$processDefinitionId\): \?array \{.*?return \$instance \?: null;\s*\}",
|
||||
"""public function getInstanceByDefId(int $personId, int $processDefinitionId): ?array {
|
||||
$stmt_code = $this->pdo->prepare("SELECT code FROM process_definitions WHERE id = ?");
|
||||
$stmt_code->execute([$processDefinitionId]);
|
||||
$code = $stmt_code->fetchColumn();
|
||||
|
||||
if (!$code) return null;
|
||||
|
||||
$stmt = $this->pdo->prepare("\n SELECT pi.* \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 = ?\n ORDER BY pi.last_activity_at DESC, pi.id DESC\n LIMIT 1\n ");
|
||||
$stmt->execute([$personId, $code]);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $instance ?: null;
|
||||
}""",
|
||||
content, flags=re.DOTALL
|
||||
)
|
||||
|
||||
# Wait, checkEligibility needs to use the NEW version definition ID?
|
||||
# Actually checkEligibility uses $processDefinitionId which IS the new version's ID!
|
||||
|
||||
with open('WorkflowEngine.php', 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
print("Patched.")
|
||||
@ -1,59 +0,0 @@
|
||||
import re
|
||||
|
||||
with open('WorkflowEngine.php', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
new_method = """
|
||||
public function createNewInstance(int $personId, int $processDefinitionId, int $userId, array $context = []): 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_def = $this->pdo->prepare("SELECT definition_json, code, is_active FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$processDefinitionId]);
|
||||
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$definition) {
|
||||
throw new WorkflowNotFoundException("Process definition #$processDefinitionId not found.");
|
||||
}
|
||||
|
||||
if (empty($definition['is_active'])) {
|
||||
throw new WorkflowNotAllowedException("Process is not active and cannot be started.");
|
||||
}
|
||||
|
||||
$eligibility = $this->checkEligibility($personId, $processDefinitionId, $context);
|
||||
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) : [];
|
||||
$start_node = $definition_json['start_node_id'] ?? 'start';
|
||||
$initial_data_map = $definition_json['initial_data'] ?? [];
|
||||
$data_json = !empty($initial_data_map) ? json_encode($initial_data_map) : null;
|
||||
|
||||
$stmt = $this->pdo->prepare(
|
||||
"INSERT INTO process_instances (person_id, process_definition_id, current_node_id, current_status, data_json, last_activity_at) VALUES (?, ?, ?, 'in_progress', ?, NOW())"
|
||||
);
|
||||
$stmt->execute([$personId, $processDefinitionId, $start_node, $data_json]);
|
||||
$newInstanceId = $this->pdo->lastInsertId();
|
||||
|
||||
$this->logEvent($newInstanceId, 'process_started', 'Process started', $start_node, ['context' => $context], $userId);
|
||||
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE id = ?");
|
||||
$stmt->execute([$newInstanceId]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
"
|
||||
|
||||
content = content.replace(
|
||||
'public function getOrCreateInstanceByDefId(int $personId, int $processDefinitionId, int $userId, array $context = []): ?array {',
|
||||
new_method + '\n public function getOrCreateInstanceByDefId(int $personId, int $processDefinitionId, int $userId, array $context = []): ?array {'
|
||||
)
|
||||
|
||||
with open('WorkflowEngine.php', 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
print("Patched.")
|
||||
@ -1,17 +0,0 @@
|
||||
import re
|
||||
|
||||
with open('_get_instance_details.php', 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
pattern = r'<button id="startNewProcessBtn"([^>]+)>.*?</button>\s*</div>\s*<script>.*?</script>'
|
||||
replacement = r'''<button id="restartProcessBtn" class="btn btn-sm btn-primary" data-person-id="<?= $person_id ?>" data-process-code="<?= htmlspecialchars($process['code']) ?>" data-mode="create_new_run">
|
||||
<?= t('modal.start_new_instance', 'Rozpocznij od nowa') ?>
|
||||
</button>
|
||||
</div>'''
|
||||
|
||||
new_content, count = re.subn(pattern, replacement, content, flags=re.DOTALL)
|
||||
|
||||
with open('_get_instance_details.php', 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
|
||||
print(f"Replaced {count} instances.")
|
||||
@ -1,60 +0,0 @@
|
||||
import re
|
||||
|
||||
with open('_save_process_definition.php', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
old_update_logic = """ if (empty($processId)) {
|
||||
// Create new process
|
||||
$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active) VALUES (?, ?, ?, ?, 1)';
|
||||
$params = [$name, $code, $definition_json, $start_node];
|
||||
$message = 'Process created successfully.';
|
||||
} else {
|
||||
// Update existing process
|
||||
$is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : 0;
|
||||
$sql = 'UPDATE process_definitions SET name = ?, definition_json = ?, start_node_id = ?, is_active = ? WHERE id = ?';
|
||||
$params = [$name, $definition_json, $start_node, $is_active, $processId];
|
||||
$message = 'Process updated successfully.';
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);"""
|
||||
|
||||
new_update_logic = """ if (empty($processId)) {
|
||||
// Create new process
|
||||
$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, is_latest) VALUES (?, ?, ?, ?, 1, 1, 1)';
|
||||
$params = [$name, $code, $definition_json, $start_node];
|
||||
$message = 'Process created successfully.';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
} else {
|
||||
// "Update" existing process by creating a new version
|
||||
$stmt_old = $pdo->prepare('SELECT code, version, sort_order, is_active FROM process_definitions WHERE id = ?');
|
||||
$stmt_old->execute([$processId]);
|
||||
$old = $stmt_old->fetch();
|
||||
|
||||
if ($old) {
|
||||
$is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : $old['is_active'];
|
||||
$new_version = $old['version'] + 1;
|
||||
$db_code = $old['code'];
|
||||
|
||||
// Mark all previous versions as not latest
|
||||
$stmt_update = $pdo->prepare('UPDATE process_definitions SET is_latest = 0 WHERE code = ?');
|
||||
$stmt_update->execute([$db_code]);
|
||||
|
||||
// Insert new version
|
||||
$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, supersedes_definition_id, is_latest, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?)';
|
||||
$params = [$name, $db_code, $definition_json, $start_node, $is_active, $new_version, $processId, $old['sort_order']];
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$message = 'Process updated successfully (new version created).';
|
||||
} else {
|
||||
throw new WorkflowRuleFailedException('Process not found.');
|
||||
}
|
||||
}"""
|
||||
|
||||
content = content.replace(old_update_logic, new_update_logic)
|
||||
|
||||
with open('_save_process_definition.php', 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
print("Patched.")
|
||||
Loading…
x
Reference in New Issue
Block a user