Duże poprawki
This commit is contained in:
parent
6460ff3ac8
commit
44d4fa5a60
@ -11,19 +11,45 @@ class WorkflowEngine {
|
||||
$this->pdo = db();
|
||||
}
|
||||
|
||||
public function getDashboardMatrix(): array {
|
||||
// Get all people (potential assignees)
|
||||
$stmt_people = $this->pdo->prepare("
|
||||
SELECT p.*, bg.name as bni_group_name
|
||||
FROM people p
|
||||
LEFT JOIN bni_groups bg ON p.bni_group_id = bg.id
|
||||
ORDER BY p.last_name, p.first_name
|
||||
");
|
||||
$stmt_people->execute();
|
||||
public function getDashboardMatrix(?string $searchTerm = null, ?int $groupId = null, ?int $activeProcessDefinitionId = 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 (!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);
|
||||
|
||||
// Fetch all process definitions with their JSON
|
||||
$stmt_defs = $this->pdo->prepare("SELECT id, name, definition_json FROM process_definitions ORDER BY name");
|
||||
// 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->execute();
|
||||
$process_definitions_raw = $stmt_defs->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
@ -38,71 +64,61 @@ class WorkflowEngine {
|
||||
$definition_map[$def['id']] = !empty($def['definition_json']) ? json_decode($def['definition_json'], true) : null;
|
||||
}
|
||||
|
||||
// Fetch instances
|
||||
$stmt_instances = $this->pdo->prepare("SELECT * FROM process_instances");
|
||||
$stmt_instances->execute();
|
||||
$instances_data = $stmt_instances->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 5. Fetch instances ONLY for the filtered people
|
||||
$instances = [];
|
||||
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++;
|
||||
$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';
|
||||
|
||||
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 {
|
||||
$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;
|
||||
}
|
||||
|
||||
// Remove pre-emptive eligibility check. This is now handled on-demand by _get_instance_details.php
|
||||
/*
|
||||
foreach ($people as $person) {
|
||||
foreach ($definitions as $def) {
|
||||
if (!isset($instances[$person['id']][$def['id']])) {
|
||||
$process_definition_raw = $process_definitions_raw[array_search($def['id'], array_column($process_definitions_raw, 'id'))];
|
||||
$eligibility = $this->checkEligibility($person['id'], $process_definition_raw);
|
||||
$instances[$person['id']][$def['id']] = ['is_eligible' => $eligibility['is_eligible']];
|
||||
$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;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Fetch ancillary data
|
||||
// 6. Fetch ancillary data
|
||||
$stmt_functions = $this->pdo->query("SELECT * FROM functions ORDER BY display_order");
|
||||
$all_functions = $stmt_functions->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
@ -115,6 +131,18 @@ class WorkflowEngine {
|
||||
$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("
|
||||
SELECT bni_groups.id as group_id, bni_groups.name as group_name, MIN(calendar_events.start_datetime) as next_meeting_date
|
||||
FROM bni_groups
|
||||
LEFT JOIN calendar_event_groups ON bni_groups.id = calendar_event_groups.bni_group_id
|
||||
LEFT JOIN calendar_events ON calendar_event_groups.calendar_event_id = calendar_events.id AND calendar_events.start_datetime >= :today
|
||||
GROUP BY bni_groups.id
|
||||
ORDER BY bni_groups.name
|
||||
");
|
||||
$stmt_meetings->execute(['today' => $today]);
|
||||
$spotkania_cols = $stmt_meetings->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return [
|
||||
'people' => $people,
|
||||
@ -123,6 +151,7 @@ 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
|
||||
];
|
||||
}
|
||||
|
||||
@ -698,4 +727,22 @@ class WorkflowEngine {
|
||||
// Also update the in-memory instance for the next step in the chain
|
||||
$instance['data_json'] = $newDataJson;
|
||||
}
|
||||
|
||||
public function deleteInstance(int $instanceId): void {
|
||||
$this->pdo->beginTransaction();
|
||||
try {
|
||||
// Delete events
|
||||
$stmt_events = $this->pdo->prepare("DELETE FROM process_events WHERE process_instance_id = ?");
|
||||
$stmt_events->execute([$instanceId]);
|
||||
|
||||
// Delete instance
|
||||
$stmt_instance = $this->pdo->prepare("DELETE FROM process_instances WHERE id = ?");
|
||||
$stmt_instance->execute([$instanceId]);
|
||||
|
||||
$this->pdo->commit();
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,6 +102,7 @@ $instance = $engine->getInstanceByDefId($person_id, $process_definition_id);
|
||||
<h5>Kroki procesu</h5>
|
||||
<ul class="list-group">
|
||||
<?php foreach ($all_nodes as $nodeId => $node):
|
||||
if (!isset($node['ui_hints']['title']) || $node['ui_hints']['title'] === '') continue;
|
||||
$is_current = ($currentNodeId === $nodeId);
|
||||
$is_completed = isset($visited_nodes[$nodeId]) && !$is_current;
|
||||
|
||||
@ -122,7 +123,7 @@ $instance = $engine->getInstanceByDefId($person_id, $process_definition_id);
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center <?= $li_class ?>">
|
||||
<div>
|
||||
<?= $status_icon ?>
|
||||
<strong><?= htmlspecialchars($node['name']) ?></strong>
|
||||
<strong><?= htmlspecialchars($node['ui_hints']['title']) ?></strong>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
@ -139,8 +140,14 @@ $instance = $engine->getInstanceByDefId($person_id, $process_definition_id);
|
||||
<label for="<?= $field['name'] ?>" class="form-label"><?= $field['label'] ?></label>
|
||||
<?php if ($field['type'] === 'textarea'): ?>
|
||||
<textarea id="<?= $field['name'] ?>" name="<?= $field['name'] ?>" class="form-control"></textarea>
|
||||
<?php elseif ($field['type'] === 'select'): ?>
|
||||
<select id="<?= $field['name'] ?>" name="<?= $field['name'] ?>" class="form-select">
|
||||
<?php foreach ($field['options'] as $option): ?>
|
||||
<option value="<?= $option['value'] ?>"><?= $option['label'] ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php else: ?>
|
||||
<input type="<?= $field['type'] ?>" id="<?= $field['name'] ?>" name="<?= $field['name'] ?>" class="form-control" value="<?= $field['default'] === 'now' ? date('Y-m-d\\TH:i') : '' ?>">
|
||||
<input type="<?= $field['type'] ?>" id="<?= $field['name'] ?>" name="<?= $field['name'] ?>" class="form-control" value="<?= ($field['default'] ?? '') === 'now' ? date('Y-m-d\\TH:i') : '' ?>">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
@ -178,18 +185,56 @@ $instance = $engine->getInstanceByDefId($person_id, $process_definition_id);
|
||||
<?php if (empty($events)): ?>
|
||||
<p>Brak zdarzeń.</p>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
// Prepare a map for outcome_status labels for readability
|
||||
$outcomeStatusOptions = [];
|
||||
if (isset($definition['nodes']['awaiting_call']['ui_hints']['form_schema'])) {
|
||||
foreach ($definition['nodes']['awaiting_call']['ui_hints']['form_schema'] as $field) {
|
||||
if ($field['name'] === 'outcome_status' && isset($field['options'])) {
|
||||
foreach ($field['options'] as $option) {
|
||||
if (!empty($option['value'])) {
|
||||
$outcomeStatusOptions[$option['value']] = $option['label'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<ul class="list-group">
|
||||
<?php foreach ($events as $event): ?>
|
||||
<?php
|
||||
$payload = json_decode($event['payload_json'], true);
|
||||
$isCallAttempt = $event['event_type'] === 'transition_completed' && isset($payload['transition_id']) && $payload['transition_id'] === 'submit_outcome';
|
||||
?>
|
||||
<li class="list-group-item">
|
||||
<strong><?= htmlspecialchars(ucfirst(str_replace('_', ' ', $event['event_type']))) ?></strong>
|
||||
<?php
|
||||
if (!empty($event['message'])) {
|
||||
$payload = json_decode($event['payload_json'], true);
|
||||
$message = $payload['message'] ?? $event['message'];
|
||||
echo '<p class="mb-1 text-muted fst-italic">' . htmlspecialchars($message) . '</p>';
|
||||
}
|
||||
?>
|
||||
<small class="text-muted">Przez <?= htmlspecialchars($event['first_name'] . ' ' . $event['last_name']) ?> dnia <?= date('d.m.Y, H:i', strtotime($event['created_at'])) ?></small>
|
||||
<?php if ($isCallAttempt): ?>
|
||||
<?php
|
||||
$data = $payload['data'] ?? [];
|
||||
$outcomeLabel = $outcomeStatusOptions[$data['outcome_status']] ?? ucfirst(str_replace('_', ' ', $data['outcome_status']));
|
||||
?>
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1">Call Attempt</h6>
|
||||
<small><?= date('d.m.Y, H:i', strtotime($data['call_date'])) ?></small>
|
||||
</div>
|
||||
<p class="mb-1"><strong>Outcome:</strong> <?= htmlspecialchars($outcomeLabel) ?></p>
|
||||
<?php if (!empty($data['note'])): ?>
|
||||
<p class="mb-1 fst-italic">"<?= nl2br(htmlspecialchars($data['note'])) ?>"</p>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($data['next_contact_date'])): ?>
|
||||
<p class="mb-1"><strong>Next follow-up:</strong> <?= date('d.m.Y, H:i', strtotime($data['next_contact_date'])) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php else: // Generic event display ?>
|
||||
<strong><?= htmlspecialchars(ucfirst(str_replace('_', ' ', $event['event_type']))) ?></strong>
|
||||
<?php
|
||||
if (!empty($event['message'])) {
|
||||
$message = $payload['message'] ?? $event['message'];
|
||||
echo '<p class="mb-1 text-muted fst-italic">' . htmlspecialchars($message) . '</p>';
|
||||
}
|
||||
?>
|
||||
<?php endif; ?>
|
||||
<small class="text-muted">By <?= htmlspecialchars($event['first_name'] . ' ' . $event['last_name']) ?> on <?= date('d.m.Y, H:i', strtotime($event['created_at'])) ?></small>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/lib/ErrorHandler.php';
|
||||
register_error_handler();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
|
||||
@ -20,6 +20,7 @@ if (!isset($_SESSION['user_id'])) {
|
||||
$userId = $_SESSION['user_id'];
|
||||
$personId = filter_input(INPUT_POST, 'person_id', FILTER_VALIDATE_INT);
|
||||
$processDefinitionId = filter_input(INPUT_POST, 'process_id', FILTER_VALIDATE_INT);
|
||||
$deleteExisting = filter_input(INPUT_POST, 'delete_existing');
|
||||
|
||||
if (!$personId || !$processDefinitionId) {
|
||||
// InvalidArgumentException will be caught by the handler and result in a 400 Bad Request
|
||||
@ -28,6 +29,13 @@ if (!$personId || !$processDefinitionId) {
|
||||
|
||||
$engine = new WorkflowEngine();
|
||||
|
||||
if($deleteExisting === '1') {
|
||||
$instance = $engine->getInstanceByDefId($personId, $processDefinitionId);
|
||||
if ($instance) {
|
||||
$engine->deleteInstance($instance['id']);
|
||||
}
|
||||
}
|
||||
|
||||
// The getOrCreateInstanceByDefId method is now responsible for all checks:
|
||||
// 1. Validating the process definition exists.
|
||||
// 2. Checking if the process is active.
|
||||
@ -41,4 +49,4 @@ if ($instance) {
|
||||
} else {
|
||||
// This case should not be reached if the engine works as expected, as failures should throw exceptions.
|
||||
throw new Exception("Failed to initialize process for an unknown reason.");
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ function validate_definition_json($json) {
|
||||
throw new WorkflowRuleFailedException('Invalid JSON format in definition.');
|
||||
}
|
||||
|
||||
$allowed_statuses = ['none', 'negative', 'in_progress', 'positive'];
|
||||
$allowed_statuses = ['none', 'negative', 'in_progress', 'positive', 'active', 'processing', 'paused', 'completed', 'terminated'];
|
||||
|
||||
if (isset($data['nodes'])) {
|
||||
foreach ($data['nodes'] as $node) {
|
||||
@ -75,8 +75,8 @@ try {
|
||||
} else {
|
||||
// Update existing process
|
||||
$is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : 0;
|
||||
$sql = 'UPDATE process_definitions SET name = ?, code = ?, definition_json = ?, is_active = ? WHERE id = ?';
|
||||
$params = [$name, $code, $definition_json, $is_active, $processId];
|
||||
$sql = 'UPDATE process_definitions SET name = ?, definition_json = ?, is_active = ? WHERE id = ?';
|
||||
$params = [$name, $definition_json, $is_active, $processId];
|
||||
$message = 'Process updated successfully.';
|
||||
}
|
||||
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
# https://curl.se/docs/http-cookies.html
|
||||
# This file was generated by libcurl! Edit at your own risk.
|
||||
|
||||
127.0.0.1 FALSE / FALSE 0 PHPSESSID abf9a8g0sidv8idojcrr65jetp
|
||||
localhost FALSE / FALSE 0 PHPSESSID rfo6k0p8l4tpnmgek7dkpkopbl
|
||||
|
||||
130
current_definition.json
Normal file
130
current_definition.json
Normal file
@ -0,0 +1,130 @@
|
||||
{
|
||||
"start_node_id": "awaiting_call",
|
||||
"eligibility_rules": [
|
||||
{
|
||||
"type": "person_property_equals",
|
||||
"params": {
|
||||
"property": "role",
|
||||
"value": "guest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"nodes": {
|
||||
"awaiting_call": {
|
||||
"ui_hints": {
|
||||
"title": "Follow-up Call",
|
||||
"status": "active",
|
||||
"reason": "Awaiting follow-up call with the guest.",
|
||||
"next_step": "Log the outcome of the call.",
|
||||
"form_schema": [
|
||||
{ "name": "call_date", "label": "Call Date", "type": "datetime-local", "default": "now", "required": true },
|
||||
{
|
||||
"name": "outcome_status",
|
||||
"label": "Call Outcome",
|
||||
"type": "select",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "value": "", "label": "-- Select Outcome --" },
|
||||
{ "value": "no_answer", "label": "No Answer" },
|
||||
{ "value": "wants_to_join", "label": "Wants to Join" },
|
||||
{ "value": "declined", "label": "Declined" },
|
||||
{ "value": "call_later", "label": "Call Later" }
|
||||
]
|
||||
},
|
||||
{ "name": "note", "label": "Notes", "type": "textarea" },
|
||||
{ "name": "next_contact_date", "label": "Next Contact Date", "type": "datetime-local", "condition": { "field": "outcome_status", "value": "call_later" }, "required": true }
|
||||
]
|
||||
}
|
||||
},
|
||||
"outcome_router": { "ui_hints": { "status": "processing" } },
|
||||
"waiting_for_next_contact": {
|
||||
"ui_hints": {
|
||||
"title": "Waiting for Scheduled Call",
|
||||
"status": "paused",
|
||||
"reason": "Waiting until the scheduled date for the next call.",
|
||||
"next_step": "Resume contact on or after the scheduled date."
|
||||
}
|
||||
},
|
||||
"decide_after_no_answer": {
|
||||
"ui_hints": {
|
||||
"title": "No Answer",
|
||||
"status": "paused",
|
||||
"reason": "The guest did not answer the call.",
|
||||
"next_step": "Decide whether to try again or end the process."
|
||||
}
|
||||
},
|
||||
"end_positive": { "ui_hints": { "title": "Wants to Join", "status": "completed", "reason": "Guest wants to join. New member process started.", "next_step": "" } },
|
||||
"end_negative_declined": { "ui_hints": { "title": "Declined", "status": "terminated", "reason": "Guest declined to join.", "next_step": "" } },
|
||||
"end_negative_no_answer": { "ui_hints": { "title": "Process Ended", "status": "terminated", "reason": "Process ended after no answer.", "next_step": "" } },
|
||||
"end_negative_terminated": { "ui_hints": { "title": "Process Terminated", "status": "terminated", "reason": "Process manually terminated by user.", "next_step": "" } }
|
||||
},
|
||||
"transitions": [
|
||||
{
|
||||
"id": "submit_outcome",
|
||||
"from": "awaiting_call",
|
||||
"to": "outcome_router",
|
||||
"name": "Submit Outcome",
|
||||
"actions": [
|
||||
{ "type": "set_data", "params": { "keys": ["call_date", "outcome_status", "note", "next_contact_date"] } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "route_wants_to_join",
|
||||
"from": "outcome_router",
|
||||
"to": "end_positive",
|
||||
"name": "Route to Positive End",
|
||||
"condition": { "field": "outcome_status", "value": "wants_to_join" },
|
||||
"actions": [
|
||||
{ "type": "start_process", "process_code": "obsluga-przyjecia-nowego-czlonka" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "route_declined",
|
||||
"from": "outcome_router",
|
||||
"to": "end_negative_declined",
|
||||
"name": "Route to Declined",
|
||||
"condition": { "field": "outcome_status", "value": "declined" }
|
||||
},
|
||||
{
|
||||
"id": "route_no_answer",
|
||||
"from": "outcome_router",
|
||||
"to": "decide_after_no_answer",
|
||||
"name": "Route to No Answer",
|
||||
"condition": { "field": "outcome_status", "value": "no_answer" }
|
||||
},
|
||||
{
|
||||
"id": "route_call_later",
|
||||
"from": "outcome_router",
|
||||
"to": "waiting_for_next_contact",
|
||||
"name": "Route to Call Later",
|
||||
"condition": { "field": "outcome_status", "value": "call_later" }
|
||||
},
|
||||
{ "id": "continue_attempts", "from": "decide_after_no_answer", "to": "awaiting_call", "name": "Try Again" },
|
||||
{ "id": "end_attempts", "from": "decide_after_no_answer", "to": "end_negative_no_answer", "name": "End Process" },
|
||||
{ "id": "resume_contact", "from": "waiting_for_next_contact", "to": "awaiting_call", "name": "Resume / Attempt Call" },
|
||||
{
|
||||
"id": "terminate_from_awaiting_call",
|
||||
"from": "awaiting_call",
|
||||
"to": "end_negative_terminated",
|
||||
"name": "Zakończ proces",
|
||||
"actions": [ { "type": "set_data", "params": { "keys": ["termination_note"] } } ],
|
||||
"form_schema": [ { "name": "termination_note", "label": "Reason for Termination (optional)", "type": "textarea" } ]
|
||||
},
|
||||
{
|
||||
"id": "terminate_from_decide",
|
||||
"from": "decide_after_no_answer",
|
||||
"to": "end_negative_terminated",
|
||||
"name": "Zakończ proces",
|
||||
"actions": [ { "type": "set_data", "params": { "keys": ["termination_note"] } } ],
|
||||
"form_schema": [ { "name": "termination_note", "label": "Reason for Termination (optional)", "type": "textarea" } ]
|
||||
},
|
||||
{
|
||||
"id": "terminate_from_waiting",
|
||||
"from": "waiting_for_next_contact",
|
||||
"to": "end_negative_terminated",
|
||||
"name": "Zakończ proces",
|
||||
"actions": [ { "type": "set_data", "params": { "keys": ["termination_note"] } } ],
|
||||
"form_schema": [ { "name": "termination_note", "label": "Reason for Termination (optional)", "type": "textarea" } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
11
db/migrations/026_simplify_follow_up_ux.php
Normal file
11
db/migrations/026_simplify_follow_up_ux.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
$db = db();
|
||||
|
||||
$new_json_definition = file_get_contents(__DIR__ . '/../../new_definition.json');
|
||||
|
||||
$stmt = $db->prepare("UPDATE process_definitions SET definition_json = :json WHERE id = 4");
|
||||
$stmt->execute(['json' => $new_json_definition]);
|
||||
|
||||
echo "Process definition for follow_up (ID: 4) updated successfully.";
|
||||
19
db/migrations/027_deactivate_sales_pipeline.php
Normal file
19
db/migrations/027_deactivate_sales_pipeline.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
function migrate_027($pdo) {
|
||||
try {
|
||||
$sql = "UPDATE process_definitions SET is_active = 0 WHERE name = 'Sales Pipeline'";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration successful: Deactivated 'Sales Pipeline' process.\n";
|
||||
} catch (PDOException $e) {
|
||||
die("Migration failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
$pdo = db();
|
||||
migrate_027($pdo);
|
||||
}
|
||||
|
||||
22
db/migrations/028_add_sort_order_to_process_definitions.php
Normal file
22
db/migrations/028_add_sort_order_to_process_definitions.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
function migrate_028($pdo) {
|
||||
try {
|
||||
$sql = "ALTER TABLE process_definitions ADD COLUMN sort_order INT NOT NULL DEFAULT 0 AFTER is_active";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration successful: sort_order column added to process_definitions table.\n";
|
||||
} catch (PDOException $e) {
|
||||
// Ignore if column already exists
|
||||
if (strpos($e->getMessage(), 'Duplicate column name') === false) {
|
||||
die("Migration failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
$pdo = db();
|
||||
migrate_028($pdo);
|
||||
}
|
||||
|
||||
32
db/migrations/029_set_initial_process_order.php
Normal file
32
db/migrations/029_set_initial_process_order.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
function migrate_029($pdo) {
|
||||
$process_order = [
|
||||
'Przygotowanie spotkania grupy' => 10,
|
||||
'Follow-up' => 20,
|
||||
'Obsługa przyjęcia nowego członka' => 30,
|
||||
'Wprowadzenie nowego członka' => 40,
|
||||
'Szkolenia dla młodego członka' => 50,
|
||||
'Mentoring' => 60,
|
||||
];
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE process_definitions SET sort_order = :sort_order WHERE name = :name");
|
||||
|
||||
foreach ($process_order as $name => $order) {
|
||||
$stmt->execute(['sort_order' => $order, 'name' => $name]);
|
||||
}
|
||||
|
||||
echo "Migration successful: Initial process order set.\n";
|
||||
} catch (PDOException $e) {
|
||||
die("Migration failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
$pdo = db();
|
||||
migrate_029($pdo);
|
||||
}
|
||||
|
||||
7
get_definition.php
Normal file
7
get_definition.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
require 'db/config.php';
|
||||
$db = db();
|
||||
$stmt = $db->prepare("SELECT definition_json FROM process_definitions WHERE id = 4");
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
echo $result['definition_json'];
|
||||
29
get_process_definitions.php
Normal file
29
get_process_definitions.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT id, code, name, is_active FROM process_definitions ORDER BY id");
|
||||
$definitions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Identify which processes appear on the dashboard
|
||||
foreach ($definitions as &$def) {
|
||||
if (!in_array($def['name'], ['Obsluga goscia', 'Przygotowanie spotkania grupy'])) {
|
||||
$def['on_dashboard'] = 'Yes';
|
||||
} else {
|
||||
$def['on_dashboard'] = 'No';
|
||||
}
|
||||
}
|
||||
|
||||
echo "--- Process Definitions ---
|
||||
";
|
||||
echo str_pad("ID", 5) . str_pad("Code", 40) . str_pad("Name", 40) . str_pad("Active", 10) . str_pad("On Dashboard", 15) . "\n";
|
||||
echo str_repeat("-", 110) . "\n";
|
||||
foreach ($definitions as $def) {
|
||||
echo str_pad($def['id'], 5) . str_pad($def['code'], 40) . str_pad($def['name'], 40) . str_pad($def['is_active'] ? 'Yes' : 'No', 10) . str_pad($def['on_dashboard'], 15) . "\n";
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("DB ERROR: " . $e->getMessage());
|
||||
}
|
||||
|
||||
109
instance_details.html
Normal file
109
instance_details.html
Normal file
@ -0,0 +1,109 @@
|
||||
|
||||
<!-- Title for the modal, to be grabbed by JS -->
|
||||
<div id="instance-modal-title" class="d-none">
|
||||
Staszek Ptaszek - Follow-up</div>
|
||||
|
||||
|
||||
<div class="process-steps-container">
|
||||
<h5>Kroki procesu</h5>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center list-group-item-primary">
|
||||
<div>
|
||||
<i class="bi bi-arrow-right-circle-fill text-primary me-2"></i> <strong>Follow-up Call</strong>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center text-muted">
|
||||
<div>
|
||||
<i class="bi bi-circle me-2"></i> <strong></strong>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center text-muted">
|
||||
<div>
|
||||
<i class="bi bi-circle me-2"></i> <strong>Call Scheduled</strong>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center text-muted">
|
||||
<div>
|
||||
<i class="bi bi-circle me-2"></i> <strong>No Answer Logged</strong>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center text-muted">
|
||||
<div>
|
||||
<i class="bi bi-circle me-2"></i> <strong>Wants to Join</strong>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center text-muted">
|
||||
<div>
|
||||
<i class="bi bi-circle me-2"></i> <strong>Declined</strong>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center text-muted">
|
||||
<div>
|
||||
<i class="bi bi-circle me-2"></i> <strong>Process Ended</strong>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center text-muted">
|
||||
<div>
|
||||
<i class="bi bi-circle me-2"></i> <strong>Process Terminated</strong>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="mt-3">
|
||||
<h5>Available Actions</h5>
|
||||
<form id="transition-form">
|
||||
<div class="mb-3">
|
||||
<label for="call_date" class="form-label">Call Date</label>
|
||||
<input type="datetime-local" id="call_date" name="call_date" class="form-control" value="2026-01-11T07:00">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="outcome_status" class="form-label">Call Outcome</label>
|
||||
<select id="outcome_status" name="outcome_status" class="form-select">
|
||||
<option value="">-- Select Outcome --</option>
|
||||
<option value="no_answer">No Answer</option>
|
||||
<option value="wants_to_join">Wants to Join</option>
|
||||
<option value="declined">Declined</option>
|
||||
<option value="call_later">Call Later</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="note" class="form-label">Notes</label>
|
||||
<textarea id="note" name="note" class="form-control"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="next_contact_date" class="form-label">Next Contact Date</label>
|
||||
<input type="datetime-local" id="next_contact_date" name="next_contact_date" class="form-control" value="">
|
||||
</div>
|
||||
</form>
|
||||
<button class="btn btn-sm btn-primary apply-transition-btn"
|
||||
data-instance-id="21"
|
||||
data-transition-id="submit_outcome">
|
||||
Log Call Attempt </button>
|
||||
<button class="btn btn-sm btn-primary apply-transition-btn"
|
||||
data-instance-id="21"
|
||||
data-transition-id="terminate_from_awaiting_call">
|
||||
Zakończ proces </button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="add-note-container">
|
||||
<h5>Dodaj notatkę</h5>
|
||||
<div class="mb-3">
|
||||
<textarea id="noteMessage" class="form-control" rows="2" placeholder="Wpisz treść notatki..."></textarea>
|
||||
</div>
|
||||
<button id="addNoteBtn" class="btn btn-secondary" data-instance-id="21">Dodaj notatkę</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="history-container">
|
||||
<h5>Historia</h5>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<strong>System</strong>
|
||||
<p class="mb-1 text-muted fst-italic">Process started.</p> <small class="text-muted">By Admin User on 11.01.2026, 07:00</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
131
new_definition.json
Normal file
131
new_definition.json
Normal file
@ -0,0 +1,131 @@
|
||||
|
||||
{
|
||||
"start_node_id": "awaiting_call",
|
||||
"eligibility_rules": [
|
||||
{
|
||||
"type": "person_property_equals",
|
||||
"params": {
|
||||
"property": "role",
|
||||
"value": "guest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"nodes": {
|
||||
"awaiting_call": {
|
||||
"ui_hints": {
|
||||
"title": "Follow-up Call",
|
||||
"status": "active",
|
||||
"reason": "Ready to log a new call attempt with the guest.",
|
||||
"next_step": "Fill out the form below to log the details of your call.",
|
||||
"form_schema": [
|
||||
{ "name": "call_date", "label": "Call Date", "type": "datetime-local", "default": "now", "required": true },
|
||||
{
|
||||
"name": "outcome_status",
|
||||
"label": "Call Outcome",
|
||||
"type": "select",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "value": "", "label": "-- Select Outcome --" },
|
||||
{ "value": "no_answer", "label": "No Answer" },
|
||||
{ "value": "wants_to_join", "label": "Wants to Join" },
|
||||
{ "value": "declined", "label": "Declined" },
|
||||
{ "value": "call_later", "label": "Call Later" }
|
||||
]
|
||||
},
|
||||
{ "name": "note", "label": "Notes", "type": "textarea" },
|
||||
{ "name": "next_contact_date", "label": "Next Contact Date", "type": "datetime-local", "condition": { "field": "outcome_status", "value": "call_later" }, "required": true }
|
||||
]
|
||||
}
|
||||
},
|
||||
"outcome_router": { "ui_hints": { "status": "processing" } },
|
||||
"waiting_for_next_contact": {
|
||||
"ui_hints": {
|
||||
"title": "Call Scheduled",
|
||||
"status": "paused",
|
||||
"reason": "A follow-up call is scheduled for a future date.",
|
||||
"next_step": "Click 'Log Next Call Attempt' to log the call now or on the scheduled date."
|
||||
}
|
||||
},
|
||||
"decide_after_no_answer": {
|
||||
"ui_hints": {
|
||||
"title": "No Answer Logged",
|
||||
"status": "paused",
|
||||
"reason": "The guest did not answer the last call.",
|
||||
"next_step": "Decide whether to try again or end the follow-up process."
|
||||
}
|
||||
},
|
||||
"end_positive": { "ui_hints": { "title": "Wants to Join", "status": "completed", "reason": "Guest wants to join. New member process started.", "next_step": "" } },
|
||||
"end_negative_declined": { "ui_hints": { "title": "Declined", "status": "terminated", "reason": "Guest declined to join.", "next_step": "" } },
|
||||
"end_negative_no_answer": { "ui_hints": { "title": "Process Ended", "status": "terminated", "reason": "Process ended after no answer.", "next_step": "" } },
|
||||
"end_negative_terminated": { "ui_hints": { "title": "Process Terminated", "status": "terminated", "reason": "Process manually terminated by user.", "next_step": "" } }
|
||||
},
|
||||
"transitions": [
|
||||
{
|
||||
"id": "submit_outcome",
|
||||
"from": "awaiting_call",
|
||||
"to": "outcome_router",
|
||||
"name": "Log Call Attempt",
|
||||
"actions": [
|
||||
{ "type": "set_data", "params": { "keys": ["call_date", "outcome_status", "note", "next_contact_date"] } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "route_wants_to_join",
|
||||
"from": "outcome_router",
|
||||
"to": "end_positive",
|
||||
"name": "Route to Positive End",
|
||||
"condition": { "field": "outcome_status", "value": "wants_to_join" },
|
||||
"actions": [
|
||||
{ "type": "start_process", "process_code": "wprowadzenie-nowego-cz-onka" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "route_declined",
|
||||
"from": "outcome_router",
|
||||
"to": "end_negative_declined",
|
||||
"name": "Route to Declined",
|
||||
"condition": { "field": "outcome_status", "value": "declined" }
|
||||
},
|
||||
{
|
||||
"id": "route_no_answer",
|
||||
"from": "outcome_router",
|
||||
"to": "decide_after_no_answer",
|
||||
"name": "Route to No Answer",
|
||||
"condition": { "field": "outcome_status", "value": "no_answer" }
|
||||
},
|
||||
{
|
||||
"id": "route_call_later",
|
||||
"from": "outcome_router",
|
||||
"to": "waiting_for_next_contact",
|
||||
"name": "Route to Call Later",
|
||||
"condition": { "field": "outcome_status", "value": "call_later" }
|
||||
},
|
||||
{ "id": "continue_attempts", "from": "decide_after_no_answer", "to": "awaiting_call", "name": "Try Again" },
|
||||
{ "id": "end_attempts", "from": "decide_after_no_answer", "to": "end_negative_no_answer", "name": "End Process" },
|
||||
{ "id": "log_next_attempt", "from": "waiting_for_next_contact", "to": "awaiting_call", "name": "Log Next Call Attempt" },
|
||||
{
|
||||
"id": "terminate_from_awaiting_call",
|
||||
"from": "awaiting_call",
|
||||
"to": "end_negative_terminated",
|
||||
"name": "Zakończ proces",
|
||||
"actions": [ { "type": "set_data", "params": { "keys": ["termination_note"] } } ],
|
||||
"form_schema": [ { "name": "termination_note", "label": "Reason for Termination (optional)", "type": "textarea" } ]
|
||||
},
|
||||
{
|
||||
"id": "terminate_from_decide",
|
||||
"from": "decide_after_no_answer",
|
||||
"to": "end_negative_terminated",
|
||||
"name": "Zakończ proces",
|
||||
"actions": [ { "type": "set_data", "params": { "keys": ["termination_note"] } } ],
|
||||
"form_schema": [ { "name": "termination_note", "label": "Reason for Termination (optional)", "type": "textarea" } ]
|
||||
},
|
||||
{
|
||||
"id": "terminate_from_waiting",
|
||||
"from": "waiting_for_next_contact",
|
||||
"to": "end_negative_terminated",
|
||||
"name": "Zakończ proces",
|
||||
"actions": [ { "type": "set_data", "params": { "keys": ["termination_note"] } } ],
|
||||
"form_schema": [ { "name": "termination_note", "label": "Reason for Termination (optional)", "type": "textarea" } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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 ORDER BY name");
|
||||
$stmt = $pdo->query("SELECT * FROM process_definitions WHERE is_active = 1 ORDER BY sort_order, name");
|
||||
$processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
165
update_follow_up_process.php
Normal file
165
update_follow_up_process.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
function update_process_definition()
|
||||
{
|
||||
$pdo = db();
|
||||
|
||||
$json_definition = <<<'EOT'
|
||||
{
|
||||
"start_node_id": "awaiting_call",
|
||||
"eligibility_rules": [
|
||||
{
|
||||
"type": "person_property_equals",
|
||||
"params": {
|
||||
"property": "role",
|
||||
"value": "guest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"nodes": {
|
||||
"awaiting_call": {
|
||||
"ui_hints": {
|
||||
"title": "Follow-up Call",
|
||||
"status": "active",
|
||||
"reason": "Awaiting follow-up call with the guest.",
|
||||
"next_step": "Log the outcome of the call.",
|
||||
"form_schema": [
|
||||
{ "name": "call_date", "label": "Call Date", "type": "datetime-local", "default": "now", "required": true },
|
||||
{
|
||||
"name": "outcome_status",
|
||||
"label": "Call Outcome",
|
||||
"type": "select",
|
||||
"required": true,
|
||||
"options": [
|
||||
{ "value": "", "label": "-- Select Outcome --" },
|
||||
{ "value": "no_answer", "label": "No Answer" },
|
||||
{ "value": "wants_to_join", "label": "Wants to Join" },
|
||||
{ "value": "declined", "label": "Declined" },
|
||||
{ "value": "call_later", "label": "Call Later" }
|
||||
]
|
||||
},
|
||||
{ "name": "note", "label": "Notes", "type": "textarea" },
|
||||
{ "name": "next_contact_date", "label": "Next Contact Date", "type": "datetime-local", "condition": { "field": "outcome_status", "value": "call_later" }, "required": true }
|
||||
]
|
||||
}
|
||||
},
|
||||
"outcome_router": { "ui_hints": { "status": "processing" } },
|
||||
"waiting_for_next_contact": {
|
||||
"ui_hints": {
|
||||
"title": "Waiting for Scheduled Call",
|
||||
"status": "paused",
|
||||
"reason": "Waiting until the scheduled date for the next call.",
|
||||
"next_step": "Resume contact on or after the scheduled date."
|
||||
}
|
||||
},
|
||||
"decide_after_no_answer": {
|
||||
"ui_hints": {
|
||||
"title": "No Answer",
|
||||
"status": "paused",
|
||||
"reason": "The guest did not answer the call.",
|
||||
"next_step": "Decide whether to try again or end the process."
|
||||
}
|
||||
},
|
||||
"end_positive": { "ui_hints": { "title": "Wants to Join", "status": "completed", "reason": "Guest wants to join. New member process started.", "next_step": "" } },
|
||||
"end_negative_declined": { "ui_hints": { "title": "Declined", "status": "terminated", "reason": "Guest declined to join.", "next_step": "" } },
|
||||
"end_negative_no_answer": { "ui_hints": { "title": "Process Ended", "status": "terminated", "reason": "Process ended after no answer.", "next_step": "" } },
|
||||
"end_negative_terminated": { "ui_hints": { "title": "Process Terminated", "status": "terminated", "reason": "Process manually terminated by user.", "next_step": "" } }
|
||||
},
|
||||
"transitions": [
|
||||
{
|
||||
"id": "submit_outcome",
|
||||
"from": "awaiting_call",
|
||||
"to": "outcome_router",
|
||||
"name": "Submit Outcome",
|
||||
"actions": [
|
||||
{ "type": "set_data", "params": { "keys": ["call_date", "outcome_status", "note", "next_contact_date"] } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "route_wants_to_join",
|
||||
"from": "outcome_router",
|
||||
"to": "end_positive",
|
||||
"name": "Route to Positive End",
|
||||
"condition": { "field": "outcome_status", "value": "wants_to_join" },
|
||||
"actions": [
|
||||
{ "type": "start_process", "process_code": "obsluga-przyjecia-nowego-czlonka" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "route_declined",
|
||||
"from": "outcome_router",
|
||||
"to": "end_negative_declined",
|
||||
"name": "Route to Declined",
|
||||
"condition": { "field": "outcome_status", "value": "declined" }
|
||||
},
|
||||
{
|
||||
"id": "route_no_answer",
|
||||
"from": "outcome_router",
|
||||
"to": "decide_after_no_answer",
|
||||
"name": "Route to No Answer",
|
||||
"condition": { "field": "outcome_status", "value": "no_answer" }
|
||||
},
|
||||
{
|
||||
"id": "route_call_later",
|
||||
"from": "outcome_router",
|
||||
"to": "waiting_for_next_contact",
|
||||
"name": "Route to Call Later",
|
||||
"condition": { "field": "outcome_status", "value": "call_later" }
|
||||
},
|
||||
{ "id": "continue_attempts", "from": "decide_after_no_answer", "to": "awaiting_call", "name": "Try Again" },
|
||||
{ "id": "end_attempts", "from": "decide_after_no_answer", "to": "end_negative_no_answer", "name": "End Process" },
|
||||
{ "id": "resume_contact", "from": "waiting_for_next_contact", "to": "awaiting_call", "name": "Resume / Attempt Call" },
|
||||
{
|
||||
"id": "terminate_from_awaiting_call",
|
||||
"from": "awaiting_call",
|
||||
"to": "end_negative_terminated",
|
||||
"name": "Zakończ proces",
|
||||
"actions": [ { "type": "set_data", "params": { "keys": ["termination_note"] } } ],
|
||||
"form_schema": [ { "name": "termination_note", "label": "Reason for Termination (optional)", "type": "textarea" } ]
|
||||
},
|
||||
{
|
||||
"id": "terminate_from_decide",
|
||||
"from": "decide_after_no_answer",
|
||||
"to": "end_negative_terminated",
|
||||
"name": "Zakończ proces",
|
||||
"actions": [ { "type": "set_data", "params": { "keys": ["termination_note"] } } ],
|
||||
"form_schema": [ { "name": "termination_note", "label": "Reason for Termination (optional)", "type": "textarea" } ]
|
||||
},
|
||||
{
|
||||
"id": "terminate_from_waiting",
|
||||
"from": "waiting_for_next_contact",
|
||||
"to": "end_negative_terminated",
|
||||
"name": "Zakończ proces",
|
||||
"actions": [ { "type": "set_data", "params": { "keys": ["termination_note"] } } ],
|
||||
"form_schema": [ { "name": "termination_note", "label": "Reason for Termination (optional)", "type": "textarea" } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOT;
|
||||
|
||||
// Update process_definition_id=4
|
||||
$stmt = $pdo->prepare("UPDATE process_definitions SET definition_json = :json, start_node_id = 'awaiting_call' WHERE id = 4");
|
||||
$stmt->execute([
|
||||
':json' => $json_definition,
|
||||
]);
|
||||
|
||||
echo "Updated process_definition_id=4 with the new follow-up workflow.\n";
|
||||
|
||||
// Deactivate process_definition_id=3
|
||||
$stmt = $pdo->prepare("UPDATE process_definitions SET is_active = 0 WHERE id = 3");
|
||||
$stmt->execute();
|
||||
|
||||
echo "Deactivated process_definition_id=3 ('guest_handling').\n";
|
||||
}
|
||||
|
||||
// Direct execution guard
|
||||
if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"]))
|
||||
{
|
||||
try {
|
||||
update_process_definition();
|
||||
} catch (Exception $e) {
|
||||
echo "Failed to update process definitions: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
11
verify_fix.sh
Executable file
11
verify_fix.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Login and save cookie
|
||||
curl -s -L -c cookie.txt -X POST -F 'email=admin@example.com' -F 'password=password' http://localhost/login.php > /dev/null
|
||||
|
||||
# Initialize the process
|
||||
# First, delete any existing instance for this person and process to ensure a clean slate
|
||||
curl -s -L -b cookie.txt -X POST -F 'person_id=9' -F 'process_id=4' -F 'delete_existing=1' http://localhost/_init_single_instance.php > /dev/null
|
||||
|
||||
# Get instance details
|
||||
curl -s -L -b cookie.txt "http://localhost/_get_instance_details.php?person_id=9&process_id=4"
|
||||
Loading…
x
Reference in New Issue
Block a user