Wiele razy ten sam proces do osoby
This commit is contained in:
parent
7717562504
commit
b1016e2a54
@ -57,7 +57,7 @@ class WorkflowEngine {
|
||||
$people = $stmt_people->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 4. Fetch all process definitions with their JSON
|
||||
$stmt_defs = $this->pdo->prepare("SELECT id, name, definition_json, is_active FROM process_definitions WHERE is_active = 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 ORDER BY sort_order, name");
|
||||
$stmt_defs->execute();
|
||||
$process_definitions_raw = $stmt_defs->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
@ -66,6 +66,7 @@ class WorkflowEngine {
|
||||
foreach ($process_definitions_raw as $def) {
|
||||
$definitions[$def['id']] = [
|
||||
'id' => $def['id'],
|
||||
'code' => $def['code'],
|
||||
'name' => $def['name'],
|
||||
'is_active' => $def['is_active']
|
||||
];
|
||||
@ -122,7 +123,16 @@ class WorkflowEngine {
|
||||
$enriched_instance['computed_next_step'] = $instance['suggested_next_step'];
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,6 +523,49 @@ class WorkflowEngine {
|
||||
$stmt->execute([$instanceId, $eventType, $message, $nodeId, json_encode($payload), $userId]);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public function getOrCreateInstanceByDefId(int $personId, int $processDefinitionId, int $userId, array $context = []): ?array {
|
||||
if (!is_int($processDefinitionId) || $processDefinitionId <= 0) {
|
||||
throw new InvalidArgumentException("processDefinitionId must be a positive integer.");
|
||||
@ -521,9 +574,7 @@ class WorkflowEngine {
|
||||
throw new InvalidArgumentException("personId must be a positive integer.");
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE `person_id` = ? AND `process_definition_id` = ?");
|
||||
$stmt->execute([$personId, $processDefinitionId]);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$instance = $this->getInstanceByDefId($personId, $processDefinitionId);
|
||||
|
||||
if (!$instance) {
|
||||
$stmt_def = $this->pdo->prepare("SELECT definition_json, code, is_active FROM process_definitions WHERE id = ?");
|
||||
@ -562,8 +613,21 @@ class WorkflowEngine {
|
||||
}
|
||||
|
||||
public function getInstanceByDefId(int $personId, int $processDefinitionId): ?array {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE `person_id` = ? AND `process_definition_id` = ?");
|
||||
$stmt->execute([$personId, $processDefinitionId]);
|
||||
$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("
|
||||
SELECT pi.*
|
||||
FROM process_instances pi
|
||||
JOIN process_definitions pd ON pi.process_definition_id = pd.id
|
||||
WHERE pi.person_id = ? AND pd.code = ?
|
||||
ORDER BY pi.last_activity_at DESC, pi.id DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$personId, $code]);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $instance ?: null;
|
||||
}
|
||||
|
||||
@ -54,6 +54,57 @@ $instance = $engine->getInstanceByDefId($person_id, $process_definition_id);
|
||||
<?php if ($instance): // INSTANCE EXISTS ?>
|
||||
<?php
|
||||
$instanceId = $instance['id'];
|
||||
$isCompleted = in_array($instance['current_status'], ['completed', 'positive', 'negative', 'error']);
|
||||
?>
|
||||
<?php if ($isCompleted): ?>
|
||||
<div class="alert alert-info d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong><?= t('modal.process_completed', 'Proces zakończony.') ?></strong>
|
||||
Obecny status: <?= htmlspecialchars($instance['current_status']) ?>.
|
||||
</div>
|
||||
<button id="startNewProcessBtn" class="btn btn-sm btn-primary" data-person-id="<?= $person_id ?>" data-process-id="<?= $process_definition_id ?>">
|
||||
<?= t('modal.start_new_instance', 'Rozpocznij od nowa') ?>
|
||||
</button>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('startNewProcessBtn')?.addEventListener('click', function() {
|
||||
const personId = this.getAttribute('data-person-id');
|
||||
const processId = this.getAttribute('data-process-id');
|
||||
if (confirm('Czy na pewno chcesz rozpocząć nową instancję tego procesu? Dotychczasowa historia pozostanie, ale proces rozpocznie się od nowa.')) {
|
||||
fetch('_init_single_instance.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'person_id=' + personId + '&process_id=' + processId + '&force=1'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Reload the modal content
|
||||
fetch('_get_instance_details.php?person_id=' + personId + '&process_id=' + processId)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
document.querySelector('#instanceModal .modal-body').innerHTML = html;
|
||||
// Trigger custom event to re-attach handlers and update title
|
||||
const event = new Event('instanceModalLoaded');
|
||||
document.querySelector('#instanceModal .modal-body').dispatchEvent(event);
|
||||
// Set a flag to refresh the matrix when the modal closes
|
||||
window.matrixNeedsRefresh = true;
|
||||
});
|
||||
} else {
|
||||
alert('Błąd: ' + (data.error || 'Nieznany błąd'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Wystąpił błąd podczas uruchamiania nowej instancji.');
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$definition = $process['definition_json'] ? json_decode($process['definition_json'], true) : null;
|
||||
$isChecklist = ($definition && isset($definition['type']) && $definition['type'] === 'checklist');
|
||||
$events = $engine->getEvents($instanceId);
|
||||
|
||||
@ -42,7 +42,12 @@ if($deleteExisting === '1') {
|
||||
// 3. Checking if the person is eligible.
|
||||
// 4. Creating the instance if it doesn't exist.
|
||||
// It will throw specific exceptions (WorkflowNotFoundException, WorkflowNotAllowedException, WorkflowEligibilityException) which our ErrorHandler will turn into 404, 409, and 422 responses.
|
||||
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
|
||||
= filter_input(INPUT_POST, 'force', FILTER_VALIDATE_INT);
|
||||
if ( === 1) {
|
||||
= ->createNewInstance(, , );
|
||||
} else {
|
||||
= ->getOrCreateInstanceByDefId(, , );
|
||||
}
|
||||
|
||||
if ($instance) {
|
||||
echo json_encode(['success' => true, 'message' => 'Process initialized successfully.', 'instance_id' => $instance['id']]);
|
||||
|
||||
@ -108,19 +108,36 @@ try {
|
||||
|
||||
if (empty($processId)) {
|
||||
// Create new process
|
||||
$sql = 'INSERT INTO process_definitions (name, code, definition_json, start_node_id, is_active) VALUES (?, ?, ?, ?, 1)';
|
||||
$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
|
||||
$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.';
|
||||
}
|
||||
// "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'];
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
// 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.');
|
||||
}
|
||||
}
|
||||
if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['message' => $message]);
|
||||
|
||||
57
db/migrations/036_process_versioning.php
Normal file
57
db/migrations/036_process_versioning.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
function migrate_036($pdo) {
|
||||
echo "Starting migration 036 (Process Versioning)...
|
||||
";
|
||||
|
||||
// 1. Process Definitions
|
||||
$stmt = $pdo->query("SHOW INDEX FROM process_definitions WHERE Key_name = 'code'");
|
||||
if ($stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_definitions DROP INDEX `code`");
|
||||
echo "Dropped unique index on process_definitions.code.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM process_definitions LIKE 'supersedes_definition_id'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_definitions ADD COLUMN supersedes_definition_id INT(11) UNSIGNED NULL AFTER version");
|
||||
echo "Added supersedes_definition_id to process_definitions.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM process_definitions LIKE 'is_latest'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_definitions ADD COLUMN is_latest TINYINT(1) NOT NULL DEFAULT 1 AFTER supersedes_definition_id");
|
||||
echo "Added is_latest to process_definitions.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW INDEX FROM process_definitions WHERE Key_name = 'idx_code_latest_active'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_definitions ADD INDEX idx_code_latest_active (code, is_latest, is_active)");
|
||||
echo "Added index idx_code_latest_active on process_definitions.
|
||||
";
|
||||
}
|
||||
|
||||
// Ensure existing rows are marked as latest properly (all existing should be latest)
|
||||
// Nothing needed, default is 1.
|
||||
|
||||
// 2. Process Instances
|
||||
$stmt = $pdo->query("SHOW INDEX FROM process_instances WHERE Key_name = 'person_process'");
|
||||
if ($stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances DROP INDEX `person_process`");
|
||||
echo "Dropped unique index on process_instances.person_process.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW INDEX FROM process_instances WHERE Key_name = 'idx_person_definition'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD INDEX idx_person_definition (person_id, process_definition_id)");
|
||||
echo "Added index idx_person_definition on process_instances.
|
||||
";
|
||||
}
|
||||
|
||||
echo "Migration 036 completed.
|
||||
";
|
||||
}
|
||||
|
||||
@ -1392,6 +1392,13 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
instanceModalElement.addEventListener('hidden.bs.modal', function () {
|
||||
if (window.matrixNeedsRefresh) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php include '_footer.php'; ?>
|
||||
|
||||
17
patch2.py
Normal file
17
patch2.py
Normal file
@ -0,0 +1,17 @@
|
||||
import re
|
||||
|
||||
with open('WorkflowEngine.php', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
target = """ $stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE `person_id` = ? AND `process_definition_id` = ?");
|
||||
$stmt->execute([$personId, $processDefinitionId]);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);"""
|
||||
|
||||
replacement = """ $instance = $this->getInstanceByDefId($personId, $processDefinitionId);"""
|
||||
|
||||
content = content.replace(target, replacement)
|
||||
|
||||
with open('WorkflowEngine.php', 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
print("Patched.")
|
||||
71
patch_engine.py
Normal file
71
patch_engine.py
Normal file
@ -0,0 +1,71 @@
|
||||
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.")
|
||||
21
patch_index.py
Normal file
21
patch_index.py
Normal file
@ -0,0 +1,21 @@
|
||||
import re
|
||||
|
||||
with open('index.php', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
patch_code = """
|
||||
instanceModalElement.addEventListener('hidden.bs.modal', function () {
|
||||
if (window.matrixNeedsRefresh) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
"""
|
||||
|
||||
content = content.replace("});\n</script>", patch_code)
|
||||
|
||||
with open('index.php', 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
print("Patched index.php")
|
||||
59
patch_init.py
Normal file
59
patch_init.py
Normal file
@ -0,0 +1,59 @@
|
||||
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.")
|
||||
73
patch_modal.py
Normal file
73
patch_modal.py
Normal file
@ -0,0 +1,73 @@
|
||||
import re
|
||||
|
||||
with open('_get_instance_details.php', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
patch_code = """<?php if ($instance): // INSTANCE EXISTS ?>
|
||||
<?php
|
||||
$instanceId = $instance['id'];
|
||||
$isCompleted = in_array($instance['current_status'], ['completed', 'positive', 'negative', 'error']);
|
||||
?>
|
||||
<?php if ($isCompleted): ?>
|
||||
<div class="alert alert-info d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong><?= t('modal.process_completed', 'Proces zakończony.') ?></strong>
|
||||
Obecny status: <?= htmlspecialchars($instance['current_status']) ?>.
|
||||
</div>
|
||||
<button id="startNewProcessBtn" class="btn btn-sm btn-primary" data-person-id="<?= $person_id ?>" data-process-id="<?= $process_definition_id ?>">
|
||||
<?= t('modal.start_new_instance', 'Rozpocznij od nowa') ?>
|
||||
</button>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('startNewProcessBtn')?.addEventListener('click', function() {
|
||||
const personId = this.getAttribute('data-person-id');
|
||||
const processId = this.getAttribute('data-process-id');
|
||||
if (confirm('Czy na pewno chcesz rozpocząć nową instancję tego procesu? Dotychczasowa historia pozostanie, ale proces rozpocznie się od nowa.')) {
|
||||
fetch('_init_single_instance.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'person_id=' + personId + '&process_id=' + processId + '&force=1'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Reload the modal content
|
||||
fetch('_get_instance_details.php?person_id=' + personId + '&process_id=' + processId)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
document.getElementById('instanceModalBody').innerHTML = html;
|
||||
// Trigger custom event to re-attach handlers and update title
|
||||
const event = new Event('instanceModalLoaded');
|
||||
document.getElementById('instanceModalBody').dispatchEvent(event);
|
||||
// Set a flag to refresh the matrix when the modal closes
|
||||
window.matrixNeedsRefresh = true;
|
||||
});
|
||||
} else {
|
||||
alert('Błąd: ' + (data.error || 'Nieznany błąd'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Wystąpił błąd podczas uruchamiania nowej instancji.');
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$definition = $process['definition_json'] ? json_decode($process['definition_json'], true) : null;"""
|
||||
|
||||
content = content.replace(
|
||||
"""<?php if ($instance): // INSTANCE EXISTS ?>
|
||||
<?php
|
||||
$instanceId = $instance['id'];
|
||||
$definition = $process['definition_json'] ? json_decode($process['definition_json'], true) : null;""",
|
||||
patch_code
|
||||
)
|
||||
|
||||
with open('_get_instance_details.php', 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
print("Patched.")
|
||||
60
patch_save.py
Normal file
60
patch_save.py
Normal file
@ -0,0 +1,60 @@
|
||||
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.")
|
||||
@ -4,7 +4,7 @@ require_once 'WorkflowEngine.php';
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
// TODO: Create a method in WorkflowEngine to get all process definitions
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT * FROM process_definitions WHERE is_active = 1 ORDER BY sort_order, name");
|
||||
$stmt = $pdo->query("SELECT * FROM process_definitions WHERE is_active = 1 AND is_latest = 1 ORDER BY sort_order, name");
|
||||
$processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
187
temp_matrix.php
Normal file
187
temp_matrix.php
Normal file
@ -0,0 +1,187 @@
|
||||
public function getDashboardMatrix(?string $searchTerm = null, ?int $groupId = null, ?int $activeProcessDefinitionId = null, ?int $meetingFilterGroupId = null, ?string $meetingFilterDatetime = null): array {
|
||||
// 1. Base query for people
|
||||
$sql_people = "SELECT p.*, bg.name as bni_group_name FROM people p LEFT JOIN bni_groups bg ON p.bni_group_id = bg.id";
|
||||
$params = [];
|
||||
$where_clauses = [];
|
||||
|
||||
// 2. Add filter conditions
|
||||
if ($searchTerm) {
|
||||
$where_clauses[] = "(p.first_name LIKE :search OR p.last_name LIKE :search OR p.company_name LIKE :search OR p.email LIKE :search)";
|
||||
$params[':search'] = '%' . $searchTerm . '%';
|
||||
}
|
||||
|
||||
if ($groupId) {
|
||||
$where_clauses[] = "p.bni_group_id = :group_id";
|
||||
$params[':group_id'] = $groupId;
|
||||
}
|
||||
|
||||
if ($activeProcessDefinitionId) {
|
||||
$terminal_statuses = ['positive', 'negative', 'completed', 'error', 'inactive'];
|
||||
$in_clause = implode(',', array_map([$this->pdo, 'quote'], $terminal_statuses));
|
||||
|
||||
$sql_people .= " INNER JOIN process_instances pi ON p.id = pi.person_id";
|
||||
$where_clauses[] = "pi.process_definition_id = :active_process_id AND (pi.current_status IS NOT NULL AND pi.current_status NOT IN ($in_clause))";
|
||||
$params[':active_process_id'] = $activeProcessDefinitionId;
|
||||
}
|
||||
|
||||
if ($meetingFilterGroupId && $meetingFilterDatetime) {
|
||||
$meetingId = $this->getOrCreateMeeting($meetingFilterGroupId, $meetingFilterDatetime);
|
||||
$sql_people .= " INNER JOIN meeting_attendance ma ON p.id = ma.person_id";
|
||||
$where_clauses[] = "ma.meeting_id = :meeting_id";
|
||||
$where_clauses[] = "ma.attendance_status IN ('present', 'absent', 'substitute')";
|
||||
$params[':meeting_id'] = $meetingId;
|
||||
}
|
||||
|
||||
if (!empty($where_clauses)) {
|
||||
$sql_people .= " WHERE " . implode(" AND ", $where_clauses);
|
||||
}
|
||||
|
||||
$sql_people .= " ORDER BY p.last_name, p.first_name";
|
||||
|
||||
// 3. Execute query to get filtered people
|
||||
$stmt_people = $this->pdo->prepare($sql_people);
|
||||
$stmt_people->execute($params);
|
||||
$people = $stmt_people->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 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->execute();
|
||||
$process_definitions_raw = $stmt_defs->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$definitions = [];
|
||||
$definition_map = [];
|
||||
foreach ($process_definitions_raw as $def) {
|
||||
$definitions[$def['id']] = [
|
||||
'id' => $def['id'],
|
||||
'code' => $def['code'],
|
||||
'name' => $def['name'],
|
||||
'is_active' => $def['is_active']
|
||||
];
|
||||
$definition_map[$def['id']] = !empty($def['definition_json']) ? json_decode($def['definition_json'], true) : null;
|
||||
}
|
||||
|
||||
// 5. Fetch instances ONLY for the filtered people
|
||||
$instances = [];
|
||||
$person_ids = array_column($people, 'id');
|
||||
if (!empty($person_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($person_ids), '?'));
|
||||
$stmt_instances = $this->pdo->prepare("SELECT * FROM process_instances WHERE person_id IN ($placeholders)");
|
||||
$stmt_instances->execute($person_ids);
|
||||
$instances_data = $stmt_instances->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($instances_data as $instance) {
|
||||
$enriched_instance = $instance;
|
||||
$def_id = $instance['process_definition_id'];
|
||||
$node_id = $instance['current_node_id'];
|
||||
|
||||
$definition = $definition_map[$def_id] ?? null;
|
||||
|
||||
if ($definition && isset($definition['type']) && $definition['type'] === 'checklist') {
|
||||
$tasks = $definition['tasks'] ?? [];
|
||||
$instanceData = $instance['data_json'] ? json_decode($instance['data_json'], true) : [];
|
||||
$totalTasks = count($tasks);
|
||||
$completedTasks = 0;
|
||||
if(is_array($instanceData)) {
|
||||
foreach ($tasks as $task) {
|
||||
if (!empty($instanceData[$task['code']])) {
|
||||
$completedTasks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($totalTasks > 0 && $completedTasks === $totalTasks) {
|
||||
$status = 'completed';
|
||||
} elseif ($completedTasks > 0) {
|
||||
$status = 'in_progress';
|
||||
} else {
|
||||
$status = 'inactive';
|
||||
}
|
||||
$enriched_instance['computed_status'] = $status;
|
||||
$enriched_instance['computed_reason'] = "$completedTasks/$totalTasks completed";
|
||||
$enriched_instance['computed_next_step'] = '';
|
||||
} else if ($definition && isset($definition['nodes'][$node_id])) {
|
||||
$node_info = $definition['nodes'][$node_id];
|
||||
$enriched_instance['computed_status'] = $node_info['ui_hints']['status'] ?? $instance['current_status'];
|
||||
$enriched_instance['computed_reason'] = $node_info['ui_hints']['reason'] ?? $instance['current_reason'];
|
||||
$enriched_instance['computed_next_step'] = $node_info['ui_hints']['next_step'] ?? $instance['suggested_next_step'];
|
||||
} else {
|
||||
$enriched_instance['computed_status'] = $instance['current_status'];
|
||||
$enriched_instance['computed_reason'] = $instance['current_reason'];
|
||||
$enriched_instance['computed_next_step'] = $instance['suggested_next_step'];
|
||||
}
|
||||
|
||||
$instances[$instance['person_id']][$def_id] = $enriched_instance;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Fetch ancillary data
|
||||
$stmt_functions = $this->pdo->query("SELECT * FROM functions ORDER BY display_order");
|
||||
$all_functions = $stmt_functions->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$stmt_person_functions = $this->pdo->query("SELECT user_id, function_id FROM user_functions");
|
||||
$person_functions_map = [];
|
||||
while ($row = $stmt_person_functions->fetch(PDO::FETCH_ASSOC)) {
|
||||
$person_functions_map[$row['user_id']][] = $row['function_id'];
|
||||
}
|
||||
|
||||
$stmt_bni_groups = $this->pdo->query("SELECT * FROM bni_groups ORDER BY name");
|
||||
$bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 7. Fetch Spotkania columns (upcoming meetings)
|
||||
$today = date('Y-m-d H:i:s');
|
||||
$stmt_meetings = $this->pdo->prepare("
|
||||
WITH RankedMeetings AS (
|
||||
SELECT
|
||||
bg.id as group_id,
|
||||
bg.name as group_name,
|
||||
ce.start_datetime,
|
||||
ROW_NUMBER() OVER(PARTITION BY bg.id ORDER BY ce.start_datetime) as rn
|
||||
FROM bni_groups bg
|
||||
JOIN calendar_event_groups ceg ON bg.id = ceg.bni_group_id
|
||||
JOIN calendar_events ce ON ceg.calendar_event_id = ce.id
|
||||
WHERE ce.start_datetime >= :today
|
||||
)
|
||||
SELECT group_id, group_name, start_datetime
|
||||
FROM RankedMeetings
|
||||
WHERE rn <= 3
|
||||
ORDER BY group_id, start_datetime;
|
||||
");
|
||||
$stmt_meetings->execute(['today' => $today]);
|
||||
$upcoming_meetings_flat = $stmt_meetings->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$spotkania_cols = [];
|
||||
foreach ($upcoming_meetings_flat as $meeting) {
|
||||
$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'];
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
'people' => $people,
|
||||
'definitions' => array_values($definitions),
|
||||
'instances' => $instances,
|
||||
'all_functions' => $all_functions,
|
||||
'person_functions_map' => $person_functions_map,
|
||||
'bni_groups' => $bni_groups,
|
||||
'spotkania_cols' => $spotkania_cols, // Add this to the return array
|
||||
];
|
||||
}
|
||||
|
||||
public function startProcess(string $processCode, int $personId, int $userId): int {
|
||||
$inTransaction = $this->pdo->inTransaction();
|
||||
if (!$inTransaction) { $this->pdo->beginTransaction(); }
|
||||
try {
|
||||
// 1. Find active process definition by code.
|
||||
$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 process definition is found, check if there is a definition for a checklist
|
||||
$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 with code or id '$processCode' not found.");
|
||||
Loading…
x
Reference in New Issue
Block a user