Podział na typy procesów

This commit is contained in:
Flatlogic Bot 2026-03-02 19:32:17 +00:00
parent 45d975adbf
commit 708233f9b4
20 changed files with 975 additions and 101 deletions

View File

@ -57,7 +57,7 @@ class WorkflowEngine {
$people = $stmt_people->fetchAll(PDO::FETCH_ASSOC); $people = $stmt_people->fetchAll(PDO::FETCH_ASSOC);
// 4. Fetch all process definitions with their JSON // 4. Fetch all process definitions with their JSON
$stmt_defs = $this->pdo->prepare("SELECT id, code, name, definition_json, is_active FROM process_definitions WHERE is_active = 1 AND is_latest = 1 ORDER BY sort_order, name"); $stmt_defs = $this->pdo->prepare("SELECT id, code, name, definition_json, is_active FROM process_definitions WHERE is_active = 1 AND is_latest = 1 AND subject_scope = 'person' ORDER BY sort_order, name");
$stmt_defs->execute(); $stmt_defs->execute();
$process_definitions_raw = $stmt_defs->fetchAll(PDO::FETCH_ASSOC); $process_definitions_raw = $stmt_defs->fetchAll(PDO::FETCH_ASSOC);
@ -1155,4 +1155,103 @@ $spotkania_cols = [];
return $stmt->fetchAll(PDO::FETCH_ASSOC); return $stmt->fetchAll(PDO::FETCH_ASSOC);
} }
public function getOrCreateInstanceBySubject(string $subjectType, int $subjectId, string $processCode, int $userId, string $mode='resume_or_create'): array {
if (!in_array($subjectType, ['person', 'meeting', 'bni_group', 'organization'])) {
throw new \InvalidArgumentException("Invalid subjectType.");
}
if ($subjectId <= 0) {
throw new \InvalidArgumentException("subjectId must be > 0.");
}
if (empty($processCode)) {
throw new \InvalidArgumentException("processCode cannot be empty.");
}
$inTransaction = $this->pdo->inTransaction();
if (!$inTransaction) { $this->pdo->beginTransaction(); }
try {
$sql = "SELECT * FROM process_definitions WHERE code = ? AND is_active = 1 AND subject_scope = ? ORDER BY version DESC, id DESC LIMIT 1";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([$processCode, $subjectType]);
$definition = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$definition) {
$stmt = $this->pdo->prepare("SELECT * FROM process_definitions WHERE code = ? AND is_active = 1 ORDER BY version DESC, id DESC LIMIT 1");
$stmt->execute([$processCode]);
$definition = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$definition) {
throw new WorkflowNotFoundException("No active process definition found for code '$processCode'");
}
}
$defId = $definition['id'];
$defJson = json_decode($definition['definition_json'], true);
$startNodeId = $defJson['start_node_id'] ?? null;
if (!$startNodeId) {
throw new \Exception("Process definition missing start_node_id");
}
$instance = null;
if ($mode === 'resume_or_create') {
$stmt = $this->pdo->prepare(
"SELECT pi.* FROM process_instances pi\n JOIN process_definitions pd ON pi.process_definition_id = pd.id\n WHERE pi.subject_type = ? AND pi.subject_id = ? AND pd.code = ?\n ORDER BY pi.id DESC LIMIT 1"
);
$stmt->execute([$subjectType, $subjectId, $processCode]);
$instance = $stmt->fetch(\PDO::FETCH_ASSOC);
}
if (!$instance) {
$stmt = $this->pdo->prepare(
"SELECT MAX(pi.run_number)
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 = ?"
);
$stmt->execute([$subjectType, $subjectId, $processCode]);
$maxRun = (int)$stmt->fetchColumn();
$runNumber = $maxRun + 1;
$personIdVal = ($subjectType === 'person') ? $subjectId : null;
$status = $defJson['nodes'][$startNodeId]['ui_hints']['status'] ?? 'in_progress';
$reason = $defJson['nodes'][$startNodeId]['ui_hints']['reason'] ?? '';
$nextStep = $defJson['nodes'][$startNodeId]['ui_hints']['next_step'] ?? '';
$stmt = $this->pdo->prepare(
"INSERT INTO process_instances
(subject_type, subject_id, person_id, process_definition_id, current_node_id, current_status, current_reason, suggested_next_step, run_number, last_activity_at, data_json)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), '{}')"
);
$stmt->execute([
$subjectType, $subjectId, $personIdVal, $defId, $startNodeId, $status, $reason, $nextStep, $runNumber
]);
$instanceId = $this->pdo->lastInsertId();
$this->addEvent($instanceId, 'system', 'Process started.', $startNodeId, [], $userId);
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE id = ?");
$stmt->execute([$instanceId]);
$instance = $stmt->fetch(\PDO::FETCH_ASSOC);
}
if (!$inTransaction) { $this->pdo->commit(); }
return [
'instance_id' => $instance['id'],
'process_definition_id' => $instance['process_definition_id'],
'process_code' => $processCode,
'subject_type' => $instance['subject_type'],
'subject_id' => $instance['subject_id'],
'person_id' => $instance['person_id'],
'current_node_id' => $instance['current_node_id'],
'current_status' => $instance['current_status'],
'last_activity_at' => $instance['last_activity_at'],
'data_json' => $instance['data_json']
];
} catch (\Exception $e) {
if (!$inTransaction) { $this->pdo->rollBack(); }
throw $e;
}
}
} }

View File

@ -53,6 +53,15 @@ if ($subject_type === 'person') {
if ($meeting) { if ($meeting) {
$subjectName = $meeting['group_name'] . ' - ' . date('d.m.Y', strtotime($meeting['meeting_datetime'])); $subjectName = $meeting['group_name'] . ' - ' . date('d.m.Y', strtotime($meeting['meeting_datetime']));
} }
} elseif ($subject_type === 'bni_group') {
$stmt_group = $pdo->prepare("SELECT name FROM bni_groups WHERE id = ?");
$stmt_group->execute([$subject_id]);
$group = $stmt_group->fetch();
if ($group) {
$subjectName = 'Grupa ' . $group['name'];
}
} elseif ($subject_type === 'organization') {
$subjectName = 'Główna Organizacja';
} }
$stmt_process = $pdo->prepare("SELECT * FROM process_definitions WHERE id = ?"); $stmt_process = $pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
@ -302,7 +311,7 @@ $instance = $engine->getActiveInstanceForSubject($process['code'], $subject_type
<div class="text-center"> <div class="text-center">
<?php if ($eligibility['is_eligible']): ?> <?php if ($eligibility['is_eligible']): ?>
<h4><?= t('modal.process_not_started', 'Proces nie został uruchomiony') ?></h4> <h4><?= t('modal.process_not_started', 'Proces nie został uruchomiony') ?></h4>
<p>This process has not been started for this person.</p> <p>This process has not been started for this subject.</p>
<button id="startProcessBtn" class="btn btn-primary" data-subject-type="<?= $subject_type ?>" data-subject-id="<?= $subject_id ?>" data-process-id="<?= $process_definition_id ?>" data-process-code="<?= $process_code ?>" data-cycle-key="<?= $cycle_key ?>"> <button id="startProcessBtn" class="btn btn-primary" data-subject-type="<?= $subject_type ?>" data-subject-id="<?= $subject_id ?>" data-process-id="<?= $process_definition_id ?>" data-process-code="<?= $process_code ?>" data-cycle-key="<?= $cycle_key ?>">
<?= t('modal.start_process', 'Uruchom proces') ?> <?= t('modal.start_process', 'Uruchom proces') ?>
</button> </button>

View File

@ -19,51 +19,35 @@ if (!isset($_SESSION['user_id'])) {
$userId = $_SESSION['user_id']; $userId = $_SESSION['user_id'];
$personId = filter_input(INPUT_POST, 'person_id', FILTER_VALIDATE_INT); $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); $processDefinitionId = filter_input(INPUT_POST, 'process_id', FILTER_VALIDATE_INT);
$subjectType = filter_input(INPUT_POST, 'subject_type', FILTER_SANITIZE_STRING);
$subjectId = filter_input(INPUT_POST, 'subject_id', FILTER_VALIDATE_INT);
$processCode = filter_input(INPUT_POST, 'process_code', FILTER_SANITIZE_STRING); $processCode = filter_input(INPUT_POST, 'process_code', FILTER_SANITIZE_STRING);
$deleteExisting = filter_input(INPUT_POST, 'delete_existing'); $mode = filter_input(INPUT_POST, 'mode', FILTER_SANITIZE_STRING) ?: 'resume_or_create';
$mode = filter_input(INPUT_POST, 'mode', FILTER_SANITIZE_STRING);
if ($personId && !$subjectType) {
$subjectType = 'person';
$subjectId = $personId;
}
$pdo = db(); $pdo = db();
if (!$processDefinitionId && $processCode) { if ($processDefinitionId && !$processCode) {
$stmt = $pdo->prepare("SELECT id FROM process_definitions WHERE code = ? AND is_latest = 1 LIMIT 1"); $stmt = $pdo->prepare("SELECT code FROM process_definitions WHERE id = ?");
$stmt->execute([$processCode]); $stmt->execute([$processDefinitionId]);
$processDefinitionId = $stmt->fetchColumn(); $processCode = $stmt->fetchColumn();
} }
if (!$subjectId || !$processDefinitionId) { if (!$subjectId || !$subjectType || !$processCode) {
throw new InvalidArgumentException('Invalid or missing subject_id or process_id/process_code.'); throw new InvalidArgumentException('Invalid or missing subject_type, subject_id, or process_code.');
} }
$engine = new WorkflowEngine(); $engine = new WorkflowEngine();
$instance = $engine->getOrCreateInstanceBySubject($subjectType, $subjectId, $processCode, $userId, $mode);
if ($instance && isset($instance['instance_id'])) {
$stmt_def = $pdo->prepare("SELECT code FROM process_definitions WHERE id = ?"); echo json_encode(['success' => true, 'message' => 'Process initialized successfully.', 'instance_id' => $instance['instance_id']]);
$stmt_def->execute([$processDefinitionId]);
$code = $stmt_def->fetchColumn() ?: $processCode;
if($deleteExisting === '1') {
$instance = $engine->getActiveInstanceForSubject($code, $subjectType, $subjectId, $cycleKey);
if ($instance) {
$engine->deleteInstance($instance['id']);
}
}
if ($mode === 'create_new_run' || filter_input(INPUT_POST, 'force', FILTER_VALIDATE_INT) === 1) {
// start new process
$instanceId = $engine->startProcessForSubject($code, $subjectType, $subjectId, $userId, 'create_new_run', $cycleKey);
$stmt = $pdo->prepare("SELECT * FROM process_instances WHERE id = ?");
$stmt->execute([$instanceId]);
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
} else {
$instance = $engine->getOrCreateInstanceForSubject($processDefinitionId, $subjectType, $subjectId, $userId);
}
if ($instance) {
echo json_encode(['success' => true, 'message' => 'Process initialized successfully.', 'instance_id' => $instance['id']]);
} else { } else {
throw new Exception("Failed to initialize process for an unknown reason."); throw new Exception("Failed to initialize process for an unknown reason.");
} }

View File

@ -93,6 +93,7 @@ try {
$processId = $_POST['process_id'] ?? null; $processId = $_POST['process_id'] ?? null;
$name = $_POST['name'] ?? ''; $name = $_POST['name'] ?? '';
$definition_json = $_POST['definition_json'] ?? ''; $definition_json = $_POST['definition_json'] ?? '';
$subject_scope = $_POST['subject_scope'] ?? 'person';
validate_definition_json($definition_json); validate_definition_json($definition_json);
@ -108,14 +109,14 @@ try {
if (empty($processId)) { if (empty($processId)) {
// Create new process // Create new process
$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, is_latest) VALUES (?, ?, ?, ?, 1, 1, 1)'; $sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, is_latest, subject_scope) VALUES (?, ?, ?, ?, 1, 1, 1, ?)';
$params = [$name, $code, $definition_json, $start_node]; $params = [$name, $code, $definition_json, $start_node, $subject_scope];
$message = 'Process created successfully.'; $message = 'Process created successfully.';
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute($params); $stmt->execute($params);
} else { } else {
// "Update" existing process by creating a new version // "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 = $pdo->prepare('SELECT code, version, sort_order, is_active, subject_scope FROM process_definitions WHERE id = ?');
$stmt_old->execute([$processId]); $stmt_old->execute([$processId]);
$old = $stmt_old->fetch(); $old = $stmt_old->fetch();
@ -129,8 +130,8 @@ try {
$stmt_update->execute([$db_code]); $stmt_update->execute([$db_code]);
// Insert new version // 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, ?)'; $sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, supersedes_definition_id, is_latest, sort_order, subject_scope) VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?)';
$params = [$name, $db_code, $definition_json, $start_node, $is_active, $new_version, $processId, $old['sort_order']]; $params = [$name, $db_code, $definition_json, $start_node, $is_active, $new_version, $processId, $old['sort_order'], $subject_scope];
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute($params); $stmt->execute($params);
$message = 'Process updated successfully (new version created).'; $message = 'Process updated successfully (new version created).';

View File

@ -7,6 +7,24 @@
<i class="bi bi-kanban"></i> <i class="bi bi-kanban"></i>
<span class="nav-link-text"><?= t('menu.process_dashboard', 'Pulpit procesów') ?></span> <span class="nav-link-text"><?= t('menu.process_dashboard', 'Pulpit procesów') ?></span>
</a> </a>
</li>
<li class="nav-item">
<a class="nav-link <?= ($current_page == 'meetings_processes.php') ? 'active' : '' ?>" href="meetings_processes.php">
<i class="bi bi-camera-video"></i>
<span class="nav-link-text">Procesy spotkań</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= ($current_page == 'groups_processes.php') ? 'active' : '' ?>" href="groups_processes.php">
<i class="bi bi-collection"></i>
<span class="nav-link-text">Procesy grup BNI</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= ($current_page == 'organization_processes.php') ? 'active' : '' ?>" href="organization_processes.php">
<i class="bi bi-building"></i>
<span class="nav-link-text">Procesy organizacji</span>
</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= ($current_page == 'calendar.php') ? 'active' : '' ?>" href="calendar.php"> <a class="nav-link <?= ($current_page == 'calendar.php') ? 'active' : '' ?>" href="calendar.php">

View File

@ -132,11 +132,13 @@ document.addEventListener('DOMContentLoaded', function () {
if (isEdit) { if (isEdit) {
const processId = button.dataset.processId; const processId = button.dataset.processId;
const processName = button.dataset.processName; const processName = button.dataset.processName;
const subjectScope = button.dataset.subjectScope || 'person';
const processDefinition = button.dataset.processDefinition; const processDefinition = button.dataset.processDefinition;
modalTitle.textContent = 'Edit Process'; modalTitle.textContent = 'Edit Process';
processIdInput.value = processId; processIdInput.value = processId;
processNameInput.value = processName; processNameInput.value = processName;
if(document.getElementById('subjectScope')) document.getElementById('subjectScope').value = subjectScope;
try { try {
definition = JSON.parse(processDefinition || '{}'); definition = JSON.parse(processDefinition || '{}');

View File

@ -2,4 +2,4 @@
# https://curl.se/docs/http-cookies.html # https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk. # This file was generated by libcurl! Edit at your own risk.
127.0.0.1 FALSE / FALSE 0 PHPSESSID np7q4kbboaogr1lb2gg3753mdu localhost FALSE / FALSE 0 PHPSESSID nupuqse7tg6gsrulg298ru8sn8

View File

@ -0,0 +1,42 @@
<?php
function migrate_038($pdo) {
echo "Starting migration 038 (Subject Scope and Run Number)...
";
$stmt = $pdo->query("SHOW COLUMNS FROM process_definitions LIKE 'subject_scope'");
if (!$stmt->fetch()) {
$pdo->exec("ALTER TABLE process_definitions ADD COLUMN subject_scope VARCHAR(32) NOT NULL DEFAULT 'person' AFTER name");
echo "Added subject_scope to process_definitions.
";
}
$stmt = $pdo->query("SHOW COLUMNS FROM process_instances LIKE 'run_number'");
if (!$stmt->fetch()) {
$pdo->exec("ALTER TABLE process_instances ADD COLUMN run_number INT NOT NULL DEFAULT 1 AFTER subject_id");
echo "Added run_number to process_instances.
";
}
$defJson = json_encode([
'start_node_id' => 'node_start',
'nodes' => [
['id' => 'node_start', 'name' => 'Planowanie', 'type' => 'task', 'ui_hints' => ['color' => 'blue', 'status' => 'not_started']],
['id' => 'node_end', 'name' => 'Zakończone', 'type' => 'end', 'ui_hints' => ['color' => 'green', 'status' => 'completed']]
],
'transitions' => [
['from' => 'node_start', 'to' => 'node_end', 'name' => 'Oznacz jako gotowe', 'trigger' => 'manual']
]
]);
$stmt = $pdo->prepare("SELECT id FROM process_definitions WHERE code = 'meeting_preparation_meeting'");
$stmt->execute();
if (!$stmt->fetch()) {
$stmtIns = $pdo->prepare("INSERT INTO process_definitions (code, name, version, is_active, subject_scope, definition_json) VALUES ('meeting_preparation_meeting', 'Przygotowanie spotkania (spotkanie)', 1, 1, 'meeting', :json)");
$stmtIns->execute(['json' => $defJson]);
echo "Inserted meeting_preparation_meeting definition.
";
}
echo "Migration 038 completed.
";
}

114
groups_processes.php Normal file
View File

@ -0,0 +1,114 @@
<?php
require_once 'WorkflowEngine.php';
require_once 'lib/i18n.php';
session_start();
$engine = new WorkflowEngine();
$pdo = db();
$stmt_groups = $pdo->prepare("SELECT * FROM bni_groups ORDER BY name");
$stmt_groups->execute();
$groups = $stmt_groups->fetchAll(\PDO::FETCH_ASSOC);
$stmt_defs = $pdo->prepare("SELECT * FROM process_definitions WHERE is_active = 1 AND is_latest = 1 AND subject_scope = 'bni_group' ORDER BY sort_order, name");
$stmt_defs->execute();
$definitions = $stmt_defs->fetchAll(\PDO::FETCH_ASSOC);
?>
<?php include '_header.php'; ?>
<?php include '_navbar.php'; ?>
<div class="container-fluid">
<div class="row">
<?php include '_sidebar.php'; ?>
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Procesy Grup BNI</h1>
</div>
<div class="table-responsive mt-3">
<table class="table table-striped table-hover table-bordered align-middle matrix-table">
<thead class="table-light">
<tr>
<th>Grupa BNI</th>
<?php foreach ($definitions as $def): ?>
<th class="text-center"><?= htmlspecialchars($def['name']) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php if (empty($groups)): ?>
<tr><td colspan="<?= count($definitions) + 1 ?>" class="text-center">Brak grup.</td></tr>
<?php endif; ?>
<?php foreach ($groups as $group): ?>
<tr>
<td>
<strong><?= htmlspecialchars($group['name']) ?></strong>
</td>
<?php foreach ($definitions as $def): ?>
<?php
$instance = $engine->getActiveInstanceForSubject($def['code'], 'bni_group', $group['id']);
$cellColor = '';
$cellText = 'Brak';
if ($instance) {
$cellText = $instance['current_status'];
if (in_array($instance['current_status'], ['completed','positive'])) {
$cellColor = 'background-color: #d1e7dd;';
} else if ($instance['current_status'] == 'in_progress') {
$cellColor = 'background-color: #fff3cd;';
}
}
?>
<td class="text-center p-2" style="<?= $cellColor ?>">
<button type="button" class="btn btn-sm btn-outline-secondary w-100"
onclick="openInstanceModal('bni_group', <?= $group['id'] ?>, '<?= htmlspecialchars($def['code']) ?>')">
<?= htmlspecialchars($cellText) ?>
</button>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</main>
</div>
</div>
<script>
function openInstanceModal(subjectType, subjectId, processCode) {
const modal = new bootstrap.Modal(document.getElementById('instanceModal'));
const modalBody = document.getElementById('instanceModalBody');
const modalTitle = document.getElementById('instanceModalLabel');
modalBody.innerHTML = '<div class="text-center"><div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div></div>';
modalTitle.textContent = 'Ładowanie...';
modal.show();
fetch(`_get_instance_details.php?subject_type=${subjectType}&subject_id=${subjectId}&process_code=${processCode}`)
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.text();
})
.then(html => {
modalBody.innerHTML = html;
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
const titleEl = tempDiv.querySelector('#instance-modal-title');
if (titleEl) {
modalTitle.textContent = titleEl.textContent;
} else {
modalTitle.textContent = 'Szczegóły procesu';
}
bindModalEvents();
})
.catch(error => {
console.error('Error fetching details:', error);
modalBody.innerHTML = '<div class="alert alert-danger">Błąd podczas pobierania szczegółów.</div>';
});
}
</script>
<?php include '_footer.php'; ?>

127
meetings_processes.php Normal file
View File

@ -0,0 +1,127 @@
<?php
require_once 'WorkflowEngine.php';
require_once 'lib/i18n.php';
session_start();
$engine = new WorkflowEngine();
$pdo = db();
// Fetch future meetings
$today = date('Y-m-d H:i:s');
$stmt_meetings = $pdo->prepare("
SELECT m.id as meeting_id, m.meeting_datetime, bg.name as group_name
FROM meetings m
JOIN bni_groups bg ON m.bni_group_id = bg.id
WHERE m.meeting_datetime >= :today
ORDER BY m.meeting_datetime ASC
");
$stmt_meetings->execute(['today' => $today]);
$meetings = $stmt_meetings->fetchAll(\PDO::FETCH_ASSOC);
// Fetch meeting process definitions
$stmt_defs = $pdo->prepare("SELECT * FROM process_definitions WHERE is_active = 1 AND is_latest = 1 AND subject_scope = 'meeting' ORDER BY sort_order, name");
$stmt_defs->execute();
$definitions = $stmt_defs->fetchAll(\PDO::FETCH_ASSOC);
?>
<?php include '_header.php'; ?>
<?php include '_navbar.php'; ?>
<div class="container-fluid">
<div class="row">
<?php include '_sidebar.php'; ?>
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Procesy spotkań</h1>
</div>
<div class="table-responsive mt-3">
<table class="table table-striped table-hover table-bordered align-middle matrix-table">
<thead class="table-light">
<tr>
<th>Spotkanie</th>
<?php foreach ($definitions as $def): ?>
<th class="text-center"><?= htmlspecialchars($def['name']) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php if (empty($meetings)): ?>
<tr><td colspan="<?= count($definitions) + 1 ?>" class="text-center">Brak przyszłych spotkań w bazie.</td></tr>
<?php endif; ?>
<?php foreach ($meetings as $meeting): ?>
<tr>
<td>
<strong><?= htmlspecialchars($meeting['group_name']) ?></strong><br>
<small class="text-muted"><?= date('d.m.Y H:i', strtotime($meeting['meeting_datetime'])) ?></small>
</td>
<?php foreach ($definitions as $def): ?>
<?php
// Let's get instance state
$instance = $engine->getActiveInstanceForSubject($def['code'], 'meeting', $meeting['meeting_id']);
$cellColor = '';
$cellText = 'Brak';
if ($instance) {
// For simplicity just map status or display button
$cellText = $instance['current_status'];
if (in_array($instance['current_status'], ['completed','positive'])) {
$cellColor = 'background-color: #d1e7dd;';
} else if ($instance['current_status'] == 'in_progress') {
$cellColor = 'background-color: #fff3cd;';
}
}
?>
<td class="text-center p-2" style="<?= $cellColor ?>">
<button type="button" class="btn btn-sm btn-outline-secondary w-100"
onclick="openInstanceModal('meeting', <?= $meeting['meeting_id'] ?>, '<?= htmlspecialchars($def['code']) ?>')">
<?= htmlspecialchars($cellText) ?>
</button>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</main>
</div>
</div>
<script>
function openInstanceModal(subjectType, subjectId, processCode) {
const modal = new bootstrap.Modal(document.getElementById('instanceModal'));
const modalBody = document.getElementById('instanceModalBody');
const modalTitle = document.getElementById('instanceModalLabel');
modalBody.innerHTML = '<div class="text-center"><div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div></div>';
modalTitle.textContent = 'Ładowanie...';
modal.show();
fetch(`_get_instance_details.php?subject_type=${subjectType}&subject_id=${subjectId}&process_code=${processCode}`)
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.text();
})
.then(html => {
modalBody.innerHTML = html;
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
const titleEl = tempDiv.querySelector('#instance-modal-title');
if (titleEl) {
modalTitle.textContent = titleEl.textContent;
} else {
modalTitle.textContent = 'Szczegóły procesu';
}
bindModalEvents();
})
.catch(error => {
console.error('Error fetching details:', error);
modalBody.innerHTML = '<div class="alert alert-danger">Błąd podczas pobierania szczegółów.</div>';
});
}
</script>
<?php include '_footer.php'; ?>

106
organization_processes.php Normal file
View File

@ -0,0 +1,106 @@
<?php
require_once 'WorkflowEngine.php';
require_once 'lib/i18n.php';
session_start();
$engine = new WorkflowEngine();
$pdo = db();
$stmt_defs = $pdo->prepare("SELECT * FROM process_definitions WHERE is_active = 1 AND is_latest = 1 AND subject_scope = 'organization' ORDER BY sort_order, name");
$stmt_defs->execute();
$definitions = $stmt_defs->fetchAll(\PDO::FETCH_ASSOC);
?>
<?php include '_header.php'; ?>
<?php include '_navbar.php'; ?>
<div class="container-fluid">
<div class="row">
<?php include '_sidebar.php'; ?>
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Procesy Organizacji</h1>
</div>
<div class="table-responsive mt-3">
<table class="table table-striped table-hover table-bordered align-middle matrix-table">
<thead class="table-light">
<tr>
<th>Organizacja</th>
<?php foreach ($definitions as $def): ?>
<th class="text-center"><?= htmlspecialchars($def['name']) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<tr>
<td>
<strong>Główna Organizacja</strong>
</td>
<?php foreach ($definitions as $def): ?>
<?php
// We use subject_id = 1 for the whole organization
$instance = $engine->getActiveInstanceForSubject($def['code'], 'organization', 1);
$cellColor = '';
$cellText = 'Brak';
if ($instance) {
$cellText = $instance['current_status'];
if (in_array($instance['current_status'], ['completed','positive'])) {
$cellColor = 'background-color: #d1e7dd;';
} else if ($instance['current_status'] == 'in_progress') {
$cellColor = 'background-color: #fff3cd;';
}
}
?>
<td class="text-center p-2" style="<?= $cellColor ?>">
<button type="button" class="btn btn-sm btn-outline-secondary w-100"
onclick="openInstanceModal('organization', 1, '<?= htmlspecialchars($def['code']) ?>')">
<?= htmlspecialchars($cellText) ?>
</button>
</td>
<?php endforeach; ?>
</tr>
</tbody>
</table>
</div>
</main>
</div>
</div>
<script>
function openInstanceModal(subjectType, subjectId, processCode) {
const modal = new bootstrap.Modal(document.getElementById('instanceModal'));
const modalBody = document.getElementById('instanceModalBody');
const modalTitle = document.getElementById('instanceModalLabel');
modalBody.innerHTML = '<div class="text-center"><div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div></div>';
modalTitle.textContent = 'Ładowanie...';
modal.show();
fetch(`_get_instance_details.php?subject_type=${subjectType}&subject_id=${subjectId}&process_code=${processCode}`)
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.text();
})
.then(html => {
modalBody.innerHTML = html;
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
const titleEl = tempDiv.querySelector('#instance-modal-title');
if (titleEl) {
modalTitle.textContent = titleEl.textContent;
} else {
modalTitle.textContent = 'Szczegóły procesu';
}
bindModalEvents();
})
.catch(error => {
console.error('Error fetching details:', error);
modalBody.innerHTML = '<div class="alert alert-danger">Błąd podczas pobierania szczegółów.</div>';
});
}
</script>
<?php include '_footer.php'; ?>

23
patch_form.py Normal file
View File

@ -0,0 +1,23 @@
import re
with open('process_definitions.php', 'r') as f:
content = f.read()
new_field = """ <div class="mb-3">
<label for="subjectScope" class="form-label">Zakres procesu (Subject Scope)</label>
<select class="form-select" id="subjectScope" name="subject_scope" required>
<option value="person">Osoba (Person)</option>
<option value="meeting">Spotkanie (Meeting)</option>
<option value="bni_group">Grupa BNI (BNI Group)</option>
<option value="organization">Organizacja (Organization)</option>
</select>
</div>
"""
content = content.replace(
'<div class="mb-3">\n <label for="processName" class="form-label">Process Name</label>',
new_field + ' <div class="mb-3">\n <label for="processName" class="form-label">Process Name</label>'
)
with open('process_definitions.php', 'w') as f:
f.write(content)

22
patch_js.py Normal file
View File

@ -0,0 +1,22 @@
import re
with open('assets/js/process_definitions.js', 'r') as f:
content = f.read()
content = content.replace(
'const processName = button.dataset.processName;',
'const processName = button.dataset.processName;\n const subjectScope = button.dataset.subjectScope || \'person\';'
)
content = content.replace(
'processNameInput.value = processName;',
'processNameInput.value = processName;\n if(document.getElementById(\'subjectScope\')) document.getElementById(\'subjectScope\').value = subjectScope;'
)
content = content.replace(
'processNameInput.value = \'";',
'processNameInput.value = \'";\n if(document.getElementById(\'subjectScope\')) document.getElementById(\'subjectScope\').value = \'person\';'
)
with open('assets/js/process_definitions.js', 'w') as f:
f.write(content)

View File

@ -1,63 +1,38 @@
import re import sys
with open('_get_instance_details.php', 'r') as f: with open('_get_instance_details.php', 'r') as f:
content = f.read() content = f.read()
old_block = " <?php foreach ($currentNode['ui_hints']['form_schema'] as $field): ?> patch = """} elseif ($subject_type === 'meeting') {
<div class="mb-3"> $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 = ?");
<label for="<?= $field['name'] ?>" class="form-label"><?= $field['label'] ?></label> $stmt_meeting->execute([$subject_id]);
<?php if ($field['type'] === 'textarea'): ?> $meeting = $stmt_meeting->fetch();
<textarea id="<?= $field['name'] ?>" name="<?= $field['name'] ?>" class="form-control"></textarea> if ($meeting) {
<?php elseif ($field['type'] === 'select'): ?> $subjectName = $meeting['group_name'] . ' - ' . date('d.m.Y', strtotime($meeting['meeting_datetime']));
<select id="<?= $field['name'] ?>" name="<?= $field['name'] ?>" class="form-select"> }
<?php foreach ($field['options'] as $option): ?> } elseif ($subject_type === 'bni_group') {
<option value="<?= $option['value'] ?>"><?= $option['label'] ?></option> $stmt_group = $pdo->prepare("SELECT name FROM bni_groups WHERE id = ?");
<?php endforeach; ?> $stmt_group->execute([$subject_id]);
</select> $group = $stmt_group->fetch();
<?php else: ?> if ($group) {
<input type="<?= $field['type'] ?>" id="<?= $field['name'] ?>" name="<?= $field['name'] ?>" class="form-control" value="<?= ($field['default'] ?? '') === 'now' ? date('Y-m-d\TH:i') : '' ?>"> $subjectName = 'Grupa ' . $group['name'];
<?php endif; ?> }
</div> } elseif ($subject_type === 'organization') {
<?php endforeach; ?>" $subjectName = 'Główna Organizacja';
}"""
new_block = " <?php foreach ($currentNode['ui_hints']['form_schema'] as $field): old_code = """} elseif ($subject_type === 'meeting') {
$fieldName = $field['name']; $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 = ?");
$currentValue = $instanceData[$fieldName] ?? null; $stmt_meeting->execute([$subject_id]);
?> $meeting = $stmt_meeting->fetch();
<?php if ($field['type'] === 'checkbox'): ?> if ($meeting) {
<div class="form-check mb-3"> $subjectName = $meeting['group_name'] . ' - ' . date('d.m.Y', strtotime($meeting['meeting_datetime']));
<input type="hidden" name="<?= $fieldName ?>" value="0"> }
<input type="checkbox" id="<?= $fieldName ?>" name="<?= $fieldName ?>" class="form-check-input" value="1" <?= (!empty($currentValue) || (!isset($currentValue) && !empty($field['default']))) ? 'checked' : '' ?>> }"""
<label for="<?= $fieldName ?>" class="form-check-label"><?= $field['label'] ?></label>
</div>
<?php else: ?>
<div class="mb-3">
<label for="<?= $fieldName ?>" class="form-label"><?= $field['label'] ?></label>
<?php if ($field['type'] === 'textarea'): ?>
<textarea id="<?= $fieldName ?>" name="<?= $fieldName ?>" class="form-control"><?= htmlspecialchars((string)($currentValue ?? '')) ?></textarea>
<?php elseif ($field['type'] === 'select'): ?>
<select id="<?= $fieldName ?>" name="<?= $fieldName ?>" class="form-select">
<?php foreach ($field['options'] as $option):
$selected = ($currentValue !== null && (string)$currentValue === (string)$option['value']) ? 'selected' : '';
?>
<option value="<?= $option['value'] ?>" <?= $selected ?>><?= $option['label'] ?></option>
<?php endforeach; ?>
</select>
<?php else: ?>
<?php
$defaultVal = ($field['default'] ?? '') === 'now' ? date('Y-m-d\\TH:i') : ($field['default'] ?? '');
$valToUse = $currentValue ?? $defaultVal;
?>
<input type="<?= $field['type'] ?>" id="<?= $fieldName ?>" name="<?= $fieldName ?>" class="form-control" value="<?= htmlspecialchars((string)$valToUse) ?>">
<?php endif; ?>
</div>
<?php endif; ?>
<?php endforeach; ?>"
if old_block in content: content = content.replace(old_code, patch)
content = content.replace(old_block, new_block)
with open('_get_instance_details.php', 'w') as f: with open('_get_instance_details.php', 'w') as f:
f.write(content) f.write(content)
print("Patched successfully")
else: print("Patched modal")
print("Old block not found!")

43
patch_save_def.php Normal file
View File

@ -0,0 +1,43 @@
<?php
$content = file_get_contents('_save_process_definition.php');
// We need to add subject_scope extraction and insert/update
$content = str_replace(
"$definition_json = $_POST['definition_json'] ?? '';",
"$definition_json = $_POST['definition_json'] ?? '';\n $subject_scope = $_POST['subject_scope'] ?? 'person';",
$content
);
$content = str_replace(
"$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, is_latest) VALUES (?, ?, ?, ?, 1, 1, 1)';",
"$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, is_latest, subject_scope) VALUES (?, ?, ?, ?, 1, 1, 1, ?)';",
$content
);
$content = str_replace(
"$params = [ $name, $code, $definition_json, $start_node ];",
"$params = [ $name, $code, $definition_json, $start_node, $subject_scope ];",
$content
);
$content = str_replace(
"$stmt_old = $pdo->prepare('SELECT code, version, sort_order, is_active FROM process_definitions WHERE id = ?');",
"$stmt_old = $pdo->prepare('SELECT code, version, sort_order, is_active, subject_scope FROM process_definitions WHERE id = ?');",
$content
);
$content = str_replace(
"$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, supersedes_definition_id, is_latest, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?)';",
"$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, supersedes_definition_id, is_latest, sort_order, subject_scope) VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?)';",
$content
);
$content = str_replace(
"$params = [ $name, $db_code, $definition_json, $start_node, $is_active, $new_version, $processId, $old['sort_order'] ];",
"$params = [ $name, $db_code, $definition_json, $start_node, $is_active, $new_version, $processId, $old['sort_order'], $subject_scope ];",
$content
);
file_put_contents('_save_process_definition.php', $content);
echo "Patched _save_process_definition.php\n";

39
patch_save_def.py Normal file
View File

@ -0,0 +1,39 @@
import sys
with open('_save_process_definition.php', 'r') as f:
content = f.read()
content = content.replace(
"$definition_json = $_POST['definition_json'] ?? '';",
"$definition_json = $_POST['definition_json'] ?? '';\n $subject_scope = $_POST['subject_scope'] ?? 'person';"
)
content = content.replace(
"$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, is_latest) VALUES (?, ?, ?, ?, 1, 1, 1)';",
"$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, is_latest, subject_scope) VALUES (?, ?, ?, ?, 1, 1, 1, ?)';"
)
content = content.replace(
"$params = [$name, $code, $definition_json, $start_node];",
"$params = [$name, $code, $definition_json, $start_node, $subject_scope];"
)
content = content.replace(
"$stmt_old = $pdo->prepare('SELECT code, version, sort_order, is_active FROM process_definitions WHERE id = ?');",
"$stmt_old = $pdo->prepare('SELECT code, version, sort_order, is_active, subject_scope FROM process_definitions WHERE id = ?');"
)
content = content.replace(
"$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, supersedes_definition_id, is_latest, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?)';",
"$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active, version, supersedes_definition_id, is_latest, sort_order, subject_scope) VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?)';"
)
content = content.replace(
"$params = [$name, $db_code, $definition_json, $start_node, $is_active, $new_version, $processId, $old['sort_order']];",
"$params = [$name, $db_code, $definition_json, $start_node, $is_active, $new_version, $processId, $old['sort_order'], $subject_scope];"
)
with open('_save_process_definition.php', 'w') as f:
f.write(content)
print("Patched")

31
patch_sidebar.php Normal file
View File

@ -0,0 +1,31 @@
<?php
$content = file_get_contents('_sidebar.php');
$newLinks = <<<HTML
<li class="nav-item">
<a class="nav-link <?= (\$current_page == 'meetings_processes.php') ? 'active' : '' ?>" href="meetings_processes.php">
<i class="bi bi-camera-video"></i>
<span class="nav-link-text">Procesy spotkań</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= (\$current_page == 'groups_processes.php') ? 'active' : '' ?>" href="groups_processes.php">
<i class="bi bi-collection"></i>
<span class="nav-link-text">Procesy grup BNI</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= (\$current_page == 'organization_processes.php') ? 'active' : '' ?>" href="organization_processes.php">
<i class="bi bi-building"></i>
<span class="nav-link-text">Procesy organizacji</span>
</a>
</li>
HTML;
$search = '<li class="nav-item">
<a class="nav-link <?= ($current_page == \'calendar.php\') ? \'active\' : \'\' ?>" href="calendar.php">';
$content = str_replace($search, $newLinks . "\n " . $search, $content);
file_put_contents('_sidebar.php', $content);
echo "Patched _sidebar.php\n";

121
patch_we.php Normal file
View File

@ -0,0 +1,121 @@
<?php
$content = file_get_contents('WorkflowEngine.php');
if (strpos($content, 'function getOrCreateInstanceBySubject') !== false) {
echo "Method already exists.\n";
exit;
}
$newMethod = <<<'EOD'
public function getOrCreateInstanceBySubject(string $subjectType, int $subjectId, string $processCode, int $userId, string $mode='resume_or_create'): array {
if (!in_array($subjectType, ['person', 'meeting', 'bni_group', 'organization'])) {
throw new \InvalidArgumentException("Invalid subjectType.");
}
if ($subjectId <= 0) {
throw new \InvalidArgumentException("subjectId must be > 0.");
}
if (empty($processCode)) {
throw new \InvalidArgumentException("processCode cannot be empty.");
}
$inTransaction = $this->pdo->inTransaction();
if (!$inTransaction) { $this->pdo->beginTransaction(); }
try {
$sql = "SELECT * FROM process_definitions WHERE code = ? AND is_active = 1 AND subject_scope = ? ORDER BY version DESC, id DESC LIMIT 1";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([$processCode, $subjectType]);
$definition = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$definition) {
$stmt = $this->pdo->prepare("SELECT * FROM process_definitions WHERE code = ? AND is_active = 1 ORDER BY version DESC, id DESC LIMIT 1");
$stmt->execute([$processCode]);
$definition = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$definition) {
throw new WorkflowNotFoundException("No active process definition found for code '$processCode'");
}
}
$defId = $definition['id'];
$defJson = json_decode($definition['definition_json'], true);
$startNodeId = $defJson['start_node_id'] ?? null;
if (!$startNodeId) {
throw new \Exception("Process definition missing start_node_id");
}
$instance = null;
if ($mode === 'resume_or_create') {
$stmt = $this->pdo->prepare(
"SELECT pi.* FROM process_instances pi\n JOIN process_definitions pd ON pi.process_definition_id = pd.id\n WHERE pi.subject_type = ? AND pi.subject_id = ? AND pd.code = ?\n ORDER BY pi.id DESC LIMIT 1"
);
$stmt->execute([$subjectType, $subjectId, $processCode]);
$instance = $stmt->fetch(\PDO::FETCH_ASSOC);
}
if (!$instance) {
$stmt = $this->pdo->prepare(
"SELECT MAX(pi.run_number)
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 = ?"
);
$stmt->execute([$subjectType, $subjectId, $processCode]);
$maxRun = (int)$stmt->fetchColumn();
$runNumber = $maxRun + 1;
$personIdVal = ($subjectType === 'person') ? $subjectId : null;
$status = $defJson['nodes'][$startNodeId]['ui_hints']['status'] ?? 'in_progress';
$reason = $defJson['nodes'][$startNodeId]['ui_hints']['reason'] ?? '';
$nextStep = $defJson['nodes'][$startNodeId]['ui_hints']['next_step'] ?? '';
$stmt = $this->pdo->prepare(
"INSERT INTO process_instances
(subject_type, subject_id, person_id, process_definition_id, current_node_id, current_status, current_reason, suggested_next_step, run_number, last_activity_at, data_json)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), '{}')"
);
$stmt->execute([
$subjectType, $subjectId, $personIdVal, $defId, $startNodeId, $status, $reason, $nextStep, $runNumber
]);
$instanceId = $this->pdo->lastInsertId();
$this->addEvent($instanceId, 'system', 'Process started.', $startNodeId, [], $userId);
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE id = ?");
$stmt->execute([$instanceId]);
$instance = $stmt->fetch(\PDO::FETCH_ASSOC);
}
if (!$inTransaction) { $this->pdo->commit(); }
return [
'instance_id' => $instance['id'],
'process_definition_id' => $instance['process_definition_id'],
'process_code' => $processCode,
'subject_type' => $instance['subject_type'],
'subject_id' => $instance['subject_id'],
'person_id' => $instance['person_id'],
'current_node_id' => $instance['current_node_id'],
'current_status' => $instance['current_status'],
'last_activity_at' => $instance['last_activity_at'],
'data_json' => $instance['data_json']
];
} catch (\Exception $e) {
if (!$inTransaction) { $this->pdo->rollBack(); }
throw $e;
}
}
EOD;
$lastBracePos = strrpos($content, '}');
if ($lastBracePos !== false) {
$content = substr($content, 0, $lastBracePos) . $newMethod . "\n}
";
file_put_contents('WorkflowEngine.php', $content);
echo "Method added.\n";
} else {
echo "Failed to find closing brace.\n";
}

109
patch_we.py Normal file
View File

@ -0,0 +1,109 @@
import re
with open('WorkflowEngine.php', 'r') as f:
content = f.read()
new_method = """
public function getOrCreateInstanceBySubject(string $subjectType, int $subjectId, string $processCode, int $userId, string $mode='resume_or_create'): array {
if (!in_array($subjectType, ['person', 'meeting', 'bni_group', 'organization'])) {
throw new InvalidArgumentException("Invalid subjectType.");
}
if ($subjectId <= 0) {
throw new InvalidArgumentException("subjectId must be > 0.");
}
if (empty($processCode)) {
throw new InvalidArgumentException("processCode cannot be empty.");
}
$inTransaction = $this->pdo->inTransaction();
if (!$inTransaction) { $this->pdo->beginTransaction(); }
try {
$sql = "SELECT * FROM process_definitions WHERE code = ? AND is_active = 1 AND subject_scope = ? ORDER BY version DESC, id DESC LIMIT 1";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([$processCode, $subjectType]);
$definition = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$definition) {
$stmt = $this->pdo->prepare("SELECT * FROM process_definitions WHERE code = ? AND is_active = 1 ORDER BY version DESC, id DESC LIMIT 1");
$stmt->execute([$processCode]);
$definition = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$definition) {
throw new WorkflowNotFoundException("No active process definition found for code '$processCode'");
}
}
$defId = $definition['id'];
$defJson = json_decode($definition['definition_json'], true);
$startNodeId = $defJson['start_node_id'] ?? null;
if (!$startNodeId) {
throw new Exception("Process definition missing start_node_id");
}
$instance = null;
if ($mode === 'resume_or_create') {
$stmt = $this->pdo->prepare(
"SELECT pi.* FROM process_instances pi\n JOIN process_definitions pd ON pi.process_definition_id = pd.id\n WHERE pi.subject_type = ? AND pi.subject_id = ? AND pd.code = ?\n ORDER BY pi.id DESC LIMIT 1"
);
$stmt->execute([$subjectType, $subjectId, $processCode]);
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
}
if (!$instance) {
$stmt = $this->pdo->prepare(
"SELECT MAX(pi.run_number) \n FROM process_instances pi\n JOIN process_definitions pd ON pi.process_definition_id = pd.id\n WHERE pi.subject_type = ? AND pi.subject_id = ? AND pd.code = ?"
);
$stmt->execute([$subjectType, $subjectId, $processCode]);
$maxRun = (int)$stmt->fetchColumn();
$runNumber = $maxRun + 1;
$personIdVal = ($subjectType === 'person') ? $subjectId : null;
$status = $defJson['nodes'][$startNodeId]['ui_hints']['status'] ?? 'in_progress';
$reason = $defJson['nodes'][$startNodeId]['ui_hints']['reason'] ?? '';
$nextStep = $defJson['nodes'][$startNodeId]['ui_hints']['next_step'] ?? '';
$stmt = $this->pdo->prepare(
"INSERT INTO process_instances \n (subject_type, subject_id, person_id, process_definition_id, current_node_id, current_status, current_reason, suggested_next_step, run_number, last_activity_at, data_json)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), '{}')"
);
$stmt->execute([
$subjectType, $subjectId, $personIdVal, $defId, $startNodeId, $status, $reason, $nextStep, $runNumber
]);
$instanceId = $this->pdo->lastInsertId();
$this->addEvent($instanceId, 'system', 'Process started.', $startNodeId, [], $userId);
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE id = ?");
$stmt->execute([$instanceId]);
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
}
if (!$inTransaction) { $this->pdo->commit(); }
return [
'instance_id' => $instance['id'],
'process_definition_id' => $instance['process_definition_id'],
'process_code' => $processCode,
'subject_type' => $instance['subject_type'],
'subject_id' => $instance['subject_id'],
'person_id' => $instance['person_id'],
'current_node_id' => $instance['current_node_id'],
'current_status' => $instance['current_status'],
'last_activity_at' => $instance['last_activity_at'],
'data_json' => $instance['data_json']
];
} catch (Exception $e) {
if (!$inTransaction) { $this->pdo->rollBack(); }
throw $e;
}
}
"
if "function getOrCreateInstanceBySubject" not in content:
last_brace_idx = content.rfind('}')
content = content[:last_brace_idx] + new_method + "\n}\n"
with open('WorkflowEngine.php', 'w') as f:
f.write(content)
print("Method added.")
else:
print("Method already exists.")

View File

@ -41,7 +41,7 @@ $processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
<button class="btn btn-sm btn-secondary edit-process-btn" <button class="btn btn-sm btn-secondary edit-process-btn"
data-bs-toggle="modal" data-bs-target="#createProcessModal" data-bs-toggle="modal" data-bs-target="#createProcessModal"
data-process-id="<?= $process['id'] ?>" data-process-id="<?= $process['id'] ?>"
data-process-name="<?= htmlspecialchars($process['name']) ?>" data-process-name="<?= htmlspecialchars($process['name']) ?>" data-subject-scope="<?= htmlspecialchars($process['subject_scope'] ?? 'person') ?>"
data-process-definition='<?= htmlspecialchars($process['definition_json'] ?? '') ?>'> data-process-definition='<?= htmlspecialchars($process['definition_json'] ?? '') ?>'>
Edit Edit
</button> </button>
@ -67,6 +67,15 @@ $processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
<div class="modal-body"> <div class="modal-body">
<form id="createProcessForm" action="_save_process_definition.php" method="post"> <form id="createProcessForm" action="_save_process_definition.php" method="post">
<input type="hidden" name="process_id" id="processId"> <input type="hidden" name="process_id" id="processId">
<div class="mb-3">
<label for="subjectScope" class="form-label">Zakres procesu (Subject Scope)</label>
<select class="form-select" id="subjectScope" name="subject_scope" required>
<option value="person">Osoba (Person)</option>
<option value="meeting">Spotkanie (Meeting)</option>
<option value="bni_group">Grupa BNI (BNI Group)</option>
<option value="organization">Organizacja (Organization)</option>
</select>
</div>
<div class="mb-3"> <div class="mb-3">
<label for="processName" class="form-label">Process Name</label> <label for="processName" class="form-label">Process Name</label>
<input type="text" class="form-control" id="processName" name="name" required> <input type="text" class="form-control" id="processName" name="name" required>