Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3553ebe363 | ||
|
|
55bc7456bf | ||
|
|
2630bc9dd8 | ||
|
|
708233f9b4 | ||
|
|
45d975adbf | ||
|
|
6d5f26a42c | ||
|
|
2e85c01115 | ||
|
|
9ea7b9d268 | ||
|
|
9134470c19 | ||
|
|
f2aacc643f | ||
|
|
b1016e2a54 | ||
|
|
7717562504 | ||
|
|
4c2c56f0dc | ||
|
|
cca6219870 | ||
|
|
dc85db3cf1 | ||
|
|
1ae32ef541 | ||
|
|
e1d38e6487 | ||
|
|
1c68363a11 | ||
|
|
61f9e88301 | ||
|
|
fb86b47003 | ||
|
|
a9ab6bd592 | ||
|
|
bedf2de8b1 | ||
|
|
c7dc809d9e | ||
|
|
1c47d12879 | ||
|
|
9f1c19bf45 | ||
|
|
6df581c51b | ||
|
|
0b3a1c1c19 | ||
|
|
a1a7a31494 | ||
|
|
4e5ed7affc | ||
|
|
9cbec6a128 | ||
|
|
1066458199 | ||
|
|
b6d5f17197 | ||
|
|
803226678c | ||
|
|
1639a6367c | ||
|
|
a15baf88b4 | ||
|
|
274148ff8b | ||
|
|
eb3e723258 | ||
|
|
2cc2c42b57 | ||
|
|
6714d8259a | ||
|
|
c52190436a | ||
|
|
8f2aebda66 | ||
|
|
4acc5b8118 | ||
|
|
779f35f634 | ||
|
|
21fc1b6baa | ||
|
|
44d4fa5a60 | ||
|
|
6460ff3ac8 | ||
|
|
4674e7458b | ||
|
|
3b1a26adc9 | ||
|
|
a703aeb1e2 | ||
|
|
524c7007ab | ||
|
|
ee89357279 | ||
|
|
47c0b35493 | ||
|
|
cdd041c172 | ||
|
|
3da7625d06 | ||
|
|
2a8ad6701c | ||
|
|
a141f60df0 | ||
|
|
82b98582c4 | ||
|
|
9234472371 |
1257
WorkflowEngine.php
Normal file
1257
WorkflowEngine.php
Normal file
File diff suppressed because it is too large
Load Diff
32
_add_bni_group.php
Normal file
32
_add_bni_group.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (isset($_POST['add'])) {
|
||||
$name = $_POST['name'];
|
||||
$city = $_POST['city'] ?? null;
|
||||
$active = isset($_POST['active']) ? 1 : 0;
|
||||
$display_order = $_POST['display_order'] ?? 0;
|
||||
|
||||
if (empty($name)) {
|
||||
$_SESSION['error_message'] = 'Name is required.';
|
||||
header('Location: bni_groups.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("INSERT INTO bni_groups (name, city, active, display_order) VALUES (:name, :city, :active, :display_order)");
|
||||
$stmt->bindParam(':name', $name);
|
||||
$stmt->bindParam(':city', $city);
|
||||
$stmt->bindParam(':active', $active, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':display_order', $display_order, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$_SESSION['success_message'] = 'BNI Group added successfully!';
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = 'Error adding BNI group: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: bni_groups.php');
|
||||
exit;
|
||||
104
_add_calendar_event.php
Normal file
104
_add_calendar_event.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$title = $_POST['title'] ?? '';
|
||||
$description = $_POST['description'] ?? '';
|
||||
$start_datetime = $_POST['start_datetime'] ?? '';
|
||||
$end_datetime = $_POST['end_datetime'] ?? '';
|
||||
$event_type_id = $_POST['event_type_id'] ?? null;
|
||||
$recurrence = $_POST['recurrence'] ?? '';
|
||||
$recurrence_end_date = $_POST['recurrence_end_date'] ?? '';
|
||||
|
||||
if (empty($recurrence)) {
|
||||
$recurrence = null;
|
||||
}
|
||||
if (empty($recurrence_end_date)) {
|
||||
$recurrence_end_date = null;
|
||||
}
|
||||
|
||||
if (empty($title) || empty($start_datetime) || empty($end_datetime) || empty($event_type_id)) {
|
||||
header("Location: calendar.php?error=empty_fields");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Insert the main event
|
||||
$stmt = $pdo->prepare("INSERT INTO calendar_events (title, description, start_datetime, end_datetime, event_type_id, recurrence, recurrence_end_date) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$title, $description, $start_datetime, $end_datetime, $event_type_id, $recurrence, $recurrence_end_date]);
|
||||
$parent_event_id = $pdo->lastInsertId();
|
||||
|
||||
// Handle group associations
|
||||
if (isset($_POST['group_ids']) && is_array($_POST['group_ids'])) {
|
||||
$stmt_groups = $pdo->prepare("INSERT INTO calendar_event_groups (calendar_event_id, bni_group_id) VALUES (?, ?)");
|
||||
foreach ($_POST['group_ids'] as $group_id) {
|
||||
$stmt_groups->execute([$parent_event_id, $group_id]);
|
||||
}
|
||||
} else {
|
||||
// The field is required, so this is a failure case.
|
||||
throw new Exception("Group IDs are required.");
|
||||
}
|
||||
|
||||
if ($recurrence && !empty($recurrence_end_date)) {
|
||||
$start_date = new DateTime($start_datetime);
|
||||
$end_date = new DateTime($end_datetime);
|
||||
$recurrence_end = new DateTime($recurrence_end_date);
|
||||
$interval_spec = '';
|
||||
|
||||
switch ($recurrence) {
|
||||
case 'daily':
|
||||
$interval_spec = 'P1D';
|
||||
break;
|
||||
case 'weekly':
|
||||
$interval_spec = 'P1W';
|
||||
break;
|
||||
case 'monthly':
|
||||
$interval_spec = 'P1M';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($interval_spec) {
|
||||
$interval = new DateInterval($interval_spec);
|
||||
$period_start = clone $start_date;
|
||||
$period_start->add($interval);
|
||||
|
||||
$period = new DatePeriod($period_start, $interval, $recurrence_end);
|
||||
|
||||
$stmt_recur = $pdo->prepare("INSERT INTO calendar_events (title, description, start_datetime, end_datetime, event_type_id, parent_event_id) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt_recur_groups = $pdo->prepare("INSERT INTO calendar_event_groups (calendar_event_id, bni_group_id) VALUES (?, ?)");
|
||||
|
||||
foreach ($period as $date) {
|
||||
$new_start_datetime = $date->format('Y-m-d H:i:s');
|
||||
$end_date_clone = clone $date;
|
||||
$new_end_datetime = $end_date_clone->add($start_date->diff($end_date))->format('Y-m-d H:i:s');
|
||||
$stmt_recur->execute([$title, $description, $new_start_datetime, $new_end_datetime, $event_type_id, $parent_event_id]);
|
||||
$new_event_id = $pdo->lastInsertId();
|
||||
foreach ($_POST['group_ids'] as $group_id) {
|
||||
$stmt_recur_groups->execute([$new_event_id, $group_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
header("Location: calendar.php");
|
||||
exit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
error_log($e->getMessage());
|
||||
header("Location: calendar.php?error=db_error");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
18
_add_event_type.php
Normal file
18
_add_event_type.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_POST['add'])) {
|
||||
$name = $_POST['name'];
|
||||
$color = $_POST['color'];
|
||||
$display_order = $_POST['display_order'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("INSERT INTO event_types (name, color, display_order) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$name, $color, $display_order]);
|
||||
|
||||
session_start();
|
||||
$_SESSION['success_message'] = 'Event type added successfully.';
|
||||
header('Location: event_types.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
35
_add_function.php
Normal file
35
_add_function.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_POST['name'], $_POST['bni_group_id'])) {
|
||||
$name = trim($_POST['name']);
|
||||
$bni_group_id = trim($_POST['bni_group_id']);
|
||||
|
||||
if (!empty($name) && !empty($bni_group_id)) {
|
||||
try {
|
||||
$pdo = db();
|
||||
// Get the current max display order
|
||||
$stmt = $pdo->query("SELECT MAX(display_order) FROM functions");
|
||||
$max_order = $stmt->fetchColumn();
|
||||
$new_order = $max_order + 1;
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO functions (name, bni_group_id, display_order) VALUES (:name, :bni_group_id, :display_order)");
|
||||
$stmt->execute(['name' => $name, 'bni_group_id' => $bni_group_id, 'display_order' => $new_order]);
|
||||
$_SESSION['success_message'] = "Function added successfully.";
|
||||
} catch (PDOException $e) {
|
||||
// Handle potential errors, e.g., duplicate name
|
||||
$_SESSION['error_message'] = "Error adding function: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Name and group are required.";
|
||||
}
|
||||
|
||||
header('Location: functions.php');
|
||||
exit();
|
||||
28
_add_process_event.php
Normal file
28
_add_process_event.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
$instanceId = $_POST['instance_id'] ?? null;
|
||||
$message = $_POST['message'] ?? null;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (!$instanceId || !$message) {
|
||||
throw new WorkflowRuleFailedException('Missing parameters: instance_id and message are required.');
|
||||
}
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$workflowEngine->addNote((int)$instanceId, $message, (int)$userId);
|
||||
|
||||
if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['message' => 'Note added successfully.']);
|
||||
} else {
|
||||
header('Location: ' . $_SERVER['HTTP_REFERER']);
|
||||
}
|
||||
exit;
|
||||
41
_apply_transition.php
Normal file
41
_apply_transition.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
register_error_handler();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
throw new WorkflowNotAllowedException('Invalid request method.');
|
||||
}
|
||||
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
if (!isset($_POST['instanceId']) || !isset($_POST['transitionId'])) {
|
||||
throw new WorkflowNotAllowedException('Błąd: Brak wymaganych parametrów.');
|
||||
}
|
||||
|
||||
$instanceId = (int)$_POST['instanceId'];
|
||||
$transitionId = $_POST['transitionId'];
|
||||
$userId = $_SESSION['user_id'] ?? null;
|
||||
$payload = $_POST['payload'] ?? [];
|
||||
|
||||
if (!$userId) {
|
||||
throw new WorkflowNotAllowedException('Błąd: Sesja wygasła.', [], 401);
|
||||
}
|
||||
|
||||
$engine = new WorkflowEngine();
|
||||
|
||||
if ($transitionId === 'note') {
|
||||
$message = $payload['message'] ?? '';
|
||||
if (empty($message)) {
|
||||
throw new WorkflowNotAllowedException('Treść notatki nie może być pusta.');
|
||||
}
|
||||
$engine->addNote($instanceId, $message, $userId);
|
||||
$response = ['success' => true, 'message' => 'Notatka została dodana.'];
|
||||
} else {
|
||||
$result = $engine->applyTransition($instanceId, $transitionId, $payload, $userId);
|
||||
$response = ['success' => true, 'message' => 'Akcja została wykonana pomyślnie.', 'data' => $result];
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
50
_bulk_add_event.php
Normal file
50
_bulk_add_event.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
$person_ids = json_decode($_POST['person_ids'] ?? '[]');
|
||||
$process_id = $_POST['process_id'] ?? null;
|
||||
$message = $_POST['description'] ?? null;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (empty($person_ids) || !$process_id || !$message) {
|
||||
throw new WorkflowRuleFailedException('Missing parameters: person_ids, process_id, and description are required.');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$placeholders = implode(',', array_fill(0, count($person_ids), '?'));
|
||||
$stmt = $pdo->prepare("SELECT id FROM process_instances WHERE process_definition_id = ? AND person_id IN ($placeholders)");
|
||||
$params = array_merge([$process_id], $person_ids);
|
||||
$stmt->execute($params);
|
||||
$instance_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (empty($instance_ids)) {
|
||||
$_SESSION['flash_message'] = "No instances found for the selected people and process.";
|
||||
header('Location: ' . $_SERVER['HTTP_REFERER']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$notes = [];
|
||||
foreach ($instance_ids as $instance_id) {
|
||||
$notes[] = [
|
||||
'instance_id' => $instance_id,
|
||||
'message' => $message,
|
||||
'user_id' => $userId
|
||||
];
|
||||
}
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$results = $workflowEngine->bulkAddNotes($notes);
|
||||
|
||||
$_SESSION['flash_message'] = "Bulk note addition completed.";
|
||||
$_SESSION['bulk_results'] = $results;
|
||||
|
||||
header('Location: ' . $_SERVER['HTTP_REFERER']);
|
||||
exit;
|
||||
56
_bulk_init_instances.php
Normal file
56
_bulk_init_instances.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'lib/WorkflowExceptions.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
$personIds = $_POST['personIds'] ?? '[]';
|
||||
if (is_string($personIds)) {
|
||||
$personIds = json_decode($personIds, true);
|
||||
}
|
||||
$process_id = $_POST['process_id'] ?? null;
|
||||
|
||||
if (empty($personIds) || !$process_id) {
|
||||
throw new WorkflowRuleFailedException('Missing parameters');
|
||||
}
|
||||
|
||||
$engine = new WorkflowEngine();
|
||||
$results = [
|
||||
'success' => [],
|
||||
'failed' => [],
|
||||
];
|
||||
|
||||
foreach ($personIds as $personId) {
|
||||
try {
|
||||
$instance = $engine->getOrCreateInstanceByDefId($personId, $process_id, $userId);
|
||||
if ($instance) {
|
||||
$results['success'][] = $personId;
|
||||
} else {
|
||||
$results['failed'][] = $personId;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$results['failed'][] = $personId;
|
||||
// Optionally log the error
|
||||
error_log("Failed to initialize process for person $personId: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$message = "Bulk initialization completed. Success: " . count($results['success']) . ", Failed: " . count($results['failed']);
|
||||
|
||||
if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'message' => $message,
|
||||
'results' => $results
|
||||
]);
|
||||
} else {
|
||||
$_SESSION['success_message'] = $message;
|
||||
header('Location: index.php');
|
||||
}
|
||||
exit();
|
||||
112
_bulk_print_attendance_list.php
Normal file
112
_bulk_print_attendance_list.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
register_error_handler();
|
||||
|
||||
session_start();
|
||||
|
||||
$correlation_id = $GLOBALS['correlation_id'];
|
||||
error_log($correlation_id . ': Bulk print attendance list request started.');
|
||||
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'lib/tfpdf/font/unifont/ttfonts.php';
|
||||
define('FPDF_FONTPATH', 'lib/tfpdf/font/');
|
||||
require_once 'lib/tfpdf/tfpdf.php';
|
||||
|
||||
// Authentication check
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
if (ob_get_length()) ob_end_clean();
|
||||
http_response_code(403);
|
||||
header('Content-Type: application/json');
|
||||
$error_message = 'Brak uprawnień.';
|
||||
error_log($correlation_id . ': ' . $error_message);
|
||||
echo json_encode(['error' => ['message' => $error_message], 'correlation_id' => $correlation_id]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Input validation
|
||||
$person_ids = json_decode($_POST['person_ids'] ?? '[]', true);
|
||||
error_log($correlation_id . ': Received ' . count($person_ids) . ' person_ids.');
|
||||
|
||||
if (empty($person_ids)) {
|
||||
if (ob_get_length()) ob_end_clean();
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
$error_message = 'Nie wybrano żadnych osób.';
|
||||
error_log($correlation_id . ': ' . $error_message);
|
||||
echo json_encode(['error' => ['message' => $error_message], 'correlation_id' => $correlation_id]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$peopleDetails = $workflowEngine->getPeopleDetails($person_ids);
|
||||
error_log($correlation_id . ': Fetched ' . count($peopleDetails) . ' rows from database.');
|
||||
|
||||
class PDF extends tFPDF
|
||||
{
|
||||
function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
|
||||
{
|
||||
parent::__construct($orientation, $unit, $size);
|
||||
$this->AddFont('DejaVu', '', 'DejaVuSans.ttf', true);
|
||||
$this->AddFont('DejaVu', 'B', 'DejaVuSans-Bold.ttf', true);
|
||||
$this->SetFont('DejaVu', '', 14);
|
||||
}
|
||||
|
||||
function generateAttendanceList($people)
|
||||
{
|
||||
$this->AddPage();
|
||||
|
||||
// Title
|
||||
$this->SetFont('DejaVu', 'B', 20);
|
||||
$this->Cell(0, 10, 'Lista obecności', 0, 1, 'C');
|
||||
$this->Ln(2);
|
||||
|
||||
// Subtitle - Date
|
||||
$this->SetFont('DejaVu', '', 10);
|
||||
$this->Cell(0, 10, date('Y-m-d H:i'), 0, 1, 'C');
|
||||
$this->Ln(10);
|
||||
|
||||
// Table Header
|
||||
$this->SetFont('DejaVu', 'B', 12);
|
||||
$this->SetFillColor(240, 240, 240);
|
||||
$this->Cell($this->GetPageWidth() * 0.7, 10, 'Imię i nazwisko', 1, 0, 'L', true);
|
||||
$this->Cell($this->GetPageWidth() * 0.3 - $this->lMargin - $this->rMargin, 10, 'Podpis', 1, 1, 'L', true);
|
||||
|
||||
// Table Body
|
||||
$this->SetFont('DejaVu', '', 12);
|
||||
foreach ($people as $person) {
|
||||
$name = $person['first_name'] . ' ' . $person['last_name'];
|
||||
$this->Cell($this->GetPageWidth() * 0.7, 15, $name, 1, 0, 'L');
|
||||
$this->Cell($this->GetPageWidth() * 0.3 - $this->lMargin - $this->rMargin, 15, '', 1, 1, 'L');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pdf = new PDF();
|
||||
$pdf->generateAttendanceList($peopleDetails);
|
||||
|
||||
$pdfData = $pdf->Output('S');
|
||||
error_log($correlation_id . ': PDF data generated. Length: ' . strlen($pdfData) . ' bytes.');
|
||||
|
||||
if (empty($pdfData)) {
|
||||
if (ob_get_length()) ob_end_clean();
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json');
|
||||
$error_message = 'Failed to generate PDF data.';
|
||||
error_log($correlation_id . ': ' . $error_message);
|
||||
echo json_encode(['error' => ['message' => $error_message], 'correlation_id' => $correlation_id]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (ob_get_length()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
header('Content-Type: application/pdf');
|
||||
header('Content-Disposition: attachment; filename="lista-obecnosci.pdf"');
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
header('Cache-Control: private, max-age=0, must-revalidate');
|
||||
header('Pragma: public');
|
||||
header('Content-Length: ' . strlen($pdfData));
|
||||
|
||||
echo $pdfData;
|
||||
exit;
|
||||
111
_bulk_print_badges.php
Normal file
111
_bulk_print_badges.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
register_error_handler();
|
||||
|
||||
session_start();
|
||||
|
||||
$correlation_id = $GLOBALS['correlation_id']; // Use correlation_id from the global error handler
|
||||
error_log($correlation_id . ': Bulk print badges request started.');
|
||||
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'lib/tfpdf/font/unifont/ttfonts.php';
|
||||
define('FPDF_FONTPATH', 'lib/tfpdf/font/');
|
||||
require_once 'lib/tfpdf/tfpdf.php';
|
||||
|
||||
// Authentication check
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
if (ob_get_length()) ob_end_clean();
|
||||
http_response_code(403);
|
||||
header('Content-Type: application/json');
|
||||
$error_message = 'Brak uprawnień.';
|
||||
error_log($correlation_id . ': ' . $error_message);
|
||||
echo json_encode(['error' => ['message' => $error_message], 'correlation_id' => $correlation_id]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Input validation
|
||||
$person_ids = json_decode($_POST['person_ids'] ?? '[]', true);
|
||||
error_log($correlation_id . ': Received ' . count($person_ids) . ' person_ids.');
|
||||
|
||||
if (empty($person_ids)) {
|
||||
if (ob_get_length()) ob_end_clean();
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
$error_message = 'Nie wybrano żadnych osób.';
|
||||
error_log($correlation_id . ': ' . $error_message);
|
||||
echo json_encode(['error' => ['message' => $error_message], 'correlation_id' => $correlation_id]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$peopleDetails = $workflowEngine->getPeopleDetails($person_ids);
|
||||
error_log($correlation_id . ': Fetched ' . count($peopleDetails) . ' rows from database.');
|
||||
|
||||
class PDF extends tFPDF
|
||||
{
|
||||
function __construct($orientation='P', $unit='mm', $size='A4')
|
||||
{
|
||||
parent::__construct($orientation, $unit, $size);
|
||||
$this->AddFont('DejaVu','','DejaVuSans.ttf',true);
|
||||
$this->AddFont('DejaVu','B','DejaVuSans-Bold.ttf',true);
|
||||
$this->SetFont('DejaVu', '', 14);
|
||||
}
|
||||
|
||||
function generateBadge($person) {
|
||||
$this->AddPage();
|
||||
if(file_exists('assets/pasted-20260112-081646-4e946aad.png')) {
|
||||
$this->Image('assets/pasted-20260112-081646-4e946aad.png', 0, 0, $this->GetPageWidth(), $this->GetPageHeight());
|
||||
}
|
||||
$this->SetY(20);
|
||||
$this->SetFont('DejaVu', 'B', 12);
|
||||
$this->Cell(0, 6, $person['first_name'] . ' ' . $person['last_name'], 0, 1, 'C');
|
||||
$this->SetFont('DejaVu', 'B', 8);
|
||||
$this->Cell(0, 3, $person['company_name'] ?? 'N/A', 0, 1, 'C');
|
||||
$this->SetFont('DejaVu', '', 6);
|
||||
$this->Cell(0, 3, $person['industry'] ?? 'N/A', 0, 1, 'C');
|
||||
|
||||
$this->SetXY(2.5,13);
|
||||
$this->SetFont('DejaVu', 'B', 6);
|
||||
$this->Cell(22, 3, $person['bni_group_name'] ?? 'GOŚĆ', 0, 0, 'C');
|
||||
}
|
||||
}
|
||||
|
||||
$pdf = new PDF('L', 'mm', array(85, 55));
|
||||
|
||||
foreach ($peopleDetails as $person) {
|
||||
// No need for converting the entire array, FPDF with iconv handles it.
|
||||
$pdf->generateBadge($person);
|
||||
}
|
||||
|
||||
// 2. Generate PDF content as a string
|
||||
$pdfData = $pdf->Output('S');
|
||||
error_log($correlation_id . ': PDF data generated. Length: ' . strlen($pdfData) . ' bytes.');
|
||||
error_log($correlation_id . ': Memory usage: ' . memory_get_usage());
|
||||
|
||||
// 3. Validate PDF data
|
||||
if (empty($pdfData) || !is_string($pdfData)) {
|
||||
if (ob_get_length()) ob_end_clean();
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json');
|
||||
$error_message = 'Failed to generate PDF data.';
|
||||
error_log($correlation_id . ': ' . $error_message);
|
||||
echo json_encode(['error' => ['message' => $error_message], 'correlation_id' => $correlation_id]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. Clean any potential output that occurred before this point
|
||||
if (ob_get_length()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// 5. Send correct headers
|
||||
header('Content-Type: application/pdf');
|
||||
header('Content-Disposition: attachment; filename="badges.pdf"');
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
header('Cache-Control: private, max-age=0, must-revalidate');
|
||||
header('Pragma: public');
|
||||
header('Content-Length: ' . strlen($pdfData));
|
||||
|
||||
// 6. Output the raw PDF data and terminate
|
||||
echo $pdfData;
|
||||
exit;
|
||||
52
_bulk_update_status.php
Normal file
52
_bulk_update_status.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
$person_ids = json_decode($_POST['person_ids'] ?? '[]');
|
||||
$process_id = $_POST['process_id'] ?? null;
|
||||
$status = $_POST['status'] ?? null;
|
||||
$reason = $_POST['reason'] ?? '';
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (empty($person_ids) || !$process_id || !$status) {
|
||||
throw new WorkflowRuleFailedException('Missing parameters: person_ids, process_id, and status are required.');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$placeholders = implode(',', array_fill(0, count($person_ids), '?'));
|
||||
$stmt = $pdo->prepare("SELECT id FROM process_instances WHERE process_definition_id = ? AND person_id IN ($placeholders)");
|
||||
$params = array_merge([$process_id], $person_ids);
|
||||
$stmt->execute($params);
|
||||
$instance_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (empty($instance_ids)) {
|
||||
$_SESSION['flash_message'] = "No instances found for the selected people and process.";
|
||||
header('Location: ' . $_SERVER['HTTP_REFERER']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$statuses = [];
|
||||
foreach ($instance_ids as $instance_id) {
|
||||
$statuses[] = [
|
||||
'instance_id' => $instance_id,
|
||||
'status' => $status,
|
||||
'reason' => $reason,
|
||||
'user_id' => $userId
|
||||
];
|
||||
}
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$results = $workflowEngine->bulkManualStatus($statuses);
|
||||
|
||||
$_SESSION['flash_message'] = "Bulk status update completed.";
|
||||
$_SESSION['bulk_results'] = $results;
|
||||
|
||||
header('Location: ' . $_SERVER['HTTP_REFERER']);
|
||||
exit;
|
||||
121
_create_person.php
Normal file
121
_create_person.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
register_error_handler();
|
||||
require_once 'lib/i18n.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => ['message' => t('error.method_not_allowed', 'Niedozwolona metoda.')], 'correlation_id' => uniqid()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$first_name = $_POST['first_name'] ?? '';
|
||||
$last_name = $_POST['last_name'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
$company_name = $_POST['company_name'] ?? null;
|
||||
$phone = $_POST['phone'] ?? null;
|
||||
$role = $_POST['role'] ?? 'guest';
|
||||
$functions = isset($_POST['functions']) ? (array)$_POST['functions'] : [];
|
||||
$bni_group_id = isset($_POST['bni_group_id']) && !empty($_POST['bni_group_id']) ? $_POST['bni_group_id'] : null;
|
||||
|
||||
$nip = $_POST['nip'] ?? null;
|
||||
$industry = $_POST['industry'] ?? null;
|
||||
$company_size_revenue = $_POST['company_size_revenue'] ?? null;
|
||||
$business_description = $_POST['business_description'] ?? null;
|
||||
|
||||
if (empty($first_name) || empty($last_name) || empty($email) || empty($password)) {
|
||||
http_response_code(422);
|
||||
echo json_encode(['error' => ['message' => t('error.missing_fields', 'Imię, nazwisko, email i hasło są wymagane.')], 'correlation_id' => uniqid()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($role !== 'member') {
|
||||
$bni_group_id = null;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$sql = 'INSERT INTO people (first_name, last_name, email, password, company_name, phone, role, bni_group_id, nip, industry, company_size_revenue, business_description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$first_name, $last_name, $email, password_hash($password, PASSWORD_DEFAULT), $company_name, $phone, $role, $bni_group_id, $nip, $industry, $company_size_revenue, $business_description]);
|
||||
$personId = $pdo->lastInsertId();
|
||||
|
||||
$upload_dir = 'uploads/people/' . $personId . '/';
|
||||
if (!is_dir($upload_dir)) {
|
||||
if (!mkdir($upload_dir, 0777, true) && !is_dir($upload_dir)) {
|
||||
throw new RuntimeException(sprintf('Directory "%s" was not created', $upload_dir));
|
||||
}
|
||||
}
|
||||
|
||||
$file_fields = [
|
||||
'company_logo' => 'company_logo_path',
|
||||
'person_photo' => 'person_photo_path',
|
||||
'gains_sheet' => 'gains_sheet_path',
|
||||
'top_wanted_contacts' => 'top_wanted_contacts_path',
|
||||
'top_owned_contacts' => 'top_owned_contacts_path'
|
||||
];
|
||||
$file_paths_to_update = [];
|
||||
|
||||
foreach ($file_fields as $form_field_name => $db_column_name) {
|
||||
if (isset($_FILES[$form_field_name]) && $_FILES[$form_field_name]['error'] == UPLOAD_ERR_OK) {
|
||||
$tmp_name = $_FILES[$form_field_name]['tmp_name'];
|
||||
$original_name = basename($_FILES[$form_field_name]['name']);
|
||||
$file_ext = pathinfo($original_name, PATHINFO_EXTENSION);
|
||||
$new_filename = uniqid($form_field_name . '_', true) . '.' . $file_ext;
|
||||
$destination = $upload_dir . $new_filename;
|
||||
|
||||
if (move_uploaded_file($tmp_name, $destination)) {
|
||||
$file_paths_to_update[$db_column_name] = $destination;
|
||||
} else {
|
||||
throw new RuntimeException("Failed to move uploaded file for {$form_field_name}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($file_paths_to_update)) {
|
||||
$sql_parts = [];
|
||||
$params = [];
|
||||
foreach ($file_paths_to_update as $column => $path) {
|
||||
$sql_parts[] = "$column = ?";
|
||||
$params[] = $path;
|
||||
}
|
||||
$params[] = $personId;
|
||||
$sql = "UPDATE people SET " . implode(', ', $sql_parts) . " WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
}
|
||||
|
||||
if (!empty($functions)) {
|
||||
$sql = "INSERT INTO user_functions (user_id, function_id) VALUES (?, ?)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
foreach ($functions as $functionId) {
|
||||
$stmt->execute([$personId, $functionId]);
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
echo json_encode(['success' => true, 'person_id' => $personId, 'message' => 'Person created successfully.']);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
if ($e->errorInfo[1] == 1062) {
|
||||
http_response_code(409); // Conflict
|
||||
echo json_encode(['error' => ['message' => t('error.email_exists', 'Konto z tym adresem email już istnieje.')], 'correlation_id' => uniqid()]);
|
||||
} else {
|
||||
throw $e; // Re-throw to be caught by the global error handler
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
throw $e; // Re-throw to be caught by the global error handler
|
||||
}
|
||||
20
_delete_bni_group.php
Normal file
20
_delete_bni_group.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$id = $_GET['id'];
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM bni_groups WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$_SESSION['success_message'] = 'BNI Group deleted successfully!';
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = 'Error deleting BNI group: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: bni_groups.php');
|
||||
exit;
|
||||
48
_delete_calendar_event.php
Normal file
48
_delete_calendar_event.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$event_id = $_GET['id'];
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Check if the event is a parent event
|
||||
$stmt = $pdo->prepare("SELECT parent_event_id FROM calendar_events WHERE id = ?");
|
||||
$stmt->execute([$event_id]);
|
||||
$event = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($event) {
|
||||
if ($event['parent_event_id'] === null) {
|
||||
// It's a parent event, delete it and all its children
|
||||
$stmt_delete_children = $pdo->prepare("DELETE FROM calendar_events WHERE parent_event_id = ?");
|
||||
$stmt_delete_children->execute([$event_id]);
|
||||
}
|
||||
|
||||
// Delete the event itself
|
||||
$stmt_delete = $pdo->prepare("DELETE FROM calendar_events WHERE id = ?");
|
||||
$stmt_delete->execute([$event_id]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
header("Location: calendar.php");
|
||||
exit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
error_log($e->getMessage());
|
||||
header("Location: calendar.php?error=db_error");
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
header("Location: calendar.php");
|
||||
exit();
|
||||
}
|
||||
16
_delete_event_type.php
Normal file
16
_delete_event_type.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$id = $_GET['id'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM event_types WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
session_start();
|
||||
$_SESSION['success_message'] = 'Event type deleted successfully.';
|
||||
header('Location: event_types.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
21
_delete_function.php
Normal file
21
_delete_function.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id']) || !isset($_GET['id'])) {
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
include 'db/config.php';
|
||||
|
||||
$id = $_GET['id'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM functions WHERE id = :id");
|
||||
$stmt->execute(['id' => $id]);
|
||||
|
||||
// Optional: Also delete user_functions associated with this function
|
||||
$stmt = $pdo->prepare("DELETE FROM user_functions WHERE function_id = :function_id");
|
||||
$stmt->execute(['function_id' => $id]);
|
||||
|
||||
header('Location: functions.php');
|
||||
exit();
|
||||
51
_delete_person.php
Normal file
51
_delete_person.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
session_start();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['person_id'])) {
|
||||
$id = $_POST['person_id'];
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM people WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
echo json_encode(['success' => true, 'message' => 'Osoba usunięta pomyślnie.']);
|
||||
} else {
|
||||
http_response_code(404);
|
||||
echo json_encode(['success' => false, 'error' => 'Nie znaleziono osoby.']);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
// Log the real error to a secure log file
|
||||
error_log("Database error on person delete: " . $e->getMessage());
|
||||
// Send a generic error message to the client
|
||||
echo json_encode(['success' => false, 'error' => 'Błąd serwera podczas usuwania osoby.']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Brak ID osoby.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Keep GET for backwards compatibility or simple cases, but it redirects.
|
||||
if (isset($_GET['id'])) {
|
||||
$id = $_GET['id'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM people WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
$_SESSION['success_message'] = 'Osoba usunięta pomyślnie.';
|
||||
header('Location: persons.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => false, 'error' => 'Nieprawidłowa metoda żądania.']);
|
||||
?>
|
||||
28
_footer.php
Normal file
28
_footer.php
Normal file
@ -0,0 +1,28 @@
|
||||
<!-- Bootstrap Bundle with Popper -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<!-- jQuery UI -->
|
||||
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
|
||||
<link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
|
||||
|
||||
<!-- Custom JS -->
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const sidebarLinks = document.querySelectorAll('#sidebar .nav-link');
|
||||
const currentPath = window.location.pathname.split('/').pop();
|
||||
|
||||
sidebarLinks.forEach(link => {
|
||||
const linkPath = link.getAttribute('href').split('/').pop();
|
||||
if (linkPath === currentPath) {
|
||||
link.classList.add('active');
|
||||
} else {
|
||||
link.classList.remove('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
40
_get_event_details.php
Normal file
40
_get_event_details.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("HTTP/1.1 401 Unauthorized");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$event_id = $_GET['id'];
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT c.*, t.name as type_name, GROUP_CONCAT(g.id) as group_ids
|
||||
FROM calendar_events c
|
||||
LEFT JOIN event_types t ON c.event_type_id = t.id
|
||||
LEFT JOIN calendar_event_groups ceg ON c.id = ceg.calendar_event_id
|
||||
LEFT JOIN bni_groups g ON ceg.bni_group_id = g.id
|
||||
WHERE c.id = ?
|
||||
GROUP BY c.id
|
||||
");
|
||||
$stmt->execute([$event_id]);
|
||||
$event = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($event) {
|
||||
if ($event['group_ids']) {
|
||||
$event['group_ids'] = explode(',', $event['group_ids']);
|
||||
} else {
|
||||
$event['group_ids'] = [];
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($event);
|
||||
} else {
|
||||
header("HTTP/1.1 404 Not Found");
|
||||
}
|
||||
} else {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
}
|
||||
41
_get_future_meetings.php
Normal file
41
_get_future_meetings.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$bni_group_id = isset($_GET['bni_group_id']) ? (int)$_GET['bni_group_id'] : 0;
|
||||
$offset = isset($_GET['offset']) ? (int)$_GET['offset'] : 0;
|
||||
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 2;
|
||||
|
||||
if ($bni_group_id === 0) {
|
||||
echo json_encode(['error' => 'Invalid BNI Group ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
$sql = "
|
||||
SELECT ce.*, bg.name as group_name
|
||||
FROM calendar_events ce
|
||||
JOIN calendar_event_groups ceg ON ce.id = ceg.calendar_event_id
|
||||
JOIN bni_groups bg ON ceg.bni_group_id = bg.id
|
||||
WHERE ceg.bni_group_id = :bni_group_id
|
||||
AND ce.start_datetime > NOW()
|
||||
ORDER BY ce.start_datetime ASC
|
||||
LIMIT :limit OFFSET :offset
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->bindParam(':bni_group_id', $bni_group_id, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$events = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode($events);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
|
||||
}
|
||||
328
_get_instance_details.php
Normal file
328
_get_instance_details.php
Normal file
@ -0,0 +1,328 @@
|
||||
<?php
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'lib/i18n.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'lib/WorkflowExceptions.php';
|
||||
|
||||
session_start();
|
||||
|
||||
// Security check
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$person_id = $_GET['person_id'] ?? null;
|
||||
$subject_id = $_GET['subject_id'] ?? $person_id;
|
||||
$subject_type = $_GET['subject_type'] ?? 'person';
|
||||
$cycle_key = $_GET['cycle_key'] ?? null;
|
||||
$process_definition_id = $_GET['process_id'] ?? null;
|
||||
$process_code = $_GET['process_code'] ?? null;
|
||||
|
||||
$pdo = db();
|
||||
|
||||
if (!$process_definition_id && $process_code) {
|
||||
$stmt = $pdo->prepare("SELECT id FROM process_definitions WHERE code = ? AND is_latest = 1 LIMIT 1");
|
||||
$stmt->execute([$process_code]);
|
||||
$process_definition_id = $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
if (!$subject_id || !$process_definition_id) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing subject_id or process_id']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
$engine = new WorkflowEngine();
|
||||
|
||||
// Fetch Subject and Process Definition details first
|
||||
$subjectName = 'Unknown';
|
||||
if ($subject_type === 'person') {
|
||||
$stmt_person = $pdo->prepare("SELECT first_name, last_name FROM people WHERE id = ?");
|
||||
$stmt_person->execute([$subject_id]);
|
||||
$person = $stmt_person->fetch();
|
||||
if ($person) {
|
||||
$subjectName = $person['first_name'] . ' ' . $person['last_name'];
|
||||
}
|
||||
} elseif ($subject_type === 'meeting') {
|
||||
$stmt_meeting = $pdo->prepare("SELECT m.meeting_datetime, bg.name as group_name FROM meetings m JOIN bni_groups bg ON m.bni_group_id = bg.id WHERE m.id = ?");
|
||||
$stmt_meeting->execute([$subject_id]);
|
||||
$meeting = $stmt_meeting->fetch();
|
||||
if ($meeting) {
|
||||
$subjectName = $meeting['group_name'] . ' - ' . date('d.m.Y', strtotime($meeting['meeting_datetime']));
|
||||
}
|
||||
} 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->execute([$process_definition_id]);
|
||||
$process = $stmt_process->fetch();
|
||||
|
||||
if (!$process) {
|
||||
http_response_code(404);
|
||||
echo "<p class='text-danger'>Could not find process.</p>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Try to find an existing instance
|
||||
$instance = $engine->getActiveInstanceForSubject($process['code'], $subject_type, $subject_id, $cycle_key);
|
||||
|
||||
?>
|
||||
|
||||
<!-- Title for the modal, to be grabbed by JS -->
|
||||
<div id="instance-modal-title" class="d-none">
|
||||
<?= htmlspecialchars($subjectName) ?> - <?= htmlspecialchars($process['name']) ?>
|
||||
</div>
|
||||
|
||||
<?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="restartProcessBtn" class="btn btn-sm btn-primary" data-subject-type="<?= $subject_type ?>" data-subject-id="<?= $subject_id ?>" data-person-id="<?= $person_id ?>" data-process-code="<?= htmlspecialchars($process['code']) ?>" data-mode="create_new_run">
|
||||
<?= t('modal.start_new_instance', 'Rozpocznij od nowa') ?>
|
||||
</button>
|
||||
</div>
|
||||
<?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);
|
||||
$instanceData = $instance['data_json'] ? json_decode($instance['data_json'], true) : [];
|
||||
?>
|
||||
|
||||
<?php if ($isChecklist): ?>
|
||||
<?php
|
||||
$tasks = $definition['tasks'] ?? [];
|
||||
?>
|
||||
<div class="checklist-modal-container" data-instance-id="<?= $instanceId ?>">
|
||||
<h5>Zadania do wykonania</h5>
|
||||
<div class="checklist-container">
|
||||
<?php foreach ($tasks as $task):
|
||||
$isChecked = !empty($instanceData[$task['code']]);
|
||||
?>
|
||||
<div class="form-check mb-2" style="font-size: 1.15rem;">
|
||||
<input class="form-check-input task-checkbox-modal" type="checkbox" value="" style="cursor: pointer; border-radius: 0.25em;"
|
||||
data-task-code="<?= htmlspecialchars($task['code']) ?>" <?= $isChecked ? 'checked' : '' ?> id="chk_<?= htmlspecialchars($task['code']) ?>">
|
||||
<label class="form-check-label pt-1" for="chk_<?= htmlspecialchars($task['code']) ?>" title="<?= htmlspecialchars($task['name']) ?>" style="cursor: pointer;">
|
||||
<?= htmlspecialchars($task['name']) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$currentNodeId = $instance['current_node_id'];
|
||||
$all_nodes = $engine->getProcessDefinitionNodes($process_definition_id);
|
||||
$availableTransitions = $engine->getAvailableTransitions($instanceId);
|
||||
|
||||
$available_target_node_ids = array_map(function($t) { return $t['to']; }, $availableTransitions);
|
||||
$available_transitions_map = [];
|
||||
foreach ($availableTransitions as $t) {
|
||||
$available_transitions_map[$t['id']] = $t;
|
||||
}
|
||||
|
||||
$visited_nodes = [];
|
||||
foreach ($events as $event) {
|
||||
if ($event['node_id']) {
|
||||
$visited_nodes[$event['node_id']] = true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="process-steps-container">
|
||||
<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;
|
||||
|
||||
$status_icon = '';
|
||||
$li_class = '';
|
||||
|
||||
if ($is_current) {
|
||||
$li_class = 'list-group-item-primary';
|
||||
$status_icon = '<i class="bi bi-arrow-right-circle-fill text-primary me-2"></i>';
|
||||
} elseif ($is_completed) {
|
||||
$li_class = 'list-group-item-success';
|
||||
$status_icon = '<i class="bi bi-check-circle-fill text-success me-2"></i>';
|
||||
} else {
|
||||
$li_class = 'text-muted';
|
||||
$status_icon = '<i class="bi bi-circle me-2"></i>';
|
||||
}
|
||||
?>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center <?= $li_class ?>">
|
||||
<div>
|
||||
<?= $status_icon ?>
|
||||
<strong><?= htmlspecialchars($node['ui_hints']['title']) ?></strong>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<div class="mt-3">
|
||||
<h5><?= t('modal.available_actions', 'Dostępne akcje') ?></h5>
|
||||
<?php
|
||||
$currentNode = $all_nodes[$currentNodeId] ?? null;
|
||||
if ($currentNode && isset($currentNode['ui_hints']['form_schema'])):
|
||||
?>
|
||||
<form id="transition-form">
|
||||
<?php foreach ($currentNode['ui_hints']['form_schema'] as $field):
|
||||
$fieldName = $field['name'];
|
||||
$currentValue = $instanceData[$fieldName] ?? null;
|
||||
?>
|
||||
<?php if ($field['type'] === 'checkbox'): ?>
|
||||
<div class="form-check mb-3" style="font-size: 1.15rem;">
|
||||
<input type="hidden" name="<?= htmlspecialchars($fieldName) ?>" value="0">
|
||||
<input type="checkbox" id="<?= htmlspecialchars($fieldName) ?>" name="<?= htmlspecialchars($fieldName) ?>" class="form-check-input" value="1" style="cursor: pointer; border-radius: 0.25em;" <?= (!empty($currentValue) || (!isset($instanceData[$fieldName]) && !empty($field['default']))) ? 'checked' : '' ?>/>
|
||||
<label for="<?= htmlspecialchars($fieldName) ?>" class="form-check-label pt-1" style="cursor: pointer;"><?= htmlspecialchars($field['label']) ?></label>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="mb-3">
|
||||
<label for="<?= htmlspecialchars($fieldName) ?>" class="form-label"><?= htmlspecialchars($field['label']) ?></label>
|
||||
<?php if ($field['type'] === 'textarea'): ?>
|
||||
<textarea id="<?= htmlspecialchars($fieldName) ?>" name="<?= htmlspecialchars($fieldName) ?>" class="form-control"><?= htmlspecialchars($currentValue ?? '') ?></textarea>
|
||||
<?php elseif ($field['type'] === 'select'): ?>
|
||||
<select id="<?= htmlspecialchars($fieldName) ?>" name="<?= htmlspecialchars($fieldName) ?>" class="form-select">
|
||||
<?php foreach ($field['options'] as $option): ?>
|
||||
<option value="<?= htmlspecialchars($option['value']) ?>" <?= $currentValue == $option['value'] ? 'selected' : '' ?>><?= htmlspecialchars($option['label']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php else: ?>
|
||||
<input type="<?= htmlspecialchars($field['type']) ?>" id="<?= htmlspecialchars($fieldName) ?>" name="<?= htmlspecialchars($fieldName) ?>" class="form-control" value="<?= htmlspecialchars((string)($currentValue ?? (($field['default'] ?? '') === 'now' ? date('Y-m-d\TH:i') : ($field['default'] ?? '')))) ?>">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php if (empty($availableTransitions)): ?>
|
||||
<p>No actions available.</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($availableTransitions as $transition): ?>
|
||||
<button class="btn btn-sm btn-primary apply-transition-btn"
|
||||
data-instance-id="<?= $instanceId ?>"
|
||||
data-transition-id="<?= $transition['id'] ?>">
|
||||
<?= htmlspecialchars($transition['name']) ?>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="add-note-container">
|
||||
<h5><?= t('modal.add_note', '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="<?= $instanceId ?>"><?= t('modal.add_note', 'Dodaj notatkę') ?></button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="history-container">
|
||||
<h5><?= t('modal.history', 'Historia') ?></h5>
|
||||
<?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">
|
||||
<?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>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php else: // NO INSTANCE EXISTS ?>
|
||||
<?php
|
||||
$eligibility = ['is_eligible' => true, 'reasons' => []];
|
||||
if ($subject_type === 'person') {
|
||||
$eligibility = $engine->checkEligibility($subject_id, $process_definition_id);
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="text-center">
|
||||
<?php if ($eligibility['is_eligible']): ?>
|
||||
<h4><?= t('modal.process_not_started', 'Proces nie został uruchomiony') ?></h4>
|
||||
<p>This process has not been started for this 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 ?>">
|
||||
<?= t('modal.start_process', 'Uruchom proces') ?>
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<h4>Not Eligible</h4>
|
||||
<p>This person is not eligible to start this process.</p>
|
||||
<ul class="list-group list-group-flush text-start">
|
||||
<?php foreach ($eligibility['reasons'] as $reason): ?>
|
||||
<li class="list-group-item list-group-item-danger"><?= htmlspecialchars($reason) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
35
_get_meeting_attendance.php
Normal file
35
_get_meeting_attendance.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
session_start();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$response = ['success' => false, 'message' => 'An error occurred.', 'attendance' => []];
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
$response['message'] = 'You must be logged in to perform this action.';
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
$groupId = $_GET['group_id'] ?? null;
|
||||
$meetingDate = $_GET['meeting_date'] ?? null;
|
||||
|
||||
if (!$groupId || !$meetingDate) {
|
||||
$response['message'] = 'Missing required parameters.';
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$attendance = $workflowEngine->getMeetingAttendanceByGroupAndDate((int)$groupId, $meetingDate);
|
||||
$response['success'] = true;
|
||||
$response['attendance'] = $attendance;
|
||||
} catch (Exception $e) {
|
||||
error_log($e->getMessage());
|
||||
$response['message'] = 'Error fetching attendance: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
27
_get_meeting_details.php
Normal file
27
_get_meeting_details.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
session_start();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$response = ['success' => false, 'message' => 'Invalid request'];
|
||||
|
||||
$personId = $_GET['person_id'] ?? null;
|
||||
$bniGroupId = $_GET['bni_group_id'] ?? null;
|
||||
$meetingDatetime = $_GET['meeting_datetime'] ?? null;
|
||||
$userId = $_SESSION['user_id'] ?? 0; // Ensure you have a user ID in the session
|
||||
|
||||
if ($personId && $bniGroupId && $meetingDatetime && $userId) {
|
||||
try {
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$details = $workflowEngine->getMeetingDetails((int)$personId, (int)$bniGroupId, $meetingDatetime);
|
||||
$response = ['success' => true, 'details' => $details];
|
||||
} catch (Exception $e) {
|
||||
$response['message'] = $e->getMessage();
|
||||
}
|
||||
} else {
|
||||
$response['message'] = 'Missing required parameters.';
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
66
_get_person_details.php
Normal file
66
_get_person_details.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
header('Content-Type: application/json');
|
||||
try {
|
||||
$person_id = $_GET['id'];
|
||||
$pdo = db();
|
||||
|
||||
// Fetch person details
|
||||
$stmt = $pdo->prepare("SELECT * FROM people WHERE id = ?");
|
||||
$stmt->execute([$person_id]);
|
||||
$person = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch all functions
|
||||
$stmt = $pdo->query("
|
||||
SELECT f.id, f.name, bg.name as group_name
|
||||
FROM functions f
|
||||
LEFT JOIN bni_groups bg ON f.bni_group_id = bg.id
|
||||
ORDER BY bg.display_order, f.display_order
|
||||
");
|
||||
$all_functions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch person's functions
|
||||
$stmt = $pdo->prepare("SELECT function_id FROM user_functions WHERE user_id = ?");
|
||||
$stmt->execute([$person_id]);
|
||||
$person_functions = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
|
||||
// --- Fetch Follow-up Process Summary ---
|
||||
$follow_up_summary = null;
|
||||
$stmt_def = $pdo->prepare("SELECT id FROM process_definitions WHERE code = 'guest_handling' LIMIT 1");
|
||||
$stmt_def->execute();
|
||||
$follow_up_def_id = $stmt_def->fetchColumn();
|
||||
|
||||
if ($follow_up_def_id) {
|
||||
$stmt_inst = $pdo->prepare("SELECT * FROM process_instances WHERE person_id = ? AND process_definition_id = ? ORDER BY id DESC LIMIT 1");
|
||||
$stmt_inst->execute([$person_id, $follow_up_def_id]);
|
||||
$instance = $stmt_inst->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($instance) {
|
||||
$data = $instance['data_json'] ? json_decode($instance['data_json'], true) : [];
|
||||
$follow_up_summary = [
|
||||
'last_call_outcome' => $data['outcome_status'] ?? null,
|
||||
'last_call_date' => $data['call_date'] ?? null,
|
||||
'next_contact_date' => $data['next_contact_date'] ?? null,
|
||||
'final_outcome' => $instance['current_status'], // e.g., completed, terminated
|
||||
'reason' => $instance['current_reason']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$response = [
|
||||
'person' => $person,
|
||||
'all_functions' => $all_functions,
|
||||
'person_functions' => $person_functions,
|
||||
'follow_up_summary' => $follow_up_summary
|
||||
];
|
||||
|
||||
echo json_encode($response);
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
96
_get_process_bulk_details.php
Normal file
96
_get_process_bulk_details.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_GET['process_id'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Process ID is required.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$process_id = $_GET['process_id'];
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// 1. Get process definition details
|
||||
$stmt_def = $pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$process_id]);
|
||||
$process_definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$process_definition) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Process definition not found.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!empty($process_definition['definition_json'])) {
|
||||
$process_definition['definition_json'] = json_decode($process_definition['definition_json'], true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception("Failed to decode process definition JSON. Error: " . json_last_error_msg());
|
||||
}
|
||||
} else {
|
||||
$process_definition['definition_json'] = [];
|
||||
}
|
||||
|
||||
// 2. Get all instances for this process
|
||||
$stmt_instances = $pdo->prepare("SELECT * FROM process_instances WHERE process_definition_id = ?");
|
||||
$stmt_instances->execute([$process_id]);
|
||||
$instances = $stmt_instances->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$instance_ids = array_map(function($i) { return $i['id']; }, $instances);
|
||||
|
||||
// 3. Get all events for these instances
|
||||
$events = [];
|
||||
if (!empty($instance_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($instance_ids), '?'));
|
||||
$stmt_events = $pdo->prepare("SELECT * FROM process_events WHERE process_instance_id IN ($placeholders) ORDER BY created_at, id");
|
||||
$stmt_events->execute($instance_ids);
|
||||
$all_events = $stmt_events->fetchAll(PDO::FETCH_ASSOC);
|
||||
// Group events by instance_id
|
||||
foreach ($all_events as $event) {
|
||||
$events[$event['process_instance_id']][] = $event;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Get People details
|
||||
$people_ids = array_unique(array_column($instances, 'person_id'));
|
||||
$people = [];
|
||||
if (!empty($people_ids)) {
|
||||
$valid_people_ids = array_filter($people_ids, 'is_numeric');
|
||||
|
||||
if (!empty($valid_people_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($valid_people_ids), '?'));
|
||||
$stmt_people = $pdo->prepare("SELECT id, first_name, last_name FROM people WHERE id IN ($placeholders)");
|
||||
$stmt_people->execute(array_values($valid_people_ids));
|
||||
$people_results = $stmt_people->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($people_results as $person) {
|
||||
$people[$person['id']] = $person;
|
||||
$people[$person['id']]['name'] = trim($person['first_name'] . ' ' . $person['last_name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assemble the response
|
||||
// Ensure steps are available, even if the JSON is empty or malformed.
|
||||
$steps = !empty($process_definition['definition_json']['steps']) ? $process_definition['definition_json']['steps'] : [];
|
||||
|
||||
$response = [
|
||||
'process' => $process_definition,
|
||||
'steps' => $steps,
|
||||
'instances' => $instances,
|
||||
'events' => $events,
|
||||
'people' => $people
|
||||
];
|
||||
|
||||
echo json_encode($response);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'A database error occurred.', 'details' => $e->getMessage()]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'A general error occurred.', 'details' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
53
_header.php
Normal file
53
_header.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . "/lib/i18n.php";
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?php echo getenv('PROJECT_NAME') ?: 'BNI obsługa regionu'; ?> - Dashboard</title>
|
||||
<meta name="description" content="<?php echo getenv('PROJECT_DESCRIPTION') ?: 'A modern web application.'; ?>">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
|
||||
<!-- OG Meta Tags -->
|
||||
<meta property="og:title" content="<?php echo getenv('PROJECT_NAME') ?: 'BNI obsługa regionu'; ?>">
|
||||
<meta property="og:description" content="<?php echo getenv('PROJECT_DESCRIPTION') ?: 'A modern web application.'; ?>">
|
||||
<meta property="og:image" content="<?php echo getenv('PROJECT_IMAGE_URL') ?: 'https://via.placeholder.com/1200x630.png?text=Visit+My+App'; ?>">
|
||||
<meta property="og:url" content="<?php echo (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]"; ?>">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="<?php echo getenv('PROJECT_NAME') ?: 'BNI obsługa regionu'; ?>">
|
||||
<meta name="twitter:description" content="<?php echo getenv('PROJECT_DESCRIPTION') ?: 'A modern web application.'; ?>">
|
||||
<meta name="twitter:image" content="<?php echo getenv('PROJECT_IMAGE_URL') ?: 'https://via.placeholder.com/1200x630.png?text=Visit+My+App'; ?>'>
|
||||
<link rel="icon" href="assets/pasted-20260111-144117-aba8ec29.jpg" type="image/jpeg">
|
||||
|
||||
</head>
|
||||
<body class="<?php echo isset($_COOKIE['sidebar_collapsed']) && $_COOKIE['sidebar_collapsed'] === 'true' ? 'sidebar-collapsed' : ''; ?>">
|
||||
<script>
|
||||
(function() {
|
||||
if (localStorage.getItem('sidebarCollapsed') === 'true') {
|
||||
document.body.classList.add('sidebar-collapsed');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
37
_init_instances.php
Normal file
37
_init_instances.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Get all active people
|
||||
$stmt_people = $pdo->prepare("SELECT id FROM people WHERE active = 1");
|
||||
$stmt_people->execute();
|
||||
$people = $stmt_people->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// Get all active process definitions
|
||||
$stmt_processes = $pdo->prepare("SELECT id FROM process_definitions WHERE is_active = 1");
|
||||
$stmt_processes->execute();
|
||||
$processes = $stmt_processes->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$insert_stmt = $pdo->prepare("INSERT IGNORE INTO process_instances (person_id, process_definition_id, current_status) VALUES (?, ?, 'none')");
|
||||
|
||||
$count = 0;
|
||||
foreach ($people as $person_id) {
|
||||
foreach ($processes as $process_id) {
|
||||
$insert_stmt->execute([$person_id, $process_id]);
|
||||
if ($insert_stmt->rowCount() > 0) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['flash_message'] = "Initialized $count new process instances.";
|
||||
|
||||
header("Location: process_dashboard.php"); // Redirect to the main dashboard
|
||||
exit;
|
||||
53
_init_single_instance.php
Normal file
53
_init_single_instance.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
register_error_handler();
|
||||
|
||||
require_once 'db/config.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => ['message' => 'Authentication required.']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
$personId = filter_input(INPUT_POST, 'person_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);
|
||||
$mode = filter_input(INPUT_POST, 'mode', FILTER_SANITIZE_STRING) ?: 'resume_or_create';
|
||||
|
||||
if ($personId && !$subjectType) {
|
||||
$subjectType = 'person';
|
||||
$subjectId = $personId;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
if ($processDefinitionId && !$processCode) {
|
||||
$stmt = $pdo->prepare("SELECT code FROM process_definitions WHERE id = ?");
|
||||
$stmt->execute([$processDefinitionId]);
|
||||
$processCode = $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
if (!$subjectId || !$subjectType || !$processCode) {
|
||||
throw new InvalidArgumentException('Invalid or missing subject_type, subject_id, or process_code.');
|
||||
}
|
||||
|
||||
$engine = new WorkflowEngine();
|
||||
$instance = $engine->getOrCreateInstanceBySubject($subjectType, $subjectId, $processCode, $userId, $mode);
|
||||
|
||||
if ($instance && isset($instance['instance_id'])) {
|
||||
echo json_encode(['success' => true, 'message' => 'Process initialized successfully.', 'instance_id' => $instance['instance_id']]);
|
||||
} else {
|
||||
throw new Exception("Failed to initialize process for an unknown reason.");
|
||||
}
|
||||
19
_navbar.php
Normal file
19
_navbar.php
Normal file
@ -0,0 +1,19 @@
|
||||
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
|
||||
<button class="navbar-toggler d-none d-md-block" type="button" id="sidebar-toggler" aria-label="Toggle sidebar" aria-expanded="true">
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#"><img src="assets/pasted-20260111-143449-befa41d3.png" class="d-inline-block align-top navbar-logo" alt=""> <?php echo getenv('PROJECT_NAME') ?: 'BNI obsługa regionu'; ?></a>
|
||||
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<ul class="navbar-nav flex-row px-3">
|
||||
<?php if (isset($_SESSION['user_name'])): ?>
|
||||
<li class="nav-item text-nowrap">
|
||||
<span class="nav-link">Witaj, <?= htmlspecialchars($_SESSION['user_name']) ?></span>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<li class="nav-item text-nowrap ms-2">
|
||||
<a class="nav-link" href="logout.php">Wyloguj</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
241
_print_group_members_pdf.php
Normal file
241
_print_group_members_pdf.php
Normal file
@ -0,0 +1,241 @@
|
||||
<?php
|
||||
// Start output buffering to prevent any accidental output before headers
|
||||
ob_start();
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/lib/ErrorHandler.php';
|
||||
require_once __DIR__ . '/lib/tfpdf/tfpdf.php';
|
||||
require_once __DIR__ . '/lib/tfpdf/font/unifont/ttfonts.php';
|
||||
|
||||
// Disable error display to avoid corrupting PDF output
|
||||
ini_set('display_errors', 0);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
$group_name = $_GET['group_name'] ?? '';
|
||||
|
||||
if (empty($group_name)) {
|
||||
while (ob_get_level() > 0) ob_end_clean();
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'error' => 'Parameter group_name is required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = db();
|
||||
|
||||
// 1. Fetch group by name
|
||||
$stmt = $db->prepare('SELECT id, name FROM bni_groups WHERE LOWER(name) = LOWER(?)');
|
||||
$stmt->execute([trim($group_name)]);
|
||||
$group = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$group) {
|
||||
while (ob_get_level() > 0) ob_end_clean();
|
||||
http_response_code(404);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'error' => 'Group not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$groupId = $group['id'];
|
||||
$groupRealName = $group['name'];
|
||||
|
||||
// 2. Fetch members
|
||||
$stmt = $db->prepare('
|
||||
SELECT
|
||||
p.first_name,
|
||||
p.last_name,
|
||||
p.person_photo_path,
|
||||
p.company_name,
|
||||
p.company_logo_path,
|
||||
p.business_description,
|
||||
p.industry,
|
||||
p.company_size
|
||||
FROM people p
|
||||
WHERE p.bni_group_id = ?
|
||||
ORDER BY p.last_name ASC, p.first_name ASC
|
||||
');
|
||||
$stmt->execute([$groupId]);
|
||||
$members = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($members)) {
|
||||
while (ob_get_level() > 0) ob_end_clean();
|
||||
http_response_code(404);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'error' => 'No members found in this group']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Helper to validate images before adding to PDF
|
||||
$tempFiles = [];
|
||||
function getVerifiedImagePath($path, &$tempFiles) {
|
||||
if (empty($path)) return null;
|
||||
|
||||
// Local file
|
||||
if (strpos($path, 'http') !== 0) {
|
||||
$localPath = rtrim(realpath(__DIR__), '/') . '/' . ltrim($path, '/');
|
||||
if (!file_exists($localPath)) return null;
|
||||
$info = @getimagesize($localPath);
|
||||
if ($info && in_array($info[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG])) {
|
||||
return $localPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// URL
|
||||
$ch = curl_init($path);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$data = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($code >= 200 && $code < 300 && $data) {
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'pdfimg_');
|
||||
file_put_contents($tmp, $data);
|
||||
$info = @getimagesize($tmp);
|
||||
if ($info && in_array($info[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG])) {
|
||||
$tempFiles[] = $tmp;
|
||||
return $tmp;
|
||||
}
|
||||
@unlink($tmp);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. Create PDF
|
||||
$pdf = new tFPDF();
|
||||
$pdf->AddFont('DejaVu', '', 'DejaVuSans.ttf', true);
|
||||
$pdf->AddFont('DejaVu', 'B', 'DejaVuSans-Bold.ttf', true);
|
||||
|
||||
$pdf->SetMargins(15, 20, 15);
|
||||
$pdf->SetAutoPageBreak(true, 15);
|
||||
|
||||
foreach ($members as $member) {
|
||||
$pdf->AddPage();
|
||||
|
||||
// --- HEADER ---
|
||||
$pdf->SetFont('DejaVu', 'B', 12);
|
||||
$pdf->SetTextColor(150, 150, 150);
|
||||
$pdf->Cell(0, 10, 'Grupa: ' . $groupRealName, 0, 1, 'R');
|
||||
$pdf->Ln(5);
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
|
||||
// --- IMAGES ---
|
||||
$yStart = $pdf->GetY();
|
||||
$minY = $yStart;
|
||||
|
||||
// Left - Photo
|
||||
if (!empty($member['person_photo_path'])) {
|
||||
$validPath = getVerifiedImagePath($member['person_photo_path'], $tempFiles);
|
||||
if ($validPath) {
|
||||
@$pdf->Image($validPath, 15, $yStart, 40);
|
||||
$minY = max($minY, $yStart + 50);
|
||||
}
|
||||
}
|
||||
|
||||
// Right - Logo
|
||||
if (!empty($member['company_logo_path'])) {
|
||||
$validPath = getVerifiedImagePath($member['company_logo_path'], $tempFiles);
|
||||
if ($validPath) {
|
||||
@$pdf->Image($validPath, 155, $yStart, 40);
|
||||
$minY = max($minY, $yStart + 50);
|
||||
}
|
||||
}
|
||||
|
||||
// --- MAIN INFO ---
|
||||
$pdf->SetY($yStart + 10);
|
||||
|
||||
$name = trim($member['first_name'] . ' ' . $member['last_name']);
|
||||
if ($name === '') {
|
||||
$name = 'Nieznany członek';
|
||||
}
|
||||
|
||||
$pdf->SetFont('DejaVu', 'B', 22);
|
||||
// Add margins for text so it doesn't overlap images
|
||||
$pdf->SetLeftMargin(55);
|
||||
$pdf->SetRightMargin(55);
|
||||
|
||||
$pdf->Cell(100, 12, $name, 0, 1, 'C');
|
||||
|
||||
$companyName = trim($member['company_name'] ?? '');
|
||||
if ($companyName !== '') {
|
||||
$pdf->SetFont('DejaVu', 'B', 16);
|
||||
$pdf->SetTextColor(80, 80, 80);
|
||||
$pdf->MultiCell(100, 8, $companyName, 0, 'C');
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
}
|
||||
|
||||
// Reset margins
|
||||
$pdf->SetLeftMargin(15);
|
||||
$pdf->SetRightMargin(15);
|
||||
$pdf->SetX(15);
|
||||
|
||||
// Ensure we draw details below the images
|
||||
if ($pdf->GetY() < $minY) {
|
||||
$pdf->SetY($minY + 5);
|
||||
} else {
|
||||
$pdf->Ln(10);
|
||||
}
|
||||
|
||||
// --- DETAILS ---
|
||||
$pdf->SetFont('DejaVu', 'B', 12);
|
||||
|
||||
// Industry
|
||||
$pdf->Cell(45, 8, 'Branża:', 0, 0);
|
||||
$pdf->SetFont('DejaVu', '', 12);
|
||||
$industry = trim($member['industry'] ?? '');
|
||||
$pdf->Cell(0, 8, $industry !== '' ? $industry : '-', 0, 1);
|
||||
|
||||
// Company Size
|
||||
$pdf->SetFont('DejaVu', 'B', 12);
|
||||
$pdf->Cell(45, 8, 'Wielkość firmy:', 0, 0);
|
||||
$pdf->SetFont('DejaVu', '', 12);
|
||||
$companySize = trim($member['company_size'] ?? '');
|
||||
$pdf->Cell(0, 8, $companySize !== '' ? $companySize : '-', 0, 1);
|
||||
|
||||
$pdf->Ln(8);
|
||||
|
||||
// Description
|
||||
$pdf->SetFont('DejaVu', 'B', 12);
|
||||
$pdf->Cell(0, 8, 'Opis:', 0, 1);
|
||||
|
||||
$pdf->SetFont('DejaVu', '', 11);
|
||||
$desc = trim($member['business_description'] ?? '');
|
||||
if ($desc === '') {
|
||||
$desc = 'Brak opisu.';
|
||||
}
|
||||
|
||||
$pdf->MultiCell(0, 6, $desc);
|
||||
}
|
||||
|
||||
// Clear buffer before sending headers
|
||||
while (ob_get_level() > 0) ob_end_clean();
|
||||
|
||||
$safeName = preg_replace('/[^a-zA-Z0-9_\-]/', '_', strtolower($groupRealName));
|
||||
$filename = 'lista_czlonkow_' . $safeName . '.pdf';
|
||||
|
||||
header('Content-Type: application/pdf');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
header('Cache-Control: private, max-age=0, must-revalidate');
|
||||
header('Pragma: public');
|
||||
|
||||
// Output directly
|
||||
$pdf->Output('D', $filename);
|
||||
|
||||
// Cleanup
|
||||
foreach ($tempFiles as $tmp) {
|
||||
@unlink($tmp);
|
||||
}
|
||||
|
||||
exit;
|
||||
|
||||
} catch (Exception $e) {
|
||||
while (ob_get_level() > 0) ob_end_clean();
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'error' => 'Wystąpił błąd podczas generowania PDF', 'details' => $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
154
_save_process_definition.php
Normal file
154
_save_process_definition.php
Normal file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
|
||||
register_error_handler();
|
||||
|
||||
session_start();
|
||||
|
||||
function validate_definition_json($json) {
|
||||
if (empty($json)) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException('Process definition JSON cannot be empty.');
|
||||
}
|
||||
$data = json_decode($json, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException('Invalid JSON format in definition.');
|
||||
}
|
||||
|
||||
$allowed_statuses = ['none', 'negative', 'in_progress', 'positive', 'active', 'processing', 'paused', 'completed', 'terminated'];
|
||||
|
||||
if (isset($data['nodes'])) {
|
||||
foreach ($data['nodes'] as $node) {
|
||||
if (isset($node['ui_hints']['status']) && !in_array($node['ui_hints']['status'], $allowed_statuses)) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException('Invalid status in ui_hints. Allowed values are: ' . implode(', ', $allowed_statuses));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['transitions'])) {
|
||||
foreach ($data['transitions'] as $transition) {
|
||||
if (isset($transition['actions'])) {
|
||||
foreach ($transition['actions'] as $action) {
|
||||
if ($action['type'] === 'start_process' && isset($action['process_name'])) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException('Use process_code instead of process_name in transition actions.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['eligibility_rules'])) {
|
||||
foreach ($data['eligibility_rules'] as $rule) {
|
||||
if ($rule['type'] === 'process_completed' && isset($rule['process_name'])) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException('Use process_code instead of process_name in eligibility_rules.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($data['start_node_id'])) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException('Process definition is missing start_node_id.');
|
||||
}
|
||||
|
||||
$start_node_id = $data['start_node_id'];
|
||||
|
||||
if (!isset($data['nodes']) || !is_array($data['nodes'])) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException('Process definition is missing a valid "nodes" object.');
|
||||
}
|
||||
|
||||
if (!isset($data['nodes'][$start_node_id])) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException("start_node_id '{$start_node_id}' does not exist in nodes.");
|
||||
}
|
||||
|
||||
if (!isset($data['transitions']) || !is_array($data['transitions'])) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException('Process definition is missing a valid "transitions" array.');
|
||||
}
|
||||
|
||||
foreach ($data['transitions'] as $index => $transition) {
|
||||
if (!isset($transition['from']) || !isset($transition['to'])) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException("Transition at index {$index} is missing 'from' or 'to' property.");
|
||||
}
|
||||
if (!isset($data['nodes'][$transition['from']])) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException("Transition from an unknown node: '{$transition['from']}'.");
|
||||
}
|
||||
if (!isset($data['nodes'][$transition['to']])) {
|
||||
http_response_code(422);
|
||||
throw new WorkflowRuleFailedException("Transition to an unknown node: '{$transition['to']}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$processId = $_POST['process_id'] ?? null;
|
||||
$name = $_POST['name'] ?? '';
|
||||
$definition_json = $_POST['definition_json'] ?? '';
|
||||
$subject_scope = $_POST['subject_scope'] ?? 'person';
|
||||
|
||||
validate_definition_json($definition_json);
|
||||
|
||||
// Generate a simple code from the name
|
||||
$code = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $name)));
|
||||
|
||||
if (empty($name)) {
|
||||
throw new WorkflowRuleFailedException('Process name is required.');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$start_node = json_decode($definition_json, true)['start_node_id'] ?? null;
|
||||
|
||||
if (empty($processId)) {
|
||||
// Create new process
|
||||
$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, $subject_scope];
|
||||
$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, subject_scope 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, subject_scope) VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?)';
|
||||
$params = [$name, $db_code, $definition_json, $start_node, $is_active, $new_version, $processId, $old['sort_order'], $subject_scope];
|
||||
$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]);
|
||||
} else {
|
||||
$_SESSION['success_message'] = $message;
|
||||
header('Location: process_definitions.php');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
} catch (WorkflowRuleFailedException $e) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
74
_sidebar.php
Normal file
74
_sidebar.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php $current_page = basename($_SERVER['PHP_SELF']); ?>
|
||||
<nav id="sidebar" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
|
||||
<div class="position-sticky pt-3">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= ($current_page == 'index.php' || $current_page == '') ? 'active' : '' ?>" aria-current="page" href="/">
|
||||
<i class="bi bi-kanban"></i>
|
||||
<span class="nav-link-text"><?= t('menu.process_dashboard', 'Pulpit procesów') ?></span>
|
||||
</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 class="nav-item">
|
||||
<a class="nav-link <?= ($current_page == 'calendar.php') ? 'active' : '' ?>" href="calendar.php">
|
||||
<i class="bi bi-calendar-event"></i>
|
||||
<span class="nav-link-text"><?= t('menu.calendar', 'Kalendarz') ?></span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="#settings-submenu" data-bs-toggle="collapse" aria-expanded="false">
|
||||
<i class="bi bi-gear"></i>
|
||||
<span class="nav-link-text"><?= t('menu.settings', 'Ustawienia') ?></span>
|
||||
</a>
|
||||
<ul class="nav flex-column collapse" id="settings-submenu" data-bs-parent="#sidebar">
|
||||
<li class="nav-item submenu-item">
|
||||
<a class="nav-link <?= ($current_page == 'event_types.php') ? 'active' : '' ?>" href="event_types.php">
|
||||
<i class="bi bi-tags"></i>
|
||||
<span class="nav-link-text"><?= t('menu.event_types', 'Typy zdarzeń') ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item submenu-item">
|
||||
<a class="nav-link <?= ($current_page == 'bni_groups.php') ? 'active' : '' ?>" href="bni_groups.php">
|
||||
<i class="bi bi-people"></i>
|
||||
<span class="nav-link-text"><?= t('menu.bni_groups', 'Grupy BNI') ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item submenu-item">
|
||||
<a class="nav-link <?= ($current_page == 'functions.php') ? 'active' : '' ?>" href="functions.php">
|
||||
<i class="bi bi-person-rolodex"></i>
|
||||
<span class="nav-link-text"><?= t('menu.functions', 'Funkcje') ?></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= ($current_page == 'process_definitions.php') ? 'active' : '' ?>" href="process_definitions.php">
|
||||
<i class="bi bi-diagram-3"></i>
|
||||
<span class="nav-link-text"><?= t('menu.process_definitions', 'Definicje procesów') ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
34
_update_bni_group.php
Normal file
34
_update_bni_group.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (isset($_POST['edit'])) {
|
||||
$id = $_POST['id'];
|
||||
$name = $_POST['name'];
|
||||
$city = $_POST['city'] ?? null;
|
||||
$active = isset($_POST['active']) ? 1 : 0;
|
||||
$display_order = $_POST['display_order'] ?? 0;
|
||||
|
||||
if (empty($name) || empty($id)) {
|
||||
$_SESSION['error_message'] = 'Name and ID are required.';
|
||||
header('Location: bni_groups.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE bni_groups SET name = :name, city = :city, active = :active, display_order = :display_order WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':name', $name);
|
||||
$stmt->bindParam(':city', $city);
|
||||
$stmt->bindParam(':active', $active, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':display_order', $display_order, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$_SESSION['success_message'] = 'BNI Group updated successfully!';
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = 'Error updating BNI group: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: bni_groups.php');
|
||||
exit;
|
||||
67
_update_bni_group_order.php
Normal file
67
_update_bni_group_order.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['order']) && is_array($_POST['order'])) {
|
||||
$ordered_ids = $_POST['order'];
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($ordered_ids as $index => $id) {
|
||||
$sql = "UPDATE bni_groups SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$index + 1, $id]);
|
||||
}
|
||||
$pdo->commit();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => 'Order updated successfully.']);
|
||||
exit();
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
header('Content-Type: application/json');
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'Error updating display order: ' . $e->getMessage()]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for old form submission, though it's being deprecated
|
||||
if (isset($_POST['ids']) && isset($_POST['display_order'])) {
|
||||
$ids = $_POST['ids'];
|
||||
$display_orders = $_POST['display_order'];
|
||||
|
||||
if (count($ids) !== count($display_orders)) {
|
||||
$_SESSION['error_message'] = "Something went wrong. Please try again.";
|
||||
header("Location: bni_groups.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$sql = "UPDATE bni_groups SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$display_orders[$i], $ids[$i]]);
|
||||
}
|
||||
$pdo->commit();
|
||||
$_SESSION['success_message'] = "Display order updated successfully.";
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
$_SESSION['error_message'] = "Error updating display order: " . $e->getMessage();
|
||||
}
|
||||
|
||||
header("Location: bni_groups.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid request.']);
|
||||
81
_update_calendar_event.php
Normal file
81
_update_calendar_event.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'lib/WorkflowExceptions.php';
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
throw new WorkflowNotAllowedException('Authentication required.');
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$event_id = $_POST['event_id'] ?? null;
|
||||
$title = $_POST['title'] ?? '';
|
||||
$description = $_POST['description'] ?? '';
|
||||
$start_datetime = $_POST['start_datetime'] ?? '';
|
||||
$end_datetime = $_POST['end_datetime'] ?? '';
|
||||
$event_type_id = $_POST['event_type_id'] ?? null;
|
||||
$update_scope = $_POST['update_scope'] ?? 'one';
|
||||
$group_ids = $_POST['group_ids'] ?? [];
|
||||
|
||||
if (empty($event_id) || empty($title) || empty($event_type_id) || !is_array($group_ids)) {
|
||||
throw new WorkflowRuleFailedException('Empty fields');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$event_ids_to_update = [];
|
||||
|
||||
if ($update_scope === 'all') {
|
||||
// Find the parent event id
|
||||
$stmt = $pdo->prepare("SELECT parent_event_id, recurrence FROM calendar_events WHERE id = ?");
|
||||
$stmt->execute([$event_id]);
|
||||
$event = $stmt->fetch();
|
||||
|
||||
$parent_event_id = $event['parent_event_id'] ?? $event_id;
|
||||
|
||||
// Get all event ids in the series
|
||||
$stmt = $pdo->prepare("SELECT id FROM calendar_events WHERE id = ? OR parent_event_id = ?");
|
||||
$stmt->execute([$parent_event_id, $parent_event_id]);
|
||||
$event_ids_to_update = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
} else {
|
||||
$event_ids_to_update[] = $event_id;
|
||||
}
|
||||
|
||||
// Prepare statements
|
||||
$stmt_update_event = $pdo->prepare("UPDATE calendar_events SET title = ?, description = ?, event_type_id = ? WHERE id = ?");
|
||||
if($update_scope === 'one'){
|
||||
$stmt_update_event = $pdo->prepare("UPDATE calendar_events SET title = ?, description = ?, start_datetime = ?, end_datetime = ?, event_type_id = ? WHERE id = ?");
|
||||
}
|
||||
|
||||
$stmt_delete_groups = $pdo->prepare("DELETE FROM calendar_event_groups WHERE calendar_event_id = ?");
|
||||
$stmt_add_groups = $pdo->prepare("INSERT INTO calendar_event_groups (calendar_event_id, bni_group_id) VALUES (?, ?)");
|
||||
|
||||
foreach ($event_ids_to_update as $id) {
|
||||
// Update event details
|
||||
if($update_scope === 'one'){
|
||||
$stmt_update_event->execute([$title, $description, $start_datetime, $end_datetime, $event_type_id, $id]);
|
||||
} else {
|
||||
$stmt_update_event->execute([$title, $description, $event_type_id, $id]);
|
||||
}
|
||||
|
||||
// Update group associations
|
||||
$stmt_delete_groups->execute([$id]);
|
||||
foreach ($group_ids as $group_id) {
|
||||
$stmt_add_groups->execute([$id, $group_id]);
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
header("Location: calendar.php");
|
||||
exit();
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
error_log("Error updating event: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
19
_update_event_type.php
Normal file
19
_update_event_type.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_POST['edit'])) {
|
||||
$id = $_POST['id'];
|
||||
$name = $_POST['name'];
|
||||
$color = $_POST['color'];
|
||||
$display_order = $_POST['display_order'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE event_types SET name = ?, color = ?, display_order = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $color, $display_order, $id]);
|
||||
|
||||
session_start();
|
||||
$_SESSION['success_message'] = 'Event type updated successfully.';
|
||||
header('Location: event_types.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
67
_update_event_type_order.php
Normal file
67
_update_event_type_order.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['order']) && is_array($_POST['order'])) {
|
||||
$ordered_ids = $_POST['order'];
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($ordered_ids as $index => $id) {
|
||||
$sql = "UPDATE event_types SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$index + 1, $id]);
|
||||
}
|
||||
$pdo->commit();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => 'Order updated successfully.']);
|
||||
exit();
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
header('Content-Type: application/json');
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'Error updating display order: ' . $e->getMessage()]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for old form submission, though it's being deprecated
|
||||
if (isset($_POST['ids']) && isset($_POST['display_order'])) {
|
||||
$ids = $_POST['ids'];
|
||||
$display_orders = $_POST['display_order'];
|
||||
|
||||
if (count($ids) !== count($display_orders)) {
|
||||
$_SESSION['error_message'] = "Something went wrong. Please try again.";
|
||||
header("Location: event_types.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$sql = "UPDATE event_types SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$display_orders[$i], $ids[$i]]);
|
||||
}
|
||||
$pdo->commit();
|
||||
$_SESSION['success_message'] = "Display order updated successfully.";
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
$_SESSION['error_message'] = "Error updating display order: " . $e->getMessage();
|
||||
}
|
||||
|
||||
header("Location: event_types.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid request.']);
|
||||
30
_update_function.php
Normal file
30
_update_function.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_POST['id'], $_POST['name'], $_POST['bni_group_id'])) {
|
||||
$id = $_POST['id'];
|
||||
$name = trim($_POST['name']);
|
||||
$bni_group_id = trim($_POST['bni_group_id']);
|
||||
|
||||
if (!empty($name) && !empty($bni_group_id)) {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE functions SET name = :name, bni_group_id = :bni_group_id WHERE id = :id");
|
||||
$stmt->execute(['name' => $name, 'bni_group_id' => $bni_group_id, 'id' => $id]);
|
||||
$_SESSION['success_message'] = "Function updated successfully.";
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = "Error updating function: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Name and group are required.";
|
||||
}
|
||||
|
||||
header('Location: functions.php');
|
||||
exit();
|
||||
67
_update_function_order.php
Normal file
67
_update_function_order.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['order']) && is_array($_POST['order'])) {
|
||||
$ordered_ids = $_POST['order'];
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($ordered_ids as $index => $id) {
|
||||
$sql = "UPDATE functions SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$index + 1, $id]);
|
||||
}
|
||||
$pdo->commit();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => 'Order updated successfully.']);
|
||||
exit();
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
header('Content-Type: application/json');
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'Error updating display order: ' . $e->getMessage()]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for old form submission, though it's being deprecated
|
||||
if (isset($_POST['ids']) && isset($_POST['display_order'])) {
|
||||
$ids = $_POST['ids'];
|
||||
$display_orders = $_POST['display_order'];
|
||||
|
||||
if (count($ids) !== count($display_orders)) {
|
||||
$_SESSION['error_message'] = "Something went wrong. Please try again.";
|
||||
header("Location: functions.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$sql = "UPDATE functions SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$display_orders[$i], $ids[$i]]);
|
||||
}
|
||||
$pdo->commit();
|
||||
$_SESSION['success_message'] = "Display order updated successfully.";
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
$_SESSION['error_message'] = "Error updating display order: " . $e->getMessage();
|
||||
}
|
||||
|
||||
header("Location: functions.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid request.']);
|
||||
29
_update_instance_status.php
Normal file
29
_update_instance_status.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
$instanceId = $_POST['instance_id'] ?? null;
|
||||
$status = $_POST['status'] ?? null;
|
||||
$reason = $_POST['reason'] ?? '';
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (!$instanceId || !$status) {
|
||||
throw new WorkflowRuleFailedException('Missing parameters: instance_id and status are required.');
|
||||
}
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$workflowEngine->applyManualStatus((int)$instanceId, $status, $reason, (int)$userId);
|
||||
|
||||
if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['message' => 'Status updated successfully.']);
|
||||
} else {
|
||||
header('Location: index.php');
|
||||
}
|
||||
exit;
|
||||
47
_update_meeting_attendance.php
Normal file
47
_update_meeting_attendance.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
register_error_handler();
|
||||
|
||||
require_once 'db/config.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
session_start();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
throw new WorkflowException('You must be logged in to perform this action.', 401);
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
throw new WorkflowException('Invalid request method.', 405);
|
||||
}
|
||||
|
||||
$personId = $_POST['person_id'] ?? null;
|
||||
$bniGroupId = $_POST['bni_group_id'] ?? $_POST['group_id'] ?? null;
|
||||
$meetingDate = $_POST['meeting_date'] ?? null;
|
||||
$status = $_POST['attendance_status'] ?? null;
|
||||
$guestSurvey = $_POST['guest_survey'] ?? null;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (!$personId || !$bniGroupId || !$meetingDate || !$status) {
|
||||
$missing_params = [];
|
||||
if (!$personId) $missing_params[] = 'person_id';
|
||||
if (!$bniGroupId) $missing_params[] = 'bni_group_id';
|
||||
if (!$meetingDate) $missing_params[] = 'meeting_date';
|
||||
if (!$status) $missing_params[] = 'status';
|
||||
throw new WorkflowException('Missing required parameters: ' . implode(', ', $missing_params), 400);
|
||||
}
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
|
||||
$meetingId = $workflowEngine->getOrCreateMeeting((int)$bniGroupId, $meetingDate);
|
||||
$workflowEngine->updateMeetingAttendance($meetingId, (int)$personId, (int)$bniGroupId, $status, (int)$userId, $guestSurvey);
|
||||
|
||||
$response = [
|
||||
'success' => true,
|
||||
'message' => 'Attendance updated successfully.'
|
||||
];
|
||||
|
||||
echo json_encode($response);
|
||||
|
||||
125
_update_person.php
Normal file
125
_update_person.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
session_start();
|
||||
require_once 'lib/i18n.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$personId = $_POST['id'];
|
||||
$first_name = $_POST['first_name'];
|
||||
$last_name = $_POST['last_name'];
|
||||
$email = $_POST['email'];
|
||||
$company_name = $_POST['company_name'];
|
||||
$phone = $_POST['phone'];
|
||||
$role = $_POST['role'] ?? 'guest';
|
||||
$functions = isset($_POST['functions']) ? $_POST['functions'] : [];
|
||||
$password = $_POST['password'];
|
||||
$bni_group_id = isset($_POST['bni_group_id']) && !empty($_POST['bni_group_id']) ? $_POST['bni_group_id'] : null;
|
||||
|
||||
// New fields
|
||||
$nip = $_POST['nip'] ?? null;
|
||||
$industry = $_POST['industry'] ?? null;
|
||||
$company_size_revenue = $_POST['company_size_revenue'] ?? null;
|
||||
$business_description = $_POST['business_description'] ?? null;
|
||||
|
||||
if (empty($first_name) || empty($last_name) || empty($email)) {
|
||||
http_response_code(422);
|
||||
echo json_encode(['error' => ['message' => t('error.missing_update_fields', 'Imię, nazwisko i email są wymagane.')], 'correlation_id' => uniqid()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Only members can be in a group
|
||||
if ($role !== 'member') {
|
||||
$bni_group_id = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Handle file uploads
|
||||
$upload_dir = 'uploads/people/' . $personId . '/';
|
||||
if (!is_dir($upload_dir)) {
|
||||
mkdir($upload_dir, 0777, true);
|
||||
}
|
||||
|
||||
$file_fields = [
|
||||
'company_logo' => 'company_logo_path',
|
||||
'person_photo' => 'person_photo_path',
|
||||
'gains_sheet' => 'gains_sheet_path',
|
||||
'top_wanted_contacts' => 'top_wanted_contacts_path',
|
||||
'top_owned_contacts' => 'top_owned_contacts_path'
|
||||
];
|
||||
|
||||
$file_paths = [];
|
||||
|
||||
foreach ($file_fields as $form_field_name => $db_column_name) {
|
||||
if (isset($_FILES[$form_field_name]) && $_FILES[$form_field_name]['error'] == UPLOAD_ERR_OK) {
|
||||
$tmp_name = $_FILES[$form_field_name]['tmp_name'];
|
||||
$original_name = basename($_FILES[$form_field_name]['name']);
|
||||
$file_ext = pathinfo($original_name, PATHINFO_EXTENSION);
|
||||
$new_filename = uniqid($form_field_name . '_', true) . '.' . $file_ext;
|
||||
$destination = $upload_dir . $new_filename;
|
||||
|
||||
if (move_uploaded_file($tmp_name, $destination)) {
|
||||
$file_paths[$db_column_name] = $destination;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare SQL for updating person details
|
||||
$sql_parts = [
|
||||
'first_name = ?', 'last_name = ?', 'email = ?', 'company_name = ?', 'phone = ?',
|
||||
'role = ?', 'bni_group_id = ?', 'nip = ?', 'industry = ?', 'company_size_revenue = ?',
|
||||
'business_description = ?'
|
||||
];
|
||||
$params = [
|
||||
$first_name, $last_name, $email, $company_name, $phone, $role, $bni_group_id,
|
||||
$nip, $industry, $company_size_revenue, $business_description
|
||||
];
|
||||
|
||||
if (!empty($password)) {
|
||||
$sql_parts[] = 'password = ?';
|
||||
$params[] = password_hash($password, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
foreach ($file_paths as $column => $path) {
|
||||
$sql_parts[] = "$column = ?";
|
||||
$params[] = $path;
|
||||
}
|
||||
|
||||
$sql = "UPDATE people SET " . implode(', ', $sql_parts) . " WHERE id = ?";
|
||||
$params[] = $personId;
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
// Update functions
|
||||
$stmt = $pdo->prepare("DELETE FROM user_functions WHERE user_id = ?");
|
||||
$stmt->execute([$personId]);
|
||||
|
||||
if (!empty($functions)) {
|
||||
$sql = "INSERT INTO user_functions (user_id, function_id) VALUES (?, ?)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
foreach ($functions as $functionId) {
|
||||
$stmt->execute([$personId, $functionId]);
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
$_SESSION['success_message'] = 'Osoba zaktualizowana pomyślnie.';
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
error_log('Update failed: ' . $e->getMessage());
|
||||
$_SESSION['error_message'] = "Błąd podczas aktualizacji osoby: " . $e->getMessage();
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
error_log('File upload or other error: ' . $e->getMessage());
|
||||
$_SESSION['error_message'] = "Błąd: " . $e->getMessage();
|
||||
}
|
||||
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
}
|
||||
36
_update_training_checklist_status.php
Normal file
36
_update_training_checklist_status.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
require_once 'lib/ErrorHandler.php';
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
session_start();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
throw new WorkflowNotAllowedException('Unauthorized');
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
throw new WorkflowNotAllowedException('Method Not Allowed');
|
||||
}
|
||||
|
||||
$inputJSON = file_get_contents('php://input');
|
||||
$input = json_decode($inputJSON, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new WorkflowRuleFailedException('Invalid JSON');
|
||||
}
|
||||
|
||||
$instanceId = $input['instance_id'] ?? null;
|
||||
$taskCode = $input['task_code'] ?? null;
|
||||
$isChecked = $input['is_checked'] ?? null;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (!$instanceId || !$taskCode || $isChecked === null) {
|
||||
throw new WorkflowRuleFailedException('Missing required parameters: instance_id, task_code, is_checked');
|
||||
}
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$result = $workflowEngine->updateChecklistStatus((int)$instanceId, $taskCode, (bool)$isChecked, (int)$userId);
|
||||
echo json_encode($result);
|
||||
exit;
|
||||
52
apply_patch.py
Normal file
52
apply_patch.py
Normal file
@ -0,0 +1,52 @@
|
||||
import re
|
||||
|
||||
with open('_get_instance_details.php', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
pattern = re.compile(r"<\?php foreach \(\dcurrentNode\['ui_hints'\]\['form_schema'\].*?endforeach;\s*\?>", re.DOTALL)
|
||||
|
||||
replacement_text = r"""<?php foreach ($currentNode['ui_hints']['form_schema'] as $field):
|
||||
$fieldName = $field['name'];
|
||||
$currentValue = $instanceData[$fieldName] ?? null;
|
||||
?>
|
||||
<?php if ($field['type'] === 'checkbox'): ?>
|
||||
<div class="form-check mb-3">
|
||||
<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; ?>"""
|
||||
|
||||
def replacer(match):
|
||||
return replacement_text
|
||||
|
||||
matches = pattern.search(content)
|
||||
if matches:
|
||||
content = pattern.sub(replacer, content, count=1)
|
||||
with open('_get_instance_details.php', 'w') as f:
|
||||
f.write(content)
|
||||
print("Patched successfully!")
|
||||
else:
|
||||
print("Pattern not found!")
|
||||
169
assets/css/custom.css
Normal file
169
assets/css/custom.css
Normal file
@ -0,0 +1,169 @@
|
||||
body {
|
||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
padding: 48px 0 0;
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
|
||||
width: 250px;
|
||||
transition: width 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#main-content {
|
||||
margin-left: 250px;
|
||||
transition: margin-left 0.3s ease-in-out;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #sidebar {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #main-content {
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #sidebar .nav-link-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #sidebar .nav-link i {
|
||||
font-size: 1.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
body.sidebar-collapsed #sidebar .nav-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-link i {
|
||||
margin-right: 10px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: #0d6efd;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
padding-top: .75rem;
|
||||
padding-bottom: .75rem;
|
||||
font-size: 1rem;
|
||||
/* background-color: rgba(0, 0, 0, .25); */
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.navbar .form-control {
|
||||
padding: .75rem 1rem;
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* Calendar styles */
|
||||
.calendar {
|
||||
table-layout: fixed;
|
||||
}
|
||||
.calendar td {
|
||||
height: 120px;
|
||||
vertical-align: top;
|
||||
border: 1px solid #ddd;
|
||||
padding: 4px;
|
||||
}
|
||||
.calendar .day-number {
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.calendar .not-month {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.events {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.event {
|
||||
font-size: 0.75rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.submenu-item .nav-link {
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
|
||||
.modal-fullscreen-xl {
|
||||
width: 95%;
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.person-cell {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.person-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.person-details {
|
||||
font-size: 0.75rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.person-details .person-group {
|
||||
font-weight: bold;
|
||||
color: #198754;
|
||||
}
|
||||
|
||||
.person-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.status-dots {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.navbar-logo {
|
||||
height: 30px;
|
||||
width: auto;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
#sidebar-toggler i {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* Make sidebar link text visible */
|
||||
.nav-link-text {
|
||||
display: inline;
|
||||
}
|
||||
253
assets/js/main.js
Normal file
253
assets/js/main.js
Normal file
@ -0,0 +1,253 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const sidebarToggler = document.getElementById('sidebar-toggler');
|
||||
|
||||
if (sidebarToggler) {
|
||||
sidebarToggler.addEventListener('click', function () {
|
||||
const isCollapsed = document.body.classList.toggle('sidebar-collapsed');
|
||||
localStorage.setItem('sidebarCollapsed', isCollapsed);
|
||||
sidebarToggler.setAttribute('aria-expanded', !isCollapsed);
|
||||
});
|
||||
|
||||
// Set initial aria-expanded state
|
||||
const isInitiallyCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
|
||||
sidebarToggler.setAttribute('aria-expanded', !isInitiallyCollapsed);
|
||||
}
|
||||
|
||||
// --- The rest of your original main.js --- //
|
||||
|
||||
$(document).ready(function() {
|
||||
// Handler for showing the edit person modal
|
||||
$('#editPersonModal').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget); // Button that triggered the modal
|
||||
var personId = button.data('person-id'); // Extract info from data-* attributes
|
||||
var modal = $(this);
|
||||
|
||||
// Clear previous data
|
||||
modal.find('form').trigger('reset');
|
||||
modal.find('#editPersonId').val('');
|
||||
modal.find('#editRoles').empty();
|
||||
modal.find('#followUpSummaryContainer').empty(); // Clear summary container
|
||||
// Clear file paths
|
||||
modal.find('#editCompanyLogoPath, #editPersonPhotoPath, #editGainsSheetPath, #editTopWantedPath, #editTopOwnedPath').text('');
|
||||
|
||||
if (personId) {
|
||||
// AJAX request to get person details
|
||||
$.ajax({
|
||||
url: '_get_person_details.php',
|
||||
type: 'GET',
|
||||
data: { id: personId },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.error) {
|
||||
alert('Error fetching person details: ' + response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
var person = response.person;
|
||||
var all_functions = response.all_functions;
|
||||
var person_functions = response.person_functions;
|
||||
var followUpSummary = response.follow_up_summary;
|
||||
|
||||
if (!person) {
|
||||
alert('Could not find person data.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate the Follow-up Summary
|
||||
var summaryContainer = modal.find('#followUpSummaryContainer');
|
||||
if (followUpSummary) {
|
||||
let summaryHtml = '<h5>Follow-up Process Summary</h5>';
|
||||
summaryHtml += '<dl class="row">';
|
||||
|
||||
if (followUpSummary.last_call_outcome) {
|
||||
summaryHtml += `<dt class="col-sm-4">Last Call Outcome</dt><dd class="col-sm-8">${followUpSummary.last_call_outcome.replace(/_/g, ' ')}</dd>`;
|
||||
}
|
||||
if (followUpSummary.last_call_date) {
|
||||
summaryHtml += `<dt class="col-sm-4">Last Call Date</dt><dd class="col-sm-8">${new Date(followUpSummary.last_call_date).toLocaleString()}</dd>`;
|
||||
}
|
||||
if (followUpSummary.next_contact_date) {
|
||||
summaryHtml += `<dt class="col-sm-4">Next Contact Date</dt><dd class="col-sm-8">${new Date(followUpSummary.next_contact_date).toLocaleString()}</dd>`;
|
||||
}
|
||||
if (followUpSummary.final_outcome) {
|
||||
summaryHtml += `<dt class="col-sm-4">Final Status</dt><dd class="col-sm-8">${followUpSummary.final_outcome} (${followUpSummary.reason || 'N/A'})</dd>`;
|
||||
}
|
||||
|
||||
summaryHtml += '</dl>';
|
||||
summaryContainer.html(summaryHtml);
|
||||
} else {
|
||||
summaryContainer.html('<p class="text-muted">No Follow-up process data found for this person.</p>');
|
||||
}
|
||||
|
||||
// Populate the form fields
|
||||
modal.find('#editPersonId').val(person.id);
|
||||
modal.find('#editFirstName').val(person.first_name);
|
||||
modal.find('#editLastName').val(person.last_name);
|
||||
modal.find('#editPhone').val(person.phone);
|
||||
modal.find('#editEmail').val(person.email);
|
||||
modal.find('#editRole').val(person.role);
|
||||
modal.find('#editBniGroup').val(person.bni_group_id);
|
||||
modal.find('#editCompanyName').val(person.company_name);
|
||||
modal.find('#editNip').val(person.nip);
|
||||
modal.find('#editIndustry').val(person.industry);
|
||||
modal.find('#editCompanySize').val(person.company_size_revenue);
|
||||
modal.find('#editBusinessDescription').val(person.business_description);
|
||||
|
||||
// Populate file paths
|
||||
if (person.company_logo_path) {
|
||||
modal.find('#editCompanyLogoPath').text('Current file: ' + person.company_logo_path.split('/').pop());
|
||||
}
|
||||
if (person.person_photo_path) {
|
||||
modal.find('#editPersonPhotoPath').text('Current file: ' + person.person_photo_path.split('/').pop());
|
||||
}
|
||||
if (person.gains_sheet_path) {
|
||||
modal.find('#editGainsSheetPath').text('Current file: ' + person.gains_sheet_path.split('/').pop());
|
||||
}
|
||||
if (person.top_wanted_contacts_path) {
|
||||
modal.find('#editTopWantedPath').text('Current file: ' + person.top_wanted_contacts_path.split('/').pop());
|
||||
}
|
||||
if (person.top_owned_contacts_path) {
|
||||
modal.find('#editTopOwnedPath').text('Current file: ' + person.top_owned_contacts_path.split('/').pop());
|
||||
}
|
||||
|
||||
// Populate functions/roles dropdown and select assigned ones
|
||||
var rolesSelect = modal.find('#editRoles');
|
||||
rolesSelect.empty(); // Clear existing options
|
||||
|
||||
if (all_functions && all_functions.length > 0) {
|
||||
const groupedFunctions = all_functions.reduce((acc, func) => {
|
||||
const groupName = func.group_name || 'General';
|
||||
if (!acc[groupName]) {
|
||||
acc[groupName] = [];
|
||||
}
|
||||
acc[groupName].push(func);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
for (const groupName in groupedFunctions) {
|
||||
const optgroup = $('<optgroup>').attr('label', groupName);
|
||||
groupedFunctions[groupName].forEach(function(func) {
|
||||
var option = $('<option></option>').val(func.id).text(func.name);
|
||||
if (person_functions && person_functions.includes(String(func.id))) {
|
||||
option.prop('selected', true);
|
||||
}
|
||||
optgroup.append(option);
|
||||
});
|
||||
rolesSelect.append(optgroup);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger change to show/hide conditional fields
|
||||
modal.find('#editRole').trigger('change');
|
||||
|
||||
// Also set up the delete button
|
||||
$('#deleteUserBtn').data('person-id', person.id);
|
||||
$('#personNameToDelete').text(person.firstName + ' ' + person.lastName);
|
||||
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
alert('An error occurred while fetching person data. Please try again.');
|
||||
console.error("AJAX Error:", status, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Show/hide group selection based on role for both Edit and Create modals
|
||||
$(document).on('change', '#editRole, #createRole', function() {
|
||||
const role = $(this).val();
|
||||
const isMember = role === 'member';
|
||||
|
||||
// Find the correct context (modal) for the elements
|
||||
const modal = $(this).closest('.modal-content');
|
||||
|
||||
modal.find('.member-only-fields').toggle(isMember);
|
||||
modal.find('#edit-group-selection-div, #create-group-selection-div').toggle(isMember);
|
||||
});
|
||||
|
||||
// Handle Delete Person confirmation
|
||||
$('#confirmDeleteBtn').on('click', function() {
|
||||
var personId = $('#deleteUserBtn').data('person-id');
|
||||
if (personId) {
|
||||
// Use a form submission to perform the delete
|
||||
var form = $('<form></form>');
|
||||
form.attr("method", "post");
|
||||
form.attr("action", "_delete_person.php");
|
||||
|
||||
var field = $('<input></input>');
|
||||
field.attr("type", "hidden");
|
||||
field.attr("name", "id");
|
||||
field.attr("value", personId);
|
||||
form.append(field);
|
||||
|
||||
$(document.body).append(form);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
|
||||
// Set initial state for create form
|
||||
$('#createPersonModal').on('show.bs.modal', function () {
|
||||
$('#createRole').trigger('change');
|
||||
});
|
||||
|
||||
function handleFormSubmit(form, errorContainer, successCallback) {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(form);
|
||||
const errorDiv = $(errorContainer).hide();
|
||||
|
||||
fetch(form.action, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
// Clone the response so we can read it twice (once as JSON, once as text if needed)
|
||||
const responseClone = response.clone();
|
||||
return response.json()
|
||||
.then(data => ({ status: response.status, ok: response.ok, body: data }))
|
||||
.catch(() => responseClone.text().then(text => ({ status: response.status, ok: response.ok, body: text, isText: true })));
|
||||
})
|
||||
.then(res => {
|
||||
const { status, ok, body, isText } = res;
|
||||
|
||||
if (!ok) {
|
||||
if (isText) {
|
||||
throw new Error(`Server Error: ${status}. Response: ${body}`);
|
||||
}
|
||||
throw new Error(body.error?.message || `An unknown server error occurred (Status: ${status})`);
|
||||
}
|
||||
|
||||
if (isText) {
|
||||
console.error("Received non-JSON response:", body);
|
||||
throw new Error("The server sent an invalid response that could not be parsed. See console for details.");
|
||||
}
|
||||
|
||||
if (body.success) {
|
||||
if (successCallback) {
|
||||
successCallback(body);
|
||||
} else {
|
||||
// Default success behavior: close modal and reload
|
||||
$(form).closest('.modal').modal('hide');
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
throw new Error(body.error?.message || 'An operation error occurred.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
errorDiv.text(error.message).show();
|
||||
});
|
||||
}
|
||||
|
||||
$('#createPersonForm').on('submit', function(event) {
|
||||
handleFormSubmit(this, '#createPersonError');
|
||||
});
|
||||
|
||||
$('#editPersonForm').on('submit', function(event) {
|
||||
handleFormSubmit(this, '#editPersonError', function(data) {
|
||||
// close modal and reload page
|
||||
$('#editPersonModal').modal('hide');
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
159
assets/js/process_definitions.js
Normal file
159
assets/js/process_definitions.js
Normal file
@ -0,0 +1,159 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const createProcessModal = document.getElementById('createProcessModal');
|
||||
const modalTitle = createProcessModal.querySelector('.modal-title');
|
||||
const form = createProcessModal.querySelector('#createProcessForm');
|
||||
const processIdInput = createProcessModal.querySelector('#processId');
|
||||
const processNameInput = createProcessModal.querySelector('#processName');
|
||||
const definitionJsonTextarea = createProcessModal.querySelector('#definitionJson');
|
||||
const eligibilityRulesJsonTextarea = createProcessModal.querySelector('#eligibilityRulesJson');
|
||||
|
||||
let definition = {};
|
||||
|
||||
function render() {
|
||||
const statusesList = document.getElementById('statuses-list');
|
||||
const transitionsList = document.getElementById('transitions-list');
|
||||
const initialStatusSelect = document.getElementById('initialStatus');
|
||||
const fromStatusSelect = document.getElementById('fromStatusSelect');
|
||||
const toStatusSelect = document.getElementById('toStatusSelect');
|
||||
|
||||
statusesList.innerHTML = '';
|
||||
transitionsList.innerHTML = '';
|
||||
initialStatusSelect.innerHTML = '';
|
||||
fromStatusSelect.innerHTML = '';
|
||||
toStatusSelect.innerHTML = '';
|
||||
|
||||
if (definition.nodes) {
|
||||
for (const nodeId in definition.nodes) {
|
||||
const node = definition.nodes[nodeId];
|
||||
const statusItem = document.createElement('div');
|
||||
statusItem.textContent = node.name;
|
||||
statusesList.appendChild(statusItem);
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = node.id;
|
||||
option.textContent = node.name;
|
||||
initialStatusSelect.appendChild(option.cloneNode(true));
|
||||
fromStatusSelect.appendChild(option.cloneNode(true));
|
||||
toStatusSelect.appendChild(option.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.start_node_id) {
|
||||
initialStatusSelect.value = definition.start_node_id;
|
||||
}
|
||||
|
||||
if (definition.transitions) {
|
||||
definition.transitions.forEach(transition => {
|
||||
const transitionItem = document.createElement('div');
|
||||
const fromNode = definition.nodes[transition.from] ? definition.nodes[transition.from].name : 'N/A';
|
||||
const toNode = definition.nodes[transition.to] ? definition.nodes[transition.to].name : 'N/A';
|
||||
let actions = '';
|
||||
if(transition.actions) {
|
||||
actions = ' - Actions: ' + JSON.stringify(transition.actions);
|
||||
}
|
||||
transitionItem.textContent = `${transition.name}: ${fromNode} => ${toNode}${actions}`;
|
||||
transitionsList.appendChild(transitionItem);
|
||||
});
|
||||
}
|
||||
|
||||
if (definition.eligibility_rules) {
|
||||
eligibilityRulesJsonTextarea.value = JSON.stringify(definition.eligibility_rules, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('addStatusBtn').addEventListener('click', function () {
|
||||
const newStatusInput = document.getElementById('newStatusInput');
|
||||
const newStatusName = newStatusInput.value.trim();
|
||||
if (newStatusName) {
|
||||
const newNodeId = (Object.keys(definition.nodes || {}).length + 1).toString();
|
||||
if (!definition.nodes) {
|
||||
definition.nodes = {};
|
||||
}
|
||||
definition.nodes[newNodeId] = { id: newNodeId, name: newStatusName };
|
||||
newStatusInput.value = '';
|
||||
render();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('addTransitionBtn').addEventListener('click', function () {
|
||||
const fromStatus = document.getElementById('fromStatusSelect').value;
|
||||
const toStatus = document.getElementById('toStatusSelect').value;
|
||||
const transitionActionJson = document.getElementById('transitionActionJson').value;
|
||||
|
||||
if (fromStatus && toStatus) {
|
||||
if (!definition.transitions) {
|
||||
definition.transitions = [];
|
||||
}
|
||||
const newTransition = {
|
||||
name: `Transition ${definition.transitions.length + 1}`,
|
||||
from: fromStatus,
|
||||
to: toStatus,
|
||||
};
|
||||
|
||||
if(transitionActionJson) {
|
||||
try {
|
||||
newTransition.actions = JSON.parse(transitionActionJson);
|
||||
} catch(e) {
|
||||
alert('Invalid JSON in transition actions');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
definition.transitions.push(newTransition);
|
||||
document.getElementById('transitionActionJson').value = '';
|
||||
render();
|
||||
}
|
||||
});
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
definition.start_node_id = document.getElementById('initialStatus').value;
|
||||
|
||||
try {
|
||||
const eligibilityRules = eligibilityRulesJsonTextarea.value;
|
||||
if(eligibilityRules) {
|
||||
definition.eligibility_rules = JSON.parse(eligibilityRules);
|
||||
} else {
|
||||
delete definition.eligibility_rules;
|
||||
}
|
||||
} catch(e) {
|
||||
alert('Invalid JSON in eligibility rules');
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
definitionJsonTextarea.value = JSON.stringify(definition, null, 2);
|
||||
});
|
||||
|
||||
createProcessModal.addEventListener('show.bs.modal', function (event) {
|
||||
const button = event.relatedTarget;
|
||||
const isEdit = button.classList.contains('edit-process-btn');
|
||||
|
||||
if (isEdit) {
|
||||
const processId = button.dataset.processId;
|
||||
const processName = button.dataset.processName;
|
||||
const subjectScope = button.dataset.subjectScope || 'person';
|
||||
const processDefinition = button.dataset.processDefinition;
|
||||
|
||||
modalTitle.textContent = 'Edit Process';
|
||||
processIdInput.value = processId;
|
||||
processNameInput.value = processName;
|
||||
if(document.getElementById('subjectScope')) document.getElementById('subjectScope').value = subjectScope;
|
||||
|
||||
try {
|
||||
definition = JSON.parse(processDefinition || '{}');
|
||||
} catch(e) {
|
||||
definition = {};
|
||||
}
|
||||
|
||||
} else {
|
||||
modalTitle.textContent = 'Create Process';
|
||||
processIdInput.value = '';
|
||||
processNameInput.value = '';
|
||||
definition = {};
|
||||
}
|
||||
|
||||
eligibilityRulesJsonTextarea.value = '';
|
||||
render();
|
||||
});
|
||||
});
|
||||
BIN
assets/pasted-20260111-143449-befa41d3.png
Normal file
BIN
assets/pasted-20260111-143449-befa41d3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
BIN
assets/pasted-20260111-144117-aba8ec29.jpg
Normal file
BIN
assets/pasted-20260111-144117-aba8ec29.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
201
bni_groups.php
Normal file
201
bni_groups.php
Normal file
@ -0,0 +1,201 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
include '_header.php';
|
||||
include '_navbar.php';
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT * FROM bni_groups ORDER BY display_order");
|
||||
$bni_groups = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<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">
|
||||
<h1 class="h2 pt-3 pb-2 mb-3 border-bottom"><?php echo t('bni_groups.title', 'BNI Groups'); ?></h1>
|
||||
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
|
||||
<?= $_SESSION['success_message']; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="d-flex justify-content-end mb-3">
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||
<?php echo t('bni_groups.add_new', 'Add New BNI Group'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive mt-4">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30px;"></th>
|
||||
<th><?php echo t('bni_groups.name', 'Name'); ?></th>
|
||||
<th><?php echo t('bni_groups.city', 'City'); ?></th>
|
||||
<th><?php echo t('bni_groups.active', 'Active'); ?></th>
|
||||
<th><?php echo t('bni_groups.actions', 'Actions'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sortable-list">
|
||||
<?php foreach ($bni_groups as $group): ?>
|
||||
<tr data-id="<?= $group['id'] ?>">
|
||||
<td class="handle"><i class="bi bi-grip-vertical"></i></td>
|
||||
<td><?= htmlspecialchars($group['name']) ?></td>
|
||||
<td><?= htmlspecialchars($group['city']) ?></td>
|
||||
<td><?= $group['active'] ? t('bni_groups.yes', 'Yes') : t('bni_groups.no', 'No') ?></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#editModal"
|
||||
data-id="<?= $group['id'] ?>"
|
||||
data-name="<?= htmlspecialchars($group['name']) ?>"
|
||||
data-city="<?= htmlspecialchars($group['city']) ?>"
|
||||
data-active="<?= $group['active'] ?>">
|
||||
<?php echo t('bni_groups.edit', 'Edit'); ?>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#deleteModal" data-id="<?= $group['id'] ?>">
|
||||
<?php echo t('bni_groups.delete', 'Delete'); ?>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Modal -->
|
||||
<div class="modal fade" id="addModal" tabindex="-1" aria-labelledby="addModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addModalLabel"><?php echo t('bni_groups.add_title', 'Add BNI Group'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_add_bni_group.php" method="post">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="addName" class="form-label"><?php echo t('bni_groups.name', 'Name'); ?></label>
|
||||
<input type="text" class="form-control" id="addName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="addCity" class="form-label"><?php echo t('bni_groups.city', 'City'); ?></label>
|
||||
<input type="text" class="form-control" id="addCity" name="city">
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="1" id="addActive" name="active" checked>
|
||||
<label class="form-check-label" for="addActive">
|
||||
<?php echo t('bni_groups.active', 'Active'); ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo t('modal.close', 'Close'); ?></button>
|
||||
<button type="submit" name="add" class="btn btn-primary"><?php echo t('bni_groups.add_button', 'Add'); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel"><?php echo t('bni_groups.edit_title', 'Edit BNI Group'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_update_bni_group.php" method="post">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="editId" name="id">
|
||||
<div class="mb-3">
|
||||
<label for="editName" class="form-label"><?php echo t('bni_groups.name', 'Name'); ?></label>
|
||||
<input type="text" class="form-control" id="editName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editCity" class="form-label"><?php echo t('bni_groups.city', 'City'); ?></label>
|
||||
<input type="text" class="form-control" id="editCity" name="city">
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="1" id="editActive" name="active">
|
||||
<label class="form-check-label" for="editActive">
|
||||
<?php echo t('bni_groups.active', 'Active'); ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo t('modal.close', 'Close'); ?></button>
|
||||
<button type="submit" name="edit" class="btn btn-primary"><?php echo t('modal.save_changes', 'Save changes'); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteModalLabel"><?php echo t('bni_groups.delete_title', 'Delete BNI Group'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php echo t('bni_groups.delete_confirm', 'Are you sure you want to delete this BNI group?'); ?>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo t('modal.cancel', 'Cancel'); ?></button>
|
||||
<a href="#" id="deleteLink" class="btn btn-danger"><?php echo t('bni_groups.delete', 'Delete'); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<?php include '_footer.php'; ?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var editModal = document.getElementById('editModal');
|
||||
editModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var name = button.getAttribute('data-name');
|
||||
var city = button.getAttribute('data-city');
|
||||
var active = button.getAttribute('data-active');
|
||||
|
||||
var idInput = editModal.querySelector('#editId');
|
||||
var nameInput = editModal.querySelector('#editName');
|
||||
var cityInput = editModal.querySelector('#editCity');
|
||||
var activeInput = editModal.querySelector('#editActive');
|
||||
|
||||
idInput.value = id;
|
||||
nameInput.value = name;
|
||||
cityInput.value = city;
|
||||
activeInput.checked = (active == 1);
|
||||
});
|
||||
|
||||
var deleteModal = document.getElementById('deleteModal');
|
||||
deleteModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var deleteLink = deleteModal.querySelector('#deleteLink');
|
||||
deleteLink.href = '_delete_bni_group.php?id=' + id;
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$("#sortable-list").sortable({
|
||||
handle: ".handle",
|
||||
update: function(event, ui) {
|
||||
var order = $(this).sortable('toArray', {attribute: 'data-id'});
|
||||
$.post('_update_bni_group_order.php', { order: order });
|
||||
}
|
||||
}).disableSelection();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
352
calendar.php
Normal file
352
calendar.php
Normal file
@ -0,0 +1,352 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
include '_header.php';
|
||||
|
||||
// Get current month and year
|
||||
$month = isset($_GET['month']) ? (int)$_GET['month'] : date('m');
|
||||
$year = isset($_GET['year']) ? (int)$_GET['year'] : date('Y');
|
||||
|
||||
// Create a DateTime object for the first day of the month
|
||||
$firstDayOfMonth = new DateTime("$year-$month-01");
|
||||
$daysInMonth = $firstDayOfMonth->format('t');
|
||||
$dayOfWeek = $firstDayOfMonth->format('N'); // 1 (for Monday) through 7 (for Sunday)
|
||||
|
||||
// Get events for the current month
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT c.*, t.name as type_name, t.color as type_color, GROUP_CONCAT(g.name SEPARATOR ', ') as group_names FROM calendar_events c LEFT JOIN event_types t ON c.event_type_id = t.id LEFT JOIN calendar_event_groups ceg ON c.id = ceg.calendar_event_id LEFT JOIN bni_groups g ON ceg.bni_group_id = g.id WHERE MONTH(c.start_datetime) = ? AND YEAR(c.start_datetime) = ? GROUP BY c.id ORDER BY c.start_datetime ASC");
|
||||
$stmt->execute([$month, $year]);
|
||||
$events = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$eventsByDay = [];
|
||||
foreach ($events as $event) {
|
||||
$day = (new DateTime($event['start_datetime']))->format('j');
|
||||
if (!isset($eventsByDay[$day])) {
|
||||
$eventsByDay[$day] = [];
|
||||
}
|
||||
$eventsByDay[$day][] = $event;
|
||||
}
|
||||
|
||||
// Get event types for the modal
|
||||
$stmt_types = $pdo->query("SELECT * FROM event_types ORDER BY display_order");
|
||||
$event_types = $stmt_types->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Get BNI groups for the modal
|
||||
$stmt_groups = $pdo->query("SELECT * FROM bni_groups ORDER BY display_order");
|
||||
$bni_groups = $stmt_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
$prevMonth = $month == 1 ? 12 : $month - 1;
|
||||
$prevYear = $month == 1 ? $year - 1 : $year;
|
||||
$nextMonth = $month == 12 ? 1 : $month + 1;
|
||||
$nextYear = $month == 12 ? $year + 1 : $year;
|
||||
|
||||
?>
|
||||
|
||||
|
||||
<?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">
|
||||
|
||||
<h2 class="text-center"><?php echo t('calendar.' . strtolower($firstDayOfMonth->format('F')), $firstDayOfMonth->format('F')) . ' ' . $firstDayOfMonth->format('Y'); ?></h2>
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<a href="?month=<?php echo $prevMonth; ?>&year=<?php echo $prevYear; ?>" class="btn btn-primary">< <?php echo t('calendar.previous', 'Previous'); ?></a>
|
||||
<a href="?month=<?php echo $nextMonth; ?>&year=<?php echo $nextYear; ?>" class="btn btn-primary"><?php echo t('calendar.next', 'Next'); ?> ></a>
|
||||
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#addEventModal">
|
||||
<?php echo t('calendar.add_event', 'Add Event'); ?>
|
||||
</button>
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php echo t('calendar.monday', 'Monday'); ?></th>
|
||||
<th><?php echo t('calendar.tuesday', 'Tuesday'); ?></th>
|
||||
<th><?php echo t('calendar.wednesday', 'Wednesday'); ?></th>
|
||||
<th><?php echo t('calendar.thursday', 'Thursday'); ?></th>
|
||||
<th><?php echo t('calendar.friday', 'Friday'); ?></th>
|
||||
<th><?php echo t('calendar.saturday', 'Saturday'); ?></th>
|
||||
<th><?php echo t('calendar.sunday', 'Sunday'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<?php
|
||||
// Print empty cells
|
||||
for ($i = 1; $i < $dayOfWeek; $i++) {
|
||||
echo "<td></td>";
|
||||
}
|
||||
|
||||
$currentDay = 1;
|
||||
while ($currentDay <= $daysInMonth) {
|
||||
if ($dayOfWeek > 7) {
|
||||
$dayOfWeek = 1;
|
||||
echo "</tr><tr>";
|
||||
}
|
||||
|
||||
echo "<td class=\"calendar-day\" data-date=\"$year-$month-$currentDay\">";
|
||||
echo "<strong>$currentDay</strong>";
|
||||
if (isset($eventsByDay[$currentDay])) {
|
||||
echo "<ul class=\"list-unstyled\">";
|
||||
foreach ($eventsByDay[$currentDay] as $event) {
|
||||
echo '<li class="badge" style="background-color: '.($event['type_color'] ?? '#007bff').'" data-event-id="'.$event['id'].'">' . htmlspecialchars($event['title']);
|
||||
if (!empty($event['group_names'])) {
|
||||
echo '<br><small class="fst-italic">(' . htmlspecialchars($event['group_names']) . ')</small>';
|
||||
}
|
||||
echo "</li>";
|
||||
}
|
||||
echo "</ul>";
|
||||
}
|
||||
echo "</td>";
|
||||
|
||||
$currentDay++;
|
||||
$dayOfWeek++;
|
||||
}
|
||||
|
||||
// Print remaining empty cells
|
||||
while ($dayOfWeek <= 7) {
|
||||
echo "<td></td>";
|
||||
$dayOfWeek++;
|
||||
}
|
||||
?>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Event Details Modal -->
|
||||
<div class="modal fade" id="eventDetailsModal" tabindex="-1" aria-labelledby="eventDetailsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="eventDetailsModalLabel"><?php echo t('calendar.event_details', 'Event Details'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h5 id="event-title"></h5>
|
||||
<p id="event-description"></p>
|
||||
<p><strong><?php echo t('calendar.starts', 'Starts:'); ?></strong> <span id="event-start"></span></p>
|
||||
<p><strong><?php echo t('calendar.ends', 'Ends:'); ?></strong> <span id="event-end"></span></p>
|
||||
<p><strong><?php echo t('calendar.type', 'Type:'); ?></strong> <span id="event-type"></span></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo t('modal.close', 'Close'); ?></button>
|
||||
<button type="button" class="btn btn-primary" id="edit-event-btn"><?php echo t('calendar.edit', 'Edit'); ?></button>
|
||||
<a href="#" id="delete-event-btn" class="btn btn-danger"><?php echo t('calendar.delete', 'Delete'); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="addEventModal" tabindex="-1" aria-labelledby="addEventModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addEventModalLabel"><?php echo t('calendar.add_event_title', 'Add Calendar Event'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="_add_calendar_event.php" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label"><?php echo t('calendar.title', 'Title'); ?></label>
|
||||
<input type="text" class="form-control" id="title" name="title" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label"><?php echo t('form.description', 'Description'); ?></label>
|
||||
<textarea class="form-control" id="description" name="description"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="start_datetime" class="form-label"><?php echo t('calendar.start_time', 'Start Time'); ?></label>
|
||||
<input type="datetime-local" class="form-control" id="start_datetime" name="start_datetime" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="end_datetime" class="form-label"><?php echo t('calendar.end_time', 'End Time'); ?></label>
|
||||
<input type="datetime-local" class="form-control" id="end_datetime" name="end_datetime" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="event_type_id" class="form-label"><?php echo t('form.type', 'Type'); ?></label>
|
||||
<select class="form-select" id="event_type_id" name="event_type_id">
|
||||
<?php foreach ($event_types as $type): ?>
|
||||
<option value="<?= $type['id'] ?>"><?= htmlspecialchars($type['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="group_ids" class="form-label"><?php echo t('calendar.groups', 'Group(s)'); ?></label>
|
||||
<select class="form-select" id="group_ids" name="group_ids[]" multiple required>
|
||||
<?php foreach ($bni_groups as $group): ?>
|
||||
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="recurrence" class="form-label"><?php echo t('calendar.recurrence', 'Recurrence'); ?></label>
|
||||
<select class="form-select" id="recurrence" name="recurrence">
|
||||
<option value=""><?php echo t('calendar.no_repeat', 'Does not repeat'); ?></option>
|
||||
<option value="daily"><?php echo t('calendar.daily', 'Daily'); ?></option>
|
||||
<option value="weekly"><?php echo t('calendar.weekly', 'Weekly'); ?></option>
|
||||
<option value="monthly"><?php echo t('calendar.monthly', 'Monthly'); ?></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3" id="recurrence_end_date_container" style="display: none;">
|
||||
<label for="recurrence_end_date" class="form-label"><?php echo t('calendar.recurrence_end_date', 'Recurrence End Date'); ?></label>
|
||||
<input type="date" class="form-control" id="recurrence_end_date" name="recurrence_end_date">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?php echo t('calendar.save_event', 'Save Event'); ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="editEventModal" tabindex="-1" aria-labelledby="editEventModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editEventModalLabel"><?php echo t('calendar.edit_event_title', 'Edit Calendar Event'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="_update_calendar_event.php" method="post">
|
||||
<input type="hidden" id="edit_event_id" name="event_id">
|
||||
<div class="mb-3">
|
||||
<label for="edit_title" class="form-label"><?php echo t('calendar.title', 'Title'); ?></label>
|
||||
<input type="text" class="form-control" id="edit_title" name="title" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_description" class="form-label"><?php echo t('form.description', 'Description'); ?></label>
|
||||
<textarea class="form-control" id="edit_description" name="description"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_start_datetime" class="form-label"><?php echo t('calendar.start_time', 'Start Time'); ?></label>
|
||||
<input type="datetime-local" class="form-control" id="edit_start_datetime" name="start_datetime" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_end_datetime" class="form-label"><?php echo t('calendar.end_time', 'End Time'); ?></label>
|
||||
<input type="datetime-local" class="form-control" id="edit_end_datetime" name="end_datetime" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_event_type_id" class="form-label"><?php echo t('form.type', 'Type'); ?></label>
|
||||
<select class="form-select" id="edit_event_type_id" name="event_type_id">
|
||||
<?php foreach ($event_types as $type): ?>
|
||||
<option value="<?= $type['id'] ?>"><?= htmlspecialchars($type['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_group_ids" class="form-label"><?php echo t('calendar.groups', 'Group(s)'); ?></label>
|
||||
<select class="form-select" id="edit_group_ids" name="group_ids[]" multiple required>
|
||||
<?php foreach ($bni_groups as $group): ?>
|
||||
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div id="edit-recurrence-options" class="mb-3" style="display: none;">
|
||||
<strong><?php echo t('calendar.recurring_notice', 'This is a recurring event.'); ?></strong><br>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="update_scope" id="update_scope_one" value="one" checked>
|
||||
<label class="form-check-label" for="update_scope_one">
|
||||
<?php echo t('calendar.update_scope_one', 'Update this event only'); ?>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="update_scope" id="update_scope_all" value="all">
|
||||
<label class="form-check-label" for="update_scope_all">
|
||||
<?php echo t('calendar.update_scope_all', 'Update all events in the series'); ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?php echo t('modal.save_changes', 'Save Changes'); ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const addEventModal = new bootstrap.Modal(document.getElementById('addEventModal'));
|
||||
const eventDetailsModal = new bootstrap.Modal(document.getElementById('eventDetailsModal'));
|
||||
const editEventModal = new bootstrap.Modal(document.getElementById('editEventModal'));
|
||||
|
||||
let currentEventData = null;
|
||||
|
||||
document.querySelectorAll('.calendar-day').forEach(day => {
|
||||
day.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('badge')) {
|
||||
const eventId = e.target.dataset.eventId;
|
||||
fetch(`_get_event_details.php?id=${eventId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
currentEventData = data;
|
||||
document.getElementById('event-title').textContent = data.title;
|
||||
document.getElementById('event-description').textContent = data.description;
|
||||
document.getElementById('event-start').textContent = new Date(data.start_datetime).toLocaleString();
|
||||
document.getElementById('event-end').textContent = new Date(data.end_datetime).toLocaleString();
|
||||
document.getElementById('event-type').textContent = data.type_name;
|
||||
document.getElementById('delete-event-btn').href = `_delete_calendar_event.php?id=${eventId}`;
|
||||
eventDetailsModal.show();
|
||||
});
|
||||
} else {
|
||||
const date = this.dataset.date;
|
||||
const now = new Date();
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const currentTime = `${hours}:${minutes}`;
|
||||
|
||||
const startInput = document.getElementById('start_datetime');
|
||||
const month = String(new Date(date).getMonth() + 1).padStart(2, '0');
|
||||
const dayOfMonth = String(new Date(date).getDate()).padStart(2, '0');
|
||||
const year = new Date(date).getFullYear();
|
||||
startInput.value = `${year}-${month}-${dayOfMonth}T${currentTime}`;
|
||||
|
||||
addEventModal.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('edit-event-btn').addEventListener('click', function() {
|
||||
if (currentEventData) {
|
||||
const recurrenceOptions = document.getElementById('edit-recurrence-options');
|
||||
if (currentEventData.parent_event_id !== null || currentEventData.recurrence !== null) {
|
||||
recurrenceOptions.style.display = 'block';
|
||||
document.getElementById('update_scope_all').checked = true;
|
||||
} else {
|
||||
recurrenceOptions.style.display = 'none';
|
||||
}
|
||||
|
||||
document.getElementById('edit_event_id').value = currentEventData.id;
|
||||
document.getElementById('edit_title').value = currentEventData.title;
|
||||
document.getElementById('edit_description').value = currentEventData.description;
|
||||
document.getElementById('edit_start_datetime').value = currentEventData.start_datetime.slice(0, 16);
|
||||
document.getElementById('edit_end_datetime').value = currentEventData.end_datetime.slice(0, 16);
|
||||
document.getElementById('edit_event_type_id').value = currentEventData.event_type_id;
|
||||
|
||||
const groupSelect = document.getElementById('edit_group_ids');
|
||||
const groupIds = currentEventData.group_ids || [];
|
||||
for (const option of groupSelect.options) {
|
||||
option.selected = groupIds.includes(option.value);
|
||||
}
|
||||
|
||||
eventDetailsModal.hide();
|
||||
editEventModal.show();
|
||||
}
|
||||
});
|
||||
|
||||
const recurrenceSelect = document.getElementById('recurrence');
|
||||
const recurrenceEndDateContainer = document.getElementById('recurrence_end_date_container');
|
||||
|
||||
recurrenceSelect.addEventListener('change', function() {
|
||||
if (this.value) {
|
||||
recurrenceEndDateContainer.style.display = 'block';
|
||||
} else {
|
||||
recurrenceEndDateContainer.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include '_footer.php';
|
||||
?>
|
||||
5
cookie.txt
Normal file
5
cookie.txt
Normal file
@ -0,0 +1,5 @@
|
||||
# Netscape HTTP Cookie File
|
||||
# https://curl.se/docs/http-cookies.html
|
||||
# This file was generated by libcurl! Edit at your own risk.
|
||||
|
||||
localhost FALSE / FALSE 0 PHPSESSID nupuqse7tg6gsrulg298ru8sn8
|
||||
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" } ]
|
||||
}
|
||||
]
|
||||
}
|
||||
8
db/migrations/001_add_process_definition_json.php
Normal file
8
db/migrations/001_add_process_definition_json.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
function migrate_001($pdo) {
|
||||
$sql = "ALTER TABLE process_definitions ADD COLUMN definition_json TEXT NULL AFTER name;";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 001 applied: Added definition_json to process_definitions.\n";
|
||||
}
|
||||
|
||||
17
db/migrations/002_create_calendar_events_table.php
Normal file
17
db/migrations/002_create_calendar_events_table.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
function migrate_002($pdo) {
|
||||
$sql = "CREATE TABLE IF NOT EXISTS calendar_events (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
start_datetime DATETIME NOT NULL,
|
||||
end_datetime DATETIME NOT NULL,
|
||||
type VARCHAR(50) NOT NULL, -- 'meeting', 'training'
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 002 applied: Created calendar_events table.\n";
|
||||
}
|
||||
|
||||
|
||||
26
db/migrations/003_create_event_types_table.php
Normal file
26
db/migrations/003_create_event_types_table.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS event_types (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
color VARCHAR(7) NOT NULL DEFAULT '#007bff'
|
||||
);";
|
||||
|
||||
$pdo->exec($sql);
|
||||
|
||||
// Add some default values
|
||||
$sql_insert = "INSERT INTO event_types (name, color) VALUES
|
||||
('Meeting', '#007bff'),
|
||||
('Training', '#ffc107'),
|
||||
('Other', '#28a745');";
|
||||
$pdo->exec($sql_insert);
|
||||
|
||||
|
||||
echo "Migration 003 completed successfully.\n";
|
||||
} catch (PDOException $e) {
|
||||
die("Migration 003 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
34
db/migrations/004_add_event_type_id_to_calendar_events.php
Normal file
34
db/migrations/004_add_event_type_id_to_calendar_events.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// First, add the column without the foreign key constraint
|
||||
$sql_add_column = "ALTER TABLE calendar_events ADD COLUMN event_type_id INT NULL;";
|
||||
$pdo->exec($sql_add_column);
|
||||
|
||||
// Set a default value for existing rows to avoid foreign key constraint errors
|
||||
// Get the ID of the 'Other' event type
|
||||
$stmt = $pdo->query("SELECT id FROM event_types WHERE name = 'Other' LIMIT 1");
|
||||
$other_type = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$default_type_id = $other_type ? $other_type['id'] : 1; // Fallback to 1 if 'Other' not found
|
||||
|
||||
$sql_update_existing = "UPDATE calendar_events SET event_type_id = ? WHERE event_type_id IS NULL;";
|
||||
$stmt_update = $pdo->prepare($sql_update_existing);
|
||||
$stmt_update->execute([$default_type_id]);
|
||||
|
||||
// Now, add the foreign key constraint
|
||||
$sql_add_fk = "ALTER TABLE calendar_events ADD CONSTRAINT fk_event_type FOREIGN KEY (event_type_id) REFERENCES event_types(id) ON DELETE SET NULL;";
|
||||
$pdo->exec($sql_add_fk);
|
||||
|
||||
echo "Migration 004 completed successfully.\n";
|
||||
} catch (PDOException $e) {
|
||||
// Check if the column already exists, which might happen on re-runs
|
||||
if (strpos($e->getMessage(), 'Duplicate column name') !== false) {
|
||||
echo "Migration 004 seems to be already applied (Column exists). Skipping.\n";
|
||||
} else {
|
||||
die("Migration 004 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
18
db/migrations/005_create_bni_groups_table.php
Normal file
18
db/migrations/005_create_bni_groups_table.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "CREATE TABLE IF NOT EXISTS `bni_groups` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`city` VARCHAR(255),
|
||||
`active` BOOLEAN NOT NULL DEFAULT 1,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);";
|
||||
$pdo->exec($sql);
|
||||
echo "Table 'bni_groups' created successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error creating table 'bni_groups': " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
20
db/migrations/006_add_recurrence_to_calendar_events.php
Normal file
20
db/migrations/006_add_recurrence_to_calendar_events.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
$sql = <<<SQL
|
||||
ALTER TABLE `calendar_events`
|
||||
ADD COLUMN `recurrence` VARCHAR(20) DEFAULT NULL,
|
||||
ADD COLUMN `recurrence_end_date` DATE DEFAULT NULL,
|
||||
ADD COLUMN `parent_event_id` INT DEFAULT NULL;
|
||||
SQL;
|
||||
|
||||
$pdo->exec($sql);
|
||||
|
||||
echo "Migration 006 executed successfully: Added recurrence columns to calendar_events table.\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
die("Error executing migration 006: " . $e->getMessage() . "\n");
|
||||
}
|
||||
17
db/migrations/007_remove_type_from_calendar_events.php
Normal file
17
db/migrations/007_remove_type_from_calendar_events.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "ALTER TABLE calendar_events DROP COLUMN type;";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 007 completed successfully: Dropped 'type' column from calendar_events.\n";
|
||||
} catch (PDOException $e) {
|
||||
// Check if the column has already been dropped
|
||||
if (strpos($e->getMessage(), 'column not found') !== false || strpos($e->getMessage(), 'Unknown column') !== false) {
|
||||
echo "Migration 007 seems to be already applied (Column not found). Skipping.\n";
|
||||
} else {
|
||||
die("Migration 007 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
16
db/migrations/008_create_roles_table.php
Normal file
16
db/migrations/008_create_roles_table.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "CREATE TABLE IF NOT EXISTS `roles` (
|
||||
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||
$pdo->exec($sql);
|
||||
echo "Table 'roles' created successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error creating table 'roles': " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
18
db/migrations/009_create_user_roles_table.php
Normal file
18
db/migrations/009_create_user_roles_table.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "CREATE TABLE IF NOT EXISTS `user_roles` (
|
||||
`user_id` INT(11) UNSIGNED NOT NULL,
|
||||
`role_id` INT(11) UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`user_id`, `role_id`),
|
||||
FOREIGN KEY (`user_id`) REFERENCES `people`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||
$pdo->exec($sql);
|
||||
echo "Table 'user_roles' created successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error creating table 'user_roles': " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
12
db/migrations/010_add_display_order_to_event_types.php
Normal file
12
db/migrations/010_add_display_order_to_event_types.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "ALTER TABLE event_types ADD COLUMN display_order INT NOT NULL DEFAULT 0";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 010_add_display_order_to_event_types executed successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error executing migration 010_add_display_order_to_event_types: " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
12
db/migrations/011_add_display_order_to_bni_groups.php
Normal file
12
db/migrations/011_add_display_order_to_bni_groups.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "ALTER TABLE bni_groups ADD COLUMN display_order INT NOT NULL DEFAULT 0";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 011_add_display_order_to_bni_groups executed successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error executing migration 011_add_display_order_to_bni_groups: " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
12
db/migrations/012_add_display_order_to_roles.php
Normal file
12
db/migrations/012_add_display_order_to_roles.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "ALTER TABLE roles ADD COLUMN display_order INT NOT NULL DEFAULT 0";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 012_add_display_order_to_roles executed successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error executing migration 012_add_display_order_to_roles: " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
21
db/migrations/013_rename_roles_to_functions.php
Normal file
21
db/migrations/013_rename_roles_to_functions.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "RENAME TABLE `roles` TO `functions`;";
|
||||
$pdo->exec($sql);
|
||||
echo "Table 'roles' renamed to 'functions' successfully." . PHP_EOL;
|
||||
|
||||
$sql = "RENAME TABLE `user_roles` TO `user_functions`;";
|
||||
$pdo->exec($sql);
|
||||
echo "Table 'user_roles' renamed to 'user_functions' successfully." . PHP_EOL;
|
||||
|
||||
$sql = "ALTER TABLE `user_functions` CHANGE `role_id` `function_id` INT(11) UNSIGNED NOT NULL;";
|
||||
$pdo->exec($sql);
|
||||
echo "Column 'role_id' renamed to 'function_id' in 'user_functions' table successfully." . PHP_EOL;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo "Error renaming tables or columns: " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
21
db/migrations/014_add_role_to_people_table.php
Normal file
21
db/migrations/014_add_role_to_people_table.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
function migrate_014_add_role_to_people_table() {
|
||||
$pdo = db();
|
||||
|
||||
// Check if the column already exists
|
||||
try {
|
||||
$result = $pdo->query("SELECT `role` FROM `people` LIMIT 1");
|
||||
} catch (PDOException $e) {
|
||||
// Column does not exist, so add it
|
||||
$pdo->exec("ALTER TABLE `people` ADD COLUMN `role` VARCHAR(50) NOT NULL DEFAULT 'członek' AFTER `email`");
|
||||
echo "Migration 014: Added 'role' column to 'people' table.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
echo "Migration 014: 'role' column already exists in 'people' table. No changes made.\n";
|
||||
}
|
||||
|
||||
migrate_014_add_role_to_people_table();
|
||||
|
||||
61
db/migrations/015_add_group_id_to_functions.php
Normal file
61
db/migrations/015_add_group_id_to_functions.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// First, check if the column already exists
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM `functions` LIKE 'bni_group_id'");
|
||||
$exists = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// Check if the foreign key constraint already exists
|
||||
$stmt = $pdo->query("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'functions' AND CONSTRAINT_NAME = 'fk_functions_bni_group'");
|
||||
$fk_exists = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($fk_exists) {
|
||||
echo "Migration 015 skipped: Foreign key 'fk_functions_bni_group' already exists." . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$exists) {
|
||||
// Add the bni_group_id column allowing NULLs temporarily
|
||||
$pdo->exec("ALTER TABLE `functions` ADD COLUMN `bni_group_id` INT NULL AFTER `id`;");
|
||||
echo "Column 'bni_group_id' added." . PHP_EOL;
|
||||
}
|
||||
|
||||
// Find a default group to assign to existing functions
|
||||
$stmt = $pdo->query("SELECT id FROM `bni_groups` ORDER BY id LIMIT 1");
|
||||
$default_group = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$default_group_id = $default_group ? $default_group['id'] : null;
|
||||
|
||||
if ($default_group_id) {
|
||||
// Update existing functions to use the default group where bni_group_id is not valid
|
||||
$pdo->exec("UPDATE `functions` SET `bni_group_id` = {$default_group_id} WHERE `bni_group_id` IS NULL OR `bni_group_id` NOT IN (SELECT id FROM bni_groups)");
|
||||
echo "Existing functions updated with a valid group ID." . PHP_EOL;
|
||||
|
||||
// Now that existing rows are updated, alter the column to be NOT NULL
|
||||
$pdo->exec("ALTER TABLE `functions` MODIFY COLUMN `bni_group_id` INT NOT NULL;");
|
||||
echo "Column 'bni_group_id' modified to NOT NULL." . PHP_EOL;
|
||||
|
||||
// Add the foreign key constraint
|
||||
$pdo->exec("ALTER TABLE `functions` ADD CONSTRAINT `fk_functions_bni_group` FOREIGN KEY (`bni_group_id`) REFERENCES `bni_groups`(`id`) ON DELETE CASCADE;");
|
||||
echo "Migration 015 successfully applied." . PHP_EOL;
|
||||
|
||||
} else {
|
||||
// If there are no groups, we can't proceed if there are functions.
|
||||
$stmt = $pdo->query("SELECT COUNT(*) FROM `functions`");
|
||||
$function_count = $stmt->fetchColumn();
|
||||
if ($function_count > 0) {
|
||||
die("Migration 015 failed: Cannot create a required association to a BNI group because no BNI groups exist, but functions that need them do exist." . PHP_EOL);
|
||||
} else {
|
||||
// No functions exist, so we can just add the column and constraint
|
||||
$pdo->exec("ALTER TABLE `functions` MODIFY COLUMN `bni_group_id` INT NOT NULL;");
|
||||
$pdo->exec("ALTER TABLE `functions` ADD CONSTRAINT `fk_functions_bni_group` FOREIGN KEY (`bni_group_id`) REFERENCES `bni_groups`(`id`) ON DELETE CASCADE;");
|
||||
echo "Migration 015 successfully applied (no existing functions to update)." . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Migration 015 failed: " . $e->getMessage() . PHP_EOL);
|
||||
}
|
||||
30
db/migrations/016_create_calendar_event_groups_table.php
Normal file
30
db/migrations/016_create_calendar_event_groups_table.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Check if table calendar_event_groups already exists
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE 'calendar_event_groups'");
|
||||
$table_exists = $stmt->rowCount() > 0;
|
||||
|
||||
if (!$table_exists) {
|
||||
$pdo->exec("
|
||||
CREATE TABLE `calendar_event_groups` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`calendar_event_id` INT NOT NULL,
|
||||
`bni_group_id` INT NOT NULL,
|
||||
FOREIGN KEY (`calendar_event_id`) REFERENCES `calendar_events`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`bni_group_id`) REFERENCES `bni_groups`(`id`) ON DELETE CASCADE,
|
||||
UNIQUE KEY `unique_event_group` (`calendar_event_id`, `bni_group_id`)
|
||||
)
|
||||
");
|
||||
echo "Table 'calendar_event_groups' created successfully.\n";
|
||||
} else {
|
||||
echo "Table 'calendar_event_groups' already exists.\n";
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Migration failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
19
db/migrations/017_add_group_id_to_people.php
Normal file
19
db/migrations/017_add_group_id_to_people.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/../../db/config.php');
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Add bni_group_id to people table
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM people LIKE 'bni_group_id'");
|
||||
if ($stmt->rowCount() == 0) {
|
||||
$pdo->exec("ALTER TABLE people ADD COLUMN bni_group_id INT NULL AFTER `role`;");
|
||||
$pdo->exec("ALTER TABLE people ADD CONSTRAINT fk_people_bni_group FOREIGN KEY (bni_group_id) REFERENCES bni_groups(id) ON DELETE SET NULL;");
|
||||
}
|
||||
|
||||
echo "Migration 017 completed successfully." . PHP_EOL;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Migration 017 failed: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
24
db/migrations/018_add_details_to_people_table.php
Normal file
24
db/migrations/018_add_details_to_people_table.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = <<<SQL
|
||||
ALTER TABLE people
|
||||
ADD COLUMN nip VARCHAR(255) DEFAULT NULL,
|
||||
ADD COLUMN industry VARCHAR(255) DEFAULT NULL,
|
||||
ADD COLUMN company_size VARCHAR(255) DEFAULT NULL,
|
||||
ADD COLUMN business_description TEXT DEFAULT NULL,
|
||||
ADD COLUMN company_logo_path VARCHAR(255) DEFAULT NULL,
|
||||
ADD COLUMN person_photo_path VARCHAR(255) DEFAULT NULL,
|
||||
ADD COLUMN gains_sheet_path VARCHAR(255) DEFAULT NULL,
|
||||
ADD COLUMN top_wanted_contacts_path VARCHAR(255) DEFAULT NULL,
|
||||
ADD COLUMN top_owned_contacts_path VARCHAR(255) DEFAULT NULL;
|
||||
SQL;
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 018 successfully applied: Added detail columns to people table.\n";
|
||||
} catch (PDOException $e) {
|
||||
echo "Error applying migration 018: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
40
db/migrations/019_add_extra_fields_to_people.php
Normal file
40
db/migrations/019_add_extra_fields_to_people.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
$pdo = db();
|
||||
|
||||
$columns = [
|
||||
'nip' => 'VARCHAR(255) DEFAULT NULL',
|
||||
'industry' => 'VARCHAR(255) DEFAULT NULL',
|
||||
'company_size_revenue' => 'VARCHAR(255) DEFAULT NULL',
|
||||
'business_description' => 'TEXT DEFAULT NULL',
|
||||
'company_logo_path' => 'VARCHAR(255) DEFAULT NULL',
|
||||
'person_photo_path' => 'VARCHAR(255) DEFAULT NULL',
|
||||
'gains_sheet_path' => 'VARCHAR(255) DEFAULT NULL',
|
||||
'top_wanted_contacts_path' => 'VARCHAR(255) DEFAULT NULL',
|
||||
'top_owned_contacts_path' => 'VARCHAR(255) DEFAULT NULL'
|
||||
];
|
||||
|
||||
$sql_check_columns = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'people'";
|
||||
$stmt_check = $pdo->query($sql_check_columns);
|
||||
$existing_columns = $stmt_check->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$sql_alter_parts = [];
|
||||
foreach ($columns as $column_name => $column_definition) {
|
||||
if (!in_array($column_name, $existing_columns)) {
|
||||
$sql_alter_parts[] = "ADD COLUMN `$column_name` $column_definition";
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($sql_alter_parts)) {
|
||||
$sql = "ALTER TABLE people " . implode(', ', $sql_alter_parts);
|
||||
try {
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 019 completed successfully: Added new fields to people table.\n";
|
||||
} catch (PDOException $e) {
|
||||
die("Migration 019 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
} else {
|
||||
echo "Migration 019 skipped: All columns already exist.\n";
|
||||
}
|
||||
|
||||
26
db/migrations/021_rename_camelcase_columns.php
Normal file
26
db/migrations/021_rename_camelcase_columns.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
echo "Starting migration 021: Rename camelCase columns to snake_case...\n";
|
||||
|
||||
// Process instances table
|
||||
$pdo->exec("ALTER TABLE `process_instances` CHANGE `lastActivityAt` `last_activity_at` TIMESTAMP NULL;");
|
||||
|
||||
echo "Columns in 'process_instances' table renamed.\n";
|
||||
|
||||
// Process events table
|
||||
$pdo->exec("ALTER TABLE `process_events` CHANGE `processInstanceId` `process_instance_id` INT(11) UNSIGNED NOT NULL;");
|
||||
$pdo->exec("ALTER TABLE `process_events` CHANGE `createdBy` `created_by` INT(11) UNSIGNED NOT NULL;");
|
||||
$pdo->exec("ALTER TABLE `process_events` CHANGE `createdAt` `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;");
|
||||
|
||||
echo "Columns in 'process_events' table renamed.\n";
|
||||
|
||||
echo "Migration 021 completed successfully.\n";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Migration 021 failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
22
db/migrations/022_add_is_active_to_process_definitions.php
Normal file
22
db/migrations/022_add_is_active_to_process_definitions.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
function migrate_022($pdo) {
|
||||
try {
|
||||
$sql = "ALTER TABLE process_definitions ADD COLUMN is_active TINYINT(1) NOT NULL DEFAULT 1 AFTER definition_json";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration successful: is_active column added to process_definitions table.\n";
|
||||
} catch (PDOException $e) {
|
||||
// Ignore if column already exists
|
||||
if ($e->getCode() !== '42S21') {
|
||||
die("Migration failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If called directly, run the migration
|
||||
if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
$pdo = db();
|
||||
migrate_022($pdo);
|
||||
}
|
||||
22
db/migrations/023_add_new_member_process.php
Normal file
22
db/migrations/023_add_new_member_process.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
function migrate_023($pdo) {
|
||||
$json_definition = '{ "nodes": { "start": { "id": "start", "name": "Start", "ui_hints": { "status": "in_progress", "reason": "Oczekuje na wystawienie pro formy", "next_step": "Wystaw pro formę" } }, "wait_for_payment": { "id": "wait_for_payment", "name": "Oczekiwanie na płatność", "ui_hints": { "status": "in_progress", "reason": "Oczekuje na potwierdzenie wpływu środków", "next_step": "Potwierdź wpływ środków" } }, "notify_committee": { "id": "notify_committee", "name": "Powiadomienie komitetu", "ui_hints": { "status": "in_progress", "reason": "Oczekuje na powiadomienie komitetu", "next_step": "Powiadom komitet i przejdź do decyzji" } }, "decision_committee": { "id": "decision_committee", "name": "Decyzja komitetu", "ui_hints": { "status": "in_progress", "reason": "Oczekuje na decyzję komitetu", "next_step": "Zatwierdź lub odrzuć" } }, "end_accepted": { "id": "end_accepted", "name": "Zatwierdzony", "ui_hints": { "status": "positive", "reason": "Członek przyjęty", "next_step": "" } }, "end_rejected": { "id": "end_rejected", "name": "Odrzucony", "ui_hints": { "status": "negative", "reason": "Członek odrzucony", "next_step": "" } } }, "transitions": [ { "id": "issue_proforma", "name": "Wystaw pro formę", "from": "start", "to": "wait_for_payment", "actions": [ { "type": "set_data", "params": { "keys": ["issue_date", "document_number"] } } ] }, { "id": "confirm_payment", "name": "Potwierdź wpływ środków", "from": "wait_for_payment", "to": "notify_committee", "actions": [ { "type": "set_data", "params": { "keys": ["payment_date"] } } ] } , { "id": "proceed_to_decision", "name": "Przekaż do decyzji komitetu", "from": "notify_committee", "to": "decision_committee" }, { "id": "accept_member", "name": "Zatwierdź", "from": "decision_committee", "to": "end_accepted", "actions": [ { "type": "start_process", "params": { "process_code": "wprowadzenie-nowego-cz-onka" } } ] }, { "id": "reject_member", "name": "Odrzuć", "from": "decision_committee", "to": "end_rejected" } ], "start_node_id": "start", "eligibility_rules": [ { "type": "deny_manual_start" } ] }';
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO process_definitions (name, code, definition_json, is_active) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([
|
||||
'Obsługa przyjęcia nowego członka',
|
||||
'obsluga-przyjecia-nowego-czlonka',
|
||||
$json_definition,
|
||||
1
|
||||
]);
|
||||
}
|
||||
|
||||
// If called directly, run the migration
|
||||
if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
$pdo = db();
|
||||
migrate_023($pdo);
|
||||
echo "Migration 023 executed successfully.\n";
|
||||
}
|
||||
154
db/migrations/024_update_follow_up_process.php
Normal file
154
db/migrations/024_update_follow_up_process.php
Normal file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
function migrate_024()
|
||||
{
|
||||
$pdo = db();
|
||||
|
||||
$process_code = 'guest_handling';
|
||||
|
||||
$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" },
|
||||
{ "name": "note", "label": "Notes", "type": "textarea" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"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": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"transitions": [
|
||||
{
|
||||
"id": "log_wants_to_join",
|
||||
"from": "awaiting_call",
|
||||
"to": "end_positive",
|
||||
"name": "Wants to Join",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_data",
|
||||
"params": { "keys": ["call_date", "note", "outcome_status"] }
|
||||
},
|
||||
{
|
||||
"type": "start_process",
|
||||
"process_code": "obsluga-przyjecia-nowego-czlonka"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "log_declined",
|
||||
"from": "awaiting_call",
|
||||
"to": "end_negative_declined",
|
||||
"name": "Declined",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_data",
|
||||
"params": { "keys": ["call_date", "note", "outcome_status"] }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "log_no_answer",
|
||||
"from": "awaiting_call",
|
||||
"to": "decide_after_no_answer",
|
||||
"name": "No Answer",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_data",
|
||||
"params": { "keys": ["call_date", "note", "outcome_status"] }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "log_call_later",
|
||||
"from": "awaiting_call",
|
||||
"to": "awaiting_call",
|
||||
"name": "Call Later",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_data",
|
||||
"params": { "keys": ["call_date", "note", "outcome_status"] }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOT;
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE process_definitions SET definition_json = :json, start_node_id = 'awaiting_call' WHERE code = :code");
|
||||
$stmt->execute([
|
||||
':json' => $json_definition,
|
||||
':code' => $process_code,
|
||||
]);
|
||||
|
||||
echo "Migration 024 executed successfully: Updated 'guest_handling' process definition.\n";
|
||||
}
|
||||
|
||||
// Direct execution guard
|
||||
if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
try {
|
||||
migrate_024();
|
||||
} catch (Exception $e) {
|
||||
echo "Migration 24 failed: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
161
db/migrations/025_update_follow_up_process_structured.php
Normal file
161
db/migrations/025_update_follow_up_process_structured.php
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
function migrate_025()
|
||||
{
|
||||
$pdo = db();
|
||||
|
||||
$process_code = 'guest_handling';
|
||||
|
||||
$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;
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE process_definitions SET definition_json = :json, start_node_id = 'awaiting_call' WHERE code = :code");
|
||||
$stmt->execute([
|
||||
':json' => $json_definition,
|
||||
':code' => $process_code,
|
||||
]);
|
||||
|
||||
echo "Migration 025 executed successfully: Updated 'guest_handling' process definition with structured data and router node.";
|
||||
}
|
||||
|
||||
// Direct execution guard
|
||||
if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"]))
|
||||
{
|
||||
try {
|
||||
migrate_025();
|
||||
} catch (Exception $e) {
|
||||
echo "Migration 25 failed: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
23
db/migrations/030_create_meetings_table.php
Normal file
23
db/migrations/030_create_meetings_table.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdoconn = db();
|
||||
$pdoconn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS meetings (
|
||||
id INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
bni_group_id INT(11) NOT NULL,
|
||||
meeting_datetime DATETIME NOT NULL,
|
||||
meeting_key VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY meeting_key (meeting_key),
|
||||
FOREIGN KEY (bni_group_id) REFERENCES bni_groups(id) ON DELETE CASCADE
|
||||
)";
|
||||
|
||||
$pdoconn->exec($sql);
|
||||
echo "Table 'meetings' created successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error creating table: " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
27
db/migrations/031_create_meeting_attendance_table.php
Normal file
27
db/migrations/031_create_meeting_attendance_table.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdoconn = db();
|
||||
$pdoconn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS meeting_attendance (
|
||||
id INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
meeting_id INT(11) UNSIGNED NOT NULL,
|
||||
person_id INT(11) UNSIGNED NOT NULL,
|
||||
attendance_status ENUM('present', 'absent', 'substitute', 'none') NOT NULL DEFAULT 'none',
|
||||
guest_survey ENUM('1', '2', '3'),
|
||||
updated_by INT(11) UNSIGNED,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY meeting_person (meeting_id, person_id),
|
||||
FOREIGN KEY (meeting_id) REFERENCES meetings(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (person_id) REFERENCES people(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (updated_by) REFERENCES people(id) ON DELETE SET NULL
|
||||
)";
|
||||
|
||||
$pdoconn->exec($sql);
|
||||
echo "Table 'meeting_attendance' created successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error creating table: " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
73
db/migrations/032_add_meeting_key_to_meetings.php
Normal file
73
db/migrations/032_add_meeting_key_to_meetings.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$db = db();
|
||||
echo "Starting migration 032.\n";
|
||||
|
||||
// Step 1: Add bni_group_id column
|
||||
try {
|
||||
echo "Step 1: Adding bni_group_id column...\n";
|
||||
$db->exec("ALTER TABLE meetings ADD COLUMN bni_group_id INT(11) UNSIGNED NULL;");
|
||||
echo "SUCCESS: Added nullable bni_group_id column.\n";
|
||||
} catch (PDOException $e) {
|
||||
if (strpos($e->getMessage(), 'Duplicate column name') !== false) {
|
||||
echo "INFO: bni_group_id column already exists.\n";
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Add meeting_datetime column
|
||||
try {
|
||||
echo "Step 2: Adding meeting_datetime column...\n";
|
||||
$db->exec("ALTER TABLE meetings ADD COLUMN meeting_datetime DATETIME NULL;");
|
||||
echo "SUCCESS: Added nullable meeting_datetime column.\n";
|
||||
} catch (PDOException $e) {
|
||||
if (strpos($e->getMessage(), 'Duplicate column name') !== false) {
|
||||
echo "INFO: meeting_datetime column already exists.\n";
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Add meeting_key column
|
||||
try {
|
||||
echo "Step 3: Adding meeting_key column...\n";
|
||||
$db->exec("ALTER TABLE meetings ADD COLUMN meeting_key VARCHAR(255) NULL;");
|
||||
echo "SUCCESS: Added nullable meeting_key column.\n";
|
||||
} catch (PDOException $e) {
|
||||
if (strpos($e->getMessage(), 'Duplicate column name') !== false) {
|
||||
echo "INFO: meeting_key column already exists.\n";
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Populate meeting_key
|
||||
echo "Step 4: Populating meeting_key...\n";
|
||||
$updateStmt = $db->prepare("UPDATE meetings SET meeting_key = CONCAT(bni_group_id, '_', meeting_datetime) WHERE (meeting_key IS NULL OR meeting_key = '') AND bni_group_id IS NOT NULL AND meeting_datetime IS NOT NULL");
|
||||
$updateStmt->execute();
|
||||
echo "SUCCESS: Populated meeting_key for " . $updateStmt->rowCount() . " rows.\n";
|
||||
|
||||
// Step 5: Add unique index on meeting_key
|
||||
try {
|
||||
echo "Step 5: Adding unique index on meeting_key...\n";
|
||||
$db->exec("ALTER TABLE meetings ADD UNIQUE (meeting_key);");
|
||||
echo "SUCCESS: Added unique index on meeting_key.\n";
|
||||
} catch (PDOException $e) {
|
||||
if (strpos($e->getMessage(), 'Duplicate entry') !== false) {
|
||||
echo "WARNING: Could not add UNIQUE index because duplicate meeting_key values exist.\n";
|
||||
} elseif (strpos($e->getMessage(), 'already exists') !== false) {
|
||||
echo "INFO: Unique index on meeting_key already exists.\n";
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
echo "Migration 032 completed successfully.\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Migration 032 failed: " . $e->getMessage());
|
||||
die("FATAL: Migration 032 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
71
db/migrations/033_standardize_bni_group_id.php
Normal file
71
db/migrations/033_standardize_bni_group_id.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$db = db();
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
echo "Starting migration 033: Standardize bni_group_id in meeting_attendance.
|
||||
";
|
||||
|
||||
// Ensure bni_group_id column exists and is nullable for now
|
||||
$stmt_bni = $db->query("SHOW COLUMNS FROM meeting_attendance LIKE 'bni_group_id'");
|
||||
$result_bni = $stmt_bni->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$result_bni) {
|
||||
$db->exec("ALTER TABLE meeting_attendance ADD COLUMN bni_group_id INT(11) NULL;");
|
||||
echo "SUCCESS: Added nullable 'bni_group_id' column.
|
||||
";
|
||||
} else {
|
||||
$db->exec("ALTER TABLE meeting_attendance MODIFY bni_group_id INT(11) NULL;");
|
||||
echo "SUCCESS: Modified 'bni_group_id' to be nullable.
|
||||
";
|
||||
}
|
||||
|
||||
// Check for orphaned bni_group_ids
|
||||
$orphan_check = $db->query("SELECT ma.bni_group_id, COUNT(*) as count FROM meeting_attendance ma LEFT JOIN bni_groups bg ON ma.bni_group_id = bg.id WHERE bg.id IS NULL AND ma.bni_group_id IS NOT NULL GROUP BY ma.bni_group_id;");
|
||||
$orphans = $orphan_check->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (count($orphans) > 0) {
|
||||
echo "WARNING: Found orphaned bni_group_ids in meeting_attendance table.
|
||||
";
|
||||
foreach ($orphans as $orphan) {
|
||||
echo " - bni_group_id: " . $orphan['bni_group_id'] . " (" . $orphan['count'] . " rows)
|
||||
";
|
||||
}
|
||||
// For now, we will set them to NULL to allow FK creation
|
||||
foreach ($orphans as $orphan) {
|
||||
if ($orphan['bni_group_id'] !== 0) { // we don't want to update rows that have 0 as this is default
|
||||
$update_stmt = $db->prepare("UPDATE meeting_attendance SET bni_group_id = NULL WHERE bni_group_id = :orphan_id;");
|
||||
$update_stmt->execute(['orphan_id' => $orphan['bni_group_id']]);
|
||||
echo "Set " . $update_stmt->rowCount() . " rows with orphaned bni_group_id " . $orphan['bni_group_id'] . " to NULL.
|
||||
";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo "INFO: No orphaned bni_group_ids found.
|
||||
";
|
||||
}
|
||||
|
||||
// Add foreign key if it doesn't exist
|
||||
$fk_check = $db->query("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'meeting_attendance' AND COLUMN_NAME = 'bni_group_id' AND REFERENCED_TABLE_NAME = 'bni_groups';")->fetch();
|
||||
if (!$fk_check) {
|
||||
echo "Adding foreign key on 'bni_group_id' to 'bni_groups.id'.
|
||||
";
|
||||
$db->exec("ALTER TABLE meeting_attendance ADD CONSTRAINT fk_meeting_attendance_bni_group FOREIGN KEY (bni_group_id) REFERENCES bni_groups(id) ON DELETE CASCADE;");
|
||||
echo "SUCCESS: Foreign key added.
|
||||
";
|
||||
} else {
|
||||
echo "INFO: Foreign key on 'bni_group_id' already exists.
|
||||
";
|
||||
}
|
||||
|
||||
// Now, alter the column to be NOT NULL. This will fail if there are any NULLs left.
|
||||
// I will not do this for now, as the user might want to check the orphaned data first.
|
||||
// I will let the column be nullable for now.
|
||||
|
||||
echo "Migration 033 completed successfully (bni_group_id is currently nullable).
|
||||
";
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Migration 033 failed: " . $e->getMessage());
|
||||
die("FATAL: Migration 033 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
37
db/migrations/034_set_bni_group_id_not_null.php
Normal file
37
db/migrations/034_set_bni_group_id_not_null.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$db = db();
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
echo "Starting migration 034: Set bni_group_id in meeting_attendance to NOT NULL.\n";
|
||||
|
||||
// First, check if there are any NULL values in the bni_group_id column.
|
||||
$null_check = $db->query("SELECT COUNT(*) FROM meeting_attendance WHERE bni_group_id IS NULL;")->fetchColumn();
|
||||
|
||||
if ($null_check > 0) {
|
||||
echo "WARNING: Found $null_check rows with NULL bni_group_id. Attempting to populate them.\n";
|
||||
|
||||
// Try to populate from the corresponding meeting record
|
||||
$update_sql = "UPDATE meeting_attendance ma JOIN meetings m ON ma.meeting_id = m.id SET ma.bni_group_id = m.bni_group_id WHERE ma.bni_group_id IS NULL AND m.bni_group_id IS NOT NULL;";
|
||||
$stmt = $db->exec($update_sql);
|
||||
echo "Populated $stmt rows based on meeting ID.\n";
|
||||
|
||||
// Re-check for NULLs
|
||||
$null_check_after = $db->query("SELECT COUNT(*) FROM meeting_attendance WHERE bni_group_id IS NULL;")->fetchColumn();
|
||||
if($null_check_after > 0) {
|
||||
// If still NULLs, we have to fail.
|
||||
throw new Exception("$null_check_after rows still have NULL bni_group_id after population attempt. Cannot proceed.");
|
||||
}
|
||||
}
|
||||
|
||||
echo "No NULL values found in bni_group_id. Altering column to NOT NULL.\n";
|
||||
$db->exec("ALTER TABLE meeting_attendance MODIFY bni_group_id INT(11) NOT NULL;");
|
||||
echo "SUCCESS: Column bni_group_id in meeting_attendance is now NOT NULL.\n";
|
||||
|
||||
echo "Migration 034 completed successfully.\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Migration 034 failed: " . $e->getMessage());
|
||||
die("FATAL: Migration 034 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
18
db/migrations/035_cleanup_meetings_table.php
Normal file
18
db/migrations/035_cleanup_meetings_table.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
$db = db();
|
||||
|
||||
// Step 1: Back-fill bni_group_id from group_id where bni_group_id is NULL
|
||||
$db->exec("UPDATE meetings SET bni_group_id = group_id WHERE bni_group_id IS NULL");
|
||||
|
||||
// Step 2: Modify bni_group_id to be NOT NULL
|
||||
$db->exec("ALTER TABLE meetings MODIFY COLUMN bni_group_id INT(11) NOT NULL");
|
||||
|
||||
// Step 3: Find and drop the foreign key constraint on group_id
|
||||
$db->exec("ALTER TABLE meetings DROP FOREIGN KEY `meetings_ibfk_1`");
|
||||
|
||||
// Step 4: Drop the old group_id column
|
||||
$db->exec("ALTER TABLE meetings DROP COLUMN group_id");
|
||||
|
||||
echo "Migration 035 cleanup meetings table applied successfully.";
|
||||
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.
|
||||
";
|
||||
}
|
||||
|
||||
66
db/migrations/037_add_process_subjects.php
Normal file
66
db/migrations/037_add_process_subjects.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
function migrate_037($pdo) {
|
||||
echo "Starting migration 037 (Process Subjects)...
|
||||
";
|
||||
|
||||
// 1. process_instances: add subject_type, subject_id, cycle_key, bni_group_id
|
||||
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM process_instances LIKE 'subject_type'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD COLUMN subject_type VARCHAR(20) NOT NULL DEFAULT 'person' AFTER person_id");
|
||||
echo "Added subject_type to process_instances.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM process_instances LIKE 'subject_id'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD COLUMN subject_id INT NOT NULL DEFAULT 0 AFTER subject_type");
|
||||
echo "Added subject_id to process_instances.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM process_instances LIKE 'cycle_key'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD COLUMN cycle_key VARCHAR(64) NULL AFTER subject_id");
|
||||
echo "Added cycle_key to process_instances.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM process_instances LIKE 'bni_group_id'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD COLUMN bni_group_id INT NULL AFTER cycle_key");
|
||||
echo "Added bni_group_id to process_instances.
|
||||
";
|
||||
}
|
||||
|
||||
// 2. Data migration
|
||||
echo "Migrating existing data...
|
||||
";
|
||||
$pdo->exec("UPDATE process_instances SET subject_type = 'person', subject_id = person_id WHERE subject_id = 0");
|
||||
|
||||
// 3. Add indexes
|
||||
$stmt = $pdo->query("SHOW INDEX FROM process_instances WHERE Key_name = 'idx_subject_active'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD INDEX idx_subject_active (subject_type, subject_id, current_status)");
|
||||
echo "Added index idx_subject_active.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW INDEX FROM process_instances WHERE Key_name = 'idx_subject_cycle'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD INDEX idx_subject_cycle (subject_type, subject_id, cycle_key)");
|
||||
echo "Added index idx_subject_cycle.
|
||||
";
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("SHOW INDEX FROM process_instances WHERE Key_name = 'idx_group'");
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->exec("ALTER TABLE process_instances ADD INDEX idx_group (bni_group_id)");
|
||||
echo "Added index idx_group.
|
||||
";
|
||||
}
|
||||
|
||||
echo "Migration 037 completed.
|
||||
";
|
||||
}
|
||||
42
db/migrations/038_add_subject_scope_and_run_number.php
Normal file
42
db/migrations/038_add_subject_scope_and_run_number.php
Normal 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.
|
||||
";
|
||||
}
|
||||
43
db/migrations/039_update_process_names_and_order.php
Normal file
43
db/migrations/039_update_process_names_and_order.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
function migrate_039($pdo) {
|
||||
// 1. Obsługa gościa (guest_handling)
|
||||
// 2. Obsługa przyjęcia nowego członka (obsluga-przyjecia-nowego-czlonka)
|
||||
// 3. Mentoring nowego członka (mentoring)
|
||||
// 4. Szkolenie nowego członka (training_new_member)
|
||||
// 5. Członkostwo (customer_journey)
|
||||
|
||||
$processes = [
|
||||
'guest_handling' => ['name' => 'Obsługa gościa', 'sort_order' => 10],
|
||||
'obsluga-przyjecia-nowego-czlonka' => ['name' => 'Obsługa przyjęcia nowego członka', 'sort_order' => 20],
|
||||
'mentoring' => ['name' => 'Mentoring nowego członka', 'sort_order' => 30],
|
||||
'training_new_member' => ['name' => 'Szkolenie nowego członka', 'sort_order' => 40],
|
||||
'customer_journey' => ['name' => 'Członkostwo', 'sort_order' => 50],
|
||||
|
||||
// Push the other ones to the bottom
|
||||
'meeting_preparation' => ['name' => 'Przygotowanie spotkania grupy', 'sort_order' => 60],
|
||||
'meeting_preparation_meeting' => ['name' => 'Przygotowanie spotkania (spotkanie)', 'sort_order' => 70],
|
||||
];
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE process_definitions SET name = :name, sort_order = :sort_order WHERE code = :code");
|
||||
|
||||
foreach ($processes as $code => $data) {
|
||||
$stmt->execute([
|
||||
'name' => $data['name'],
|
||||
'sort_order' => $data['sort_order'],
|
||||
'code' => $code
|
||||
]);
|
||||
}
|
||||
|
||||
echo "Migration 039 successful: Process names and order updated.\n";
|
||||
} catch (PDOException $e) {
|
||||
die("Migration 039 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// For manual execution if needed
|
||||
if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
migrate_039(db());
|
||||
}
|
||||
481
db/migrations/040_insert_missing_processes.php
Normal file
481
db/migrations/040_insert_missing_processes.php
Normal file
@ -0,0 +1,481 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
function migrate_040($pdo) {
|
||||
$processes = [
|
||||
[
|
||||
'code' => 'training_new_member',
|
||||
'name' => 'Szkolenie nowego członka',
|
||||
'subject_scope' => 'person',
|
||||
'version' => 1,
|
||||
'is_latest' => 1,
|
||||
'is_active' => 1,
|
||||
'sort_order' => 40,
|
||||
'start_node_id' => 'tracker',
|
||||
'definition_json' => '{
|
||||
"start_node_id": "tracker",
|
||||
"nodes": {
|
||||
"tracker": {
|
||||
"id": "tracker",
|
||||
"name": "Tracker szkoleń",
|
||||
"ui_hints": {
|
||||
"title": "Szkolenia dla młodego członka",
|
||||
"status": "in_progress",
|
||||
"reason": "Oczekiwanie na ukończenie wszystkich szkoleń",
|
||||
"next_step": "Zaktualizuj status",
|
||||
"form_schema": [
|
||||
{
|
||||
"name": "w1",
|
||||
"label": "W1",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "w2",
|
||||
"label": "W2",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "w3",
|
||||
"label": "W3",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "w4",
|
||||
"label": "W4",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "w5",
|
||||
"label": "W5",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "w6",
|
||||
"label": "W6",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "msp",
|
||||
"label": "MSP",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "ltt",
|
||||
"label": "LTT",
|
||||
"type": "checkbox"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"completed": {
|
||||
"id": "completed",
|
||||
"name": "Zakończony",
|
||||
"ui_hints": {
|
||||
"title": "Wszystkie szkolenia ukończone",
|
||||
"status": "positive",
|
||||
"reason": "Wszystkie szkolenia zostały ukończone",
|
||||
"next_step": "Zaktualizuj status",
|
||||
"form_schema": [
|
||||
{
|
||||
"name": "w1",
|
||||
"label": "W1",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "w2",
|
||||
"label": "W2",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "w3",
|
||||
"label": "W3",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "w4",
|
||||
"label": "W4",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "w5",
|
||||
"label": "W5",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "w6",
|
||||
"label": "W6",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "msp",
|
||||
"label": "MSP",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"name": "ltt",
|
||||
"label": "LTT",
|
||||
"type": "checkbox"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"transitions": [
|
||||
{
|
||||
"id": "update_tracker",
|
||||
"name": "Zapisz zmiany",
|
||||
"from": "tracker",
|
||||
"to": "tracker",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_data",
|
||||
"params": {
|
||||
"keys": [
|
||||
"w1",
|
||||
"w2",
|
||||
"w3",
|
||||
"w4",
|
||||
"w5",
|
||||
"w6",
|
||||
"msp",
|
||||
"ltt"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "auto_complete",
|
||||
"name": "Automatyczne zakończenie",
|
||||
"from": "tracker",
|
||||
"to": "completed",
|
||||
"condition": {
|
||||
"type": "all_true",
|
||||
"fields": [
|
||||
"w1",
|
||||
"w2",
|
||||
"w3",
|
||||
"w4",
|
||||
"w5",
|
||||
"w6",
|
||||
"msp",
|
||||
"ltt"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "update_completed",
|
||||
"name": "Zapisz zmiany",
|
||||
"from": "completed",
|
||||
"to": "tracker",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_data",
|
||||
"params": {
|
||||
"keys": [
|
||||
"w1",
|
||||
"w2",
|
||||
"w3",
|
||||
"w4",
|
||||
"w5",
|
||||
"w6",
|
||||
"msp",
|
||||
"ltt"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]'
|
||||
],
|
||||
[
|
||||
'code' => 'customer_journey',
|
||||
'name' => 'Członkostwo',
|
||||
'subject_scope' => 'person',
|
||||
'version' => 1,
|
||||
'is_latest' => 1,
|
||||
'is_active' => 1,
|
||||
'sort_order' => 50,
|
||||
'start_node_id' => 'survey_3m',
|
||||
'definition_json' => '{
|
||||
"start_node_id": "survey_3m",
|
||||
"initial_data": {
|
||||
"cycle_year": 1
|
||||
},
|
||||
"nodes": {
|
||||
"survey_3m": {
|
||||
"name": "Ankieta 3 miesiąca",
|
||||
"type": "user_task",
|
||||
"ui_hints": {
|
||||
"title": "Ankieta 3 miesiąca",
|
||||
"status": "in_progress",
|
||||
"reason": "Oczekuje na ankietę po 3 miesiącach",
|
||||
"next_step": "Wypełnij ankietę",
|
||||
"form_schema": [
|
||||
{
|
||||
"name": "survey_3m_done_at",
|
||||
"type": "date",
|
||||
"label": "Data wykonania",
|
||||
"required": true,
|
||||
"default": "now"
|
||||
},
|
||||
{
|
||||
"name": "note",
|
||||
"type": "text",
|
||||
"label": "Notatka"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"survey_7m": {
|
||||
"name": "Ankieta 7 miesiąca",
|
||||
"type": "user_task",
|
||||
"ui_hints": {
|
||||
"title": "Ankieta 7 miesiąca",
|
||||
"status": "in_progress",
|
||||
"reason": "Oczekuje na ankietę po 7 miesiącach",
|
||||
"next_step": "Wypełnij ankietę",
|
||||
"form_schema": [
|
||||
{
|
||||
"name": "survey_7m_done_at",
|
||||
"type": "date",
|
||||
"label": "Data wykonania",
|
||||
"required": true,
|
||||
"default": "now"
|
||||
},
|
||||
{
|
||||
"name": "note",
|
||||
"type": "text",
|
||||
"label": "Notatka"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"payment": {
|
||||
"name": "Wpłata",
|
||||
"type": "user_task",
|
||||
"ui_hints": {
|
||||
"title": "Wpłata",
|
||||
"status": "in_progress",
|
||||
"reason": "Oczekuje na wpłatę",
|
||||
"next_step": "Zaksięguj wpłatę",
|
||||
"form_schema": [
|
||||
{
|
||||
"name": "payment_done_at",
|
||||
"type": "date",
|
||||
"label": "Data wpłaty",
|
||||
"required": true,
|
||||
"default": "now"
|
||||
},
|
||||
{
|
||||
"name": "note",
|
||||
"type": "text",
|
||||
"label": "Notatka"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"committee": {
|
||||
"name": "Komitet",
|
||||
"type": "user_task",
|
||||
"ui_hints": {
|
||||
"title": "Komitet",
|
||||
"status": "in_progress",
|
||||
"reason": "Decyzja komitetu",
|
||||
"next_step": "Wprowadź decyzję",
|
||||
"form_schema": [
|
||||
{
|
||||
"name": "committee_decision",
|
||||
"type": "select",
|
||||
"label": "Decyzja",
|
||||
"options": [
|
||||
{
|
||||
"value": "approved",
|
||||
"label": "Zatwierdzony"
|
||||
},
|
||||
{
|
||||
"value": "rejected",
|
||||
"label": "Odrzucony"
|
||||
},
|
||||
{
|
||||
"value": "pending",
|
||||
"label": "Oczekujący"
|
||||
}
|
||||
],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "note",
|
||||
"type": "text",
|
||||
"label": "Notatka"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"renewal": {
|
||||
"name": "Przedłużenie członkostwa",
|
||||
"type": "user_task",
|
||||
"ui_hints": {
|
||||
"title": "Przedłużenie członkostwa",
|
||||
"status": "in_progress",
|
||||
"reason": "Oczekuje na przedłużenie",
|
||||
"next_step": "Przedłuż na kolejny rok",
|
||||
"form_schema": [
|
||||
{
|
||||
"name": "renewal_done_at",
|
||||
"type": "date",
|
||||
"label": "Data przedłużenia",
|
||||
"required": true,
|
||||
"default": "now"
|
||||
},
|
||||
{
|
||||
"name": "note",
|
||||
"type": "text",
|
||||
"label": "Notatka"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"transitions": [
|
||||
{
|
||||
"id": "finish_survey_3m",
|
||||
"name": "Zakończ ankietę (3m)",
|
||||
"from": "survey_3m",
|
||||
"to": "survey_7m",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_data",
|
||||
"params": {
|
||||
"keys": [
|
||||
"survey_3m_done_at",
|
||||
"note"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "finish_survey_7m",
|
||||
"name": "Zakończ ankietę (7m)",
|
||||
"from": "survey_7m",
|
||||
"to": "payment",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_data",
|
||||
"params": {
|
||||
"keys": [
|
||||
"survey_7m_done_at",
|
||||
"note"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "finish_payment",
|
||||
"name": "Potwierdź wpłatę",
|
||||
"from": "payment",
|
||||
"to": "committee",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_data",
|
||||
"params": {
|
||||
"keys": [
|
||||
"payment_done_at",
|
||||
"note"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "finish_committee",
|
||||
"name": "Wprowadź decyzję komitetu",
|
||||
"from": "committee",
|
||||
"to": "renewal",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_data",
|
||||
"params": {
|
||||
"keys": [
|
||||
"committee_decision",
|
||||
"note"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "finish_renewal",
|
||||
"name": "Zamknij cykl i rozpocznij kolejny rok",
|
||||
"from": "renewal",
|
||||
"to": "survey_3m",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_data",
|
||||
"params": {
|
||||
"keys": [
|
||||
"renewal_done_at",
|
||||
"note"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "increment_data",
|
||||
"params": {
|
||||
"key": "cycle_year",
|
||||
"by": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "reset_data",
|
||||
"params": {
|
||||
"keys": [
|
||||
"survey_3m_done_at",
|
||||
"survey_7m_done_at",
|
||||
"payment_done_at",
|
||||
"committee_decision",
|
||||
"renewal_done_at"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]'
|
||||
]
|
||||
];
|
||||
|
||||
try {
|
||||
$checkStmt = $pdo->prepare("SELECT COUNT(*) FROM process_definitions WHERE code = :code");
|
||||
$insertStmt = $pdo->prepare("INSERT INTO process_definitions (code, name, subject_scope, version, is_latest, is_active, sort_order, start_node_id, definition_json) VALUES (:code, :name, :subject_scope, :version, :is_latest, :is_active, :sort_order, :start_node_id, :definition_json)");
|
||||
|
||||
foreach ($processes as $p) {
|
||||
$checkStmt->execute(['code' => $p['code']]);
|
||||
$exists = $checkStmt->fetchColumn() > 0;
|
||||
|
||||
if (!$exists) {
|
||||
$insertStmt->execute([
|
||||
'code' => $p['code'],
|
||||
'name' => $p['name'],
|
||||
'subject_scope' => $p['subject_scope'],
|
||||
'version' => $p['version'],
|
||||
'is_latest' => $p['is_latest'],
|
||||
'is_active' => $p['is_active'],
|
||||
'sort_order' => $p['sort_order'],
|
||||
'start_node_id' => $p['start_node_id'],
|
||||
'definition_json' => $p['definition_json']
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
echo "Migration 040 successful: Missing process definitions inserted.\n";
|
||||
} catch (PDOException $e) {
|
||||
die("Migration 040 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// For manual execution if needed
|
||||
if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
migrate_040(db());
|
||||
}
|
||||
134
db_setup.php
Normal file
134
db_setup.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
echo "Starting database setup...\n";
|
||||
|
||||
// 1. People table (unified table for users and contacts)
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `people` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`first_name` VARCHAR(255) NOT NULL,
|
||||
`last_name` VARCHAR(255) NOT NULL,
|
||||
`email` VARCHAR(255) NOT NULL UNIQUE,
|
||||
`password` VARCHAR(255) NULL,
|
||||
`company_name` VARCHAR(255) DEFAULT NULL,
|
||||
`phone` VARCHAR(50) DEFAULT NULL,
|
||||
`role` ENUM('admin', 'team_member', 'member', 'guest') NOT NULL DEFAULT 'guest',
|
||||
`is_user` BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
`active` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
echo "People table created or already exists.\n";
|
||||
|
||||
// Seed default admin user
|
||||
$stmt = $pdo->prepare("SELECT id FROM people WHERE email = ?");
|
||||
$stmt->execute(['admin@example.com']);
|
||||
if ($stmt->fetchColumn() === false) {
|
||||
$password = password_hash('password', PASSWORD_DEFAULT);
|
||||
$insert_stmt = $pdo->prepare(
|
||||
"INSERT INTO people (email, password, role, first_name, last_name, is_user, active) VALUES (?, ?, 'admin', 'Admin', 'User', TRUE, TRUE)"
|
||||
);
|
||||
$insert_stmt->execute(['admin@example.com', $password]);
|
||||
echo "Default admin user created. Email: admin@example.com, Password: password\n";
|
||||
}
|
||||
|
||||
// 2. Process Definitions table
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `process_definitions` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`code` VARCHAR(255) NOT NULL UNIQUE,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`description` TEXT,
|
||||
`version` INT NOT NULL DEFAULT 1,
|
||||
`is_active` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`start_node_id` VARCHAR(255),
|
||||
`definition_json` TEXT,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
echo "Process definitions table created or already exists.\n";
|
||||
|
||||
// Seed process_definitions
|
||||
$processes = [
|
||||
['code' => 'mentoring', 'name' => 'Mentoring nowego czlonka', 'description' => 'Proces wdrozenia nowego czlonka do organizacji.'],
|
||||
['code' => 'meeting_preparation', 'name' => 'Przygotowanie spotkania grupy', 'description' => 'Proces przygotowania do spotkania grupy, w tym agenda, materialy, etc.'],
|
||||
['code' => 'guest_handling', 'name' => 'Obsluga goscia', 'description' => 'Proces obslugi gosci odwiedzajacych organizacje.']
|
||||
];
|
||||
$stmt = $pdo->prepare("SELECT id FROM process_definitions WHERE code = ?");
|
||||
$insert_stmt = $pdo->prepare("INSERT INTO process_definitions (code, name, description) VALUES (?, ?, ?)");
|
||||
foreach ($processes as $process) {
|
||||
$stmt->execute([$process['code']]);
|
||||
if ($stmt->fetchColumn() === false) {
|
||||
$insert_stmt->execute([$process['code'], $process['name'], $process['description']]);
|
||||
echo "Seeded process: " . $process['name'] . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Process Instances table (updated FK)
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `process_instances` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`person_id` INT(11) UNSIGNED NOT NULL,
|
||||
`process_definition_id` INT(11) UNSIGNED NOT NULL,
|
||||
`current_status` VARCHAR(255) NOT NULL DEFAULT 'none',
|
||||
`current_node_id` VARCHAR(255),
|
||||
`current_reason` TEXT,
|
||||
`suggested_next_step` TEXT,
|
||||
`data_json` TEXT,
|
||||
`last_activity_at` TIMESTAMP NULL,
|
||||
FOREIGN KEY (person_id) REFERENCES people(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (process_definition_id) REFERENCES process_definitions(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY `person_process` (`person_id`, `process_definition_id`)
|
||||
)");
|
||||
echo "Process instances table created or already exists.\n";
|
||||
|
||||
// 4. Process Events table (updated FK)
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `process_events` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`process_instance_id` INT(11) UNSIGNED NOT NULL,
|
||||
`event_type` VARCHAR(50) NOT NULL,
|
||||
`message` TEXT,
|
||||
`node_id` VARCHAR(255),
|
||||
`payload_json` TEXT,
|
||||
`created_by` INT(11) UNSIGNED NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (process_instance_id) REFERENCES process_instances(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (created_by) REFERENCES people(id) ON DELETE CASCADE
|
||||
)");
|
||||
echo "Process events table created or already exists.\n";
|
||||
|
||||
// MIGRATIONS
|
||||
echo "Starting migrations...\n";
|
||||
|
||||
// Migration: Rename `processId` to `processDefinitionId` in `process_instances`
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM `process_instances` LIKE 'processId'");
|
||||
if ($stmt->fetch()) {
|
||||
$checkFk = $pdo->query("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = SCHEMA() AND TABLE_NAME = 'process_instances' AND COLUMN_NAME = 'processId';")->fetch();
|
||||
if ($checkFk) {
|
||||
$pdo->exec("ALTER TABLE `process_instances` DROP FOREIGN KEY `{$checkFk['CONSTRAINT_NAME']}`;");
|
||||
}
|
||||
$pdo->exec("ALTER TABLE `process_instances` CHANGE `processId` `process_definition_id` INT(11) UNSIGNED NOT NULL;");
|
||||
$pdo->exec("ALTER TABLE `process_instances` ADD FOREIGN KEY (`process_definition_id`) REFERENCES `process_definitions`(`id`) ON DELETE CASCADE;");
|
||||
echo "Migrated process_instances: processId -> processDefinitionId.\n";
|
||||
}
|
||||
|
||||
// Migration: Rename `contactId` to `personId` and update foreign key in `process_instances`
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM `process_instances` LIKE 'contactId'");
|
||||
if ($stmt->fetch()) {
|
||||
$checkFk = $pdo->query("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = SCHEMA() AND TABLE_NAME = 'process_instances' AND COLUMN_NAME = 'contactId';")->fetch();
|
||||
if ($checkFk) {
|
||||
$pdo->exec("ALTER TABLE `process_instances` DROP FOREIGN KEY `{$checkFk['CONSTRAINT_NAME']}`;");
|
||||
}
|
||||
$pdo->exec("ALTER TABLE `process_instances` CHANGE `contactId` `person_id` INT(11) UNSIGNED NOT NULL;");
|
||||
echo "Migrated process_instances: contactId -> personId.\n";
|
||||
}
|
||||
|
||||
|
||||
// Drop old tables if they exist
|
||||
$pdo->exec("DROP TABLE IF EXISTS `users`, `contacts`;");
|
||||
echo "Dropped old 'users' and 'contacts' tables.\n";
|
||||
|
||||
echo "\nDatabase setup/update completed successfully.\n";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Database setup failed: " . $e->getMessage());
|
||||
}
|
||||
63
do_patch.py
Normal file
63
do_patch.py
Normal file
@ -0,0 +1,63 @@
|
||||
import re
|
||||
|
||||
with open('_get_instance_details.php', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
old_block = """ <?php foreach ($currentNode['ui_hints']['form_schema'] as $field): ?>
|
||||
<div class="mb-3">
|
||||
<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') : '' ?>">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>"""
|
||||
|
||||
new_block = """ <?php foreach ($currentNode['ui_hints']['form_schema'] as $field):
|
||||
$fieldName = $field['name'];
|
||||
$currentValue = $instanceData[$fieldName] ?? null;
|
||||
?>
|
||||
<?php if ($field['type'] === 'checkbox'): ?>
|
||||
<div class="form-check mb-3">
|
||||
<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_block, new_block)
|
||||
with open('_get_instance_details.php', 'w') as f:
|
||||
f.write(content)
|
||||
print("Patched successfully")
|
||||
else:
|
||||
print("Old block not found!")
|
||||
53
do_patch_base64.py
Normal file
53
do_patch_base64.py
Normal file
@ -0,0 +1,53 @@
|
||||
import re
|
||||
|
||||
with open('_get_instance_details.php', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
pattern = re.compile(r"<\?php foreach \(\dcurrentNode\['ui_hints'\]\['form_schema'\] as \$field\): \?>(.*?)<\?php endforeach; \?>", re.DOTALL)
|
||||
|
||||
# use a function for replacement so it doesnt parse backslashes
|
||||
replacement_text = r"""<?php foreach ($currentNode['ui_hints']['form_schema'] as $field):
|
||||
$fieldName = $field['name'];
|
||||
$currentValue = $instanceData[$fieldName] ?? null;
|
||||
?>
|
||||
<?php if ($field['type'] === 'checkbox'): ?>
|
||||
<div class="form-check mb-3">
|
||||
<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: ?>
|
||||
<?ph`
|
||||
$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; ?>"""
|
||||
|
||||
def replacer(match):
|
||||
return replacement_text
|
||||
|
||||
matches = pattern.search(content)
|
||||
if matches:
|
||||
content = pattern.sub(replacer, content, count=1)
|
||||
with open('_get_instance_details.php', 'w') as f:
|
||||
f.write(content)
|
||||
print("Patched successfully!")
|
||||
else:
|
||||
print("Pattern not found!")
|
||||
48
do_patch_final.py
Normal file
48
do_patch_final.py
Normal file
@ -0,0 +1,48 @@
|
||||
import re
|
||||
|
||||
with open('_get_instance_details.php', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
pattern = re.compile(r"<\?php foreach \(\dcurrentNode\['ui_hints'\]\['form_schema'\] as \$field\): \?>(.*?)<\?php endforeach; \?>", re.DOTALL)
|
||||
|
||||
replacement = r"""<?php foreach ($currentNode['ui_hints']['form_schema'] as $field):
|
||||
$fieldName = $field['name'];
|
||||
$currentValue = $instanceData[$fieldName] ?? null;
|
||||
?>
|
||||
<?php if ($field['type'] === 'checkbox'): ?>
|
||||
<div class="form-check mb-3">
|
||||
<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 pattern.search(content):
|
||||
content = pattern.sub(lambda m: replacement, content, count=1)
|
||||
with open('_get_instance_details.php', 'w') as f:
|
||||
f.write(content)
|
||||
print("Patched successfully!")
|
||||
else:
|
||||
print("Pattern not found!")
|
||||
185
event_types.php
Normal file
185
event_types.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
include '_header.php';
|
||||
include '_navbar.php';
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT * FROM event_types ORDER BY display_order");
|
||||
$event_types = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<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">
|
||||
<h1 class="h2 pt-3 pb-2 mb-3 border-bottom"><?php echo t('event_types.title', 'Event Types'); ?></h1>
|
||||
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
|
||||
<?= $_SESSION['success_message']; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="d-flex justify-content-end mb-3">
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||
<?php echo t('event_types.add_new', 'Add New Event Type'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive mt-4">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30px;"></th>
|
||||
<th><?php echo t('event_types.name', 'Name'); ?></th>
|
||||
<th><?php echo t('event_types.color', 'Color'); ?></th>
|
||||
<th><?php echo t('event_types.actions', 'Actions'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sortable-list">
|
||||
<?php foreach ($event_types as $type): ?>
|
||||
<tr data-id="<?= $type['id'] ?>">
|
||||
<td class="handle"><i class="bi bi-grip-vertical"></i></td>
|
||||
<td><?= htmlspecialchars($type['name']) ?></td>
|
||||
<td><span class="badge" style="background-color: <?= htmlspecialchars($type['color']) ?>"><?= htmlspecialchars($type['color']) ?></span></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#editModal" data-id="<?= $type['id'] ?>" data-name="<?= htmlspecialchars($type['name']) ?>" data-color="<?= htmlspecialchars($type['color']) ?>" data-display-order="<?= htmlspecialchars($type['display_order']) ?>">
|
||||
<?php echo t('event_types.edit', 'Edit'); ?>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#deleteModal" data-id="<?= $type['id'] ?>">
|
||||
<?php echo t('event_types.delete', 'Delete'); ?>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Modal -->
|
||||
<div class="modal fade" id="addModal" tabindex="-1" aria-labelledby="addModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addModalLabel"><?php echo t('event_types.add_title', 'Add Event Type'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_add_event_type.php" method="post">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="addName" class="form-label"><?php echo t('event_types.name', 'Name'); ?></label>
|
||||
<input type="text" class="form-control" id="addName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="addColor" class="form-label"><?php echo t('event_types.color', 'Color'); ?></label>
|
||||
<input type="color" class="form-control" id="addColor" name="color" value="#007bff" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo t('modal.close', 'Close'); ?></button>
|
||||
<button type="submit" name="add" class="btn btn-primary"><?php echo t('event_types.add_button', 'Add'); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel"><?php echo t('event_types.edit_title', 'Edit Event Type'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_update_event_type.php" method="post">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="editId" name="id">
|
||||
<input type="hidden" id="editDisplayOrder" name="display_order">
|
||||
<div class="mb-3">
|
||||
<label for="editName" class="form-label"><?php echo t('event_types.name', 'Name'); ?></label>
|
||||
<input type="text" class="form-control" id="editName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editColor" class="form-label"><?php echo t('event_types.color', 'Color'); ?></label>
|
||||
<input type="color" class="form-control" id="editColor" name="color" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo t('modal.close', 'Close'); ?></button>
|
||||
<button type="submit" name="edit" class="btn btn-primary"><?php echo t('modal.save_changes', 'Save changes'); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteModalLabel"><?php echo t('event_types.delete_title', 'Delete Event Type'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php echo t('event_types.delete_confirm', 'Are you sure you want to delete this event type?'); ?>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo t('modal.cancel', 'Cancel'); ?></button>
|
||||
<a href="#" id="deleteLink" class="btn btn-danger"><?php echo t('event_types.delete', 'Delete'); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<?php include '_footer.php'; ?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var editModal = document.getElementById('editModal');
|
||||
editModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var name = button.getAttribute('data-name');
|
||||
var color = button.getAttribute('data-color');
|
||||
var display_order = button.getAttribute('data-display-order');
|
||||
|
||||
var modalTitle = editModal.querySelector('.modal-title');
|
||||
var idInput = editModal.querySelector('#editId');
|
||||
var nameInput = editModal.querySelector('#editName');
|
||||
var colorInput = editModal.querySelector('#editColor');
|
||||
var displayOrderInput = editModal.querySelector('#editDisplayOrder');
|
||||
|
||||
idInput.value = id;
|
||||
nameInput.value = name;
|
||||
colorInput.value = color;
|
||||
displayOrderInput.value = display_order;
|
||||
});
|
||||
|
||||
var deleteModal = document.getElementById('deleteModal');
|
||||
deleteModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var deleteLink = deleteModal.querySelector('#deleteLink');
|
||||
deleteLink.href = '_delete_event_type.php?id=' + id;
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$("#sortable-list").sortable({
|
||||
handle: ".handle",
|
||||
update: function(event, ui) {
|
||||
var order = $(this).sortable('toArray', {attribute: 'data-id'});
|
||||
$.post('_update_event_type_order.php', { order: order });
|
||||
}
|
||||
}).disableSelection();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
195
functions.php
Normal file
195
functions.php
Normal file
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
include '_header.php';
|
||||
include '_navbar.php';
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT f.*, bg.name as group_name FROM functions f LEFT JOIN bni_groups bg ON f.bni_group_id = bg.id ORDER BY f.display_order");
|
||||
$functions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$group_stmt = $pdo->query("SELECT * FROM bni_groups WHERE active = 1 ORDER BY name");
|
||||
$bni_groups = $group_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
?>
|
||||
|
||||
<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">
|
||||
<h1 class="h2 pt-3 pb-2 mb-3 border-bottom"><?php echo t('functions.title', 'Functions'); ?></h1>
|
||||
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show mt-3" function="alert">
|
||||
<?= $_SESSION['success_message']; ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php unset($_SESSION['success_message']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="d-flex justify-content-end mb-3">
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||
<?php echo t('functions.add_new', 'Add New Function'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive mt-4">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30px;"></th>
|
||||
<th><?php echo t('functions.name', 'Name'); ?></th>
|
||||
<th><?php echo t('functions.group', 'Group'); ?></th>
|
||||
<th><?php echo t('functions.actions', 'Actions'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sortable-list">
|
||||
<?php foreach ($functions as $function): ?>
|
||||
<tr data-id="<?= $function['id'] ?>">
|
||||
<td class="handle"><i class="bi bi-grip-vertical"></i></td>
|
||||
<td><?= htmlspecialchars($function['name']) ?></td>
|
||||
<td><?= htmlspecialchars($function['group_name']) ?></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#editModal" data-id="<?= $function['id'] ?>" data-name="<?= htmlspecialchars($function['name']) ?>" data-bni_group_id="<?= $function['bni_group_id'] ?>">
|
||||
<?php echo t('functions.edit', 'Edit'); ?>
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#deleteModal" data-id="<?= $function['id'] ?>">
|
||||
<?php echo t('functions.delete', 'Delete'); ?>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Modal -->
|
||||
<div class="modal fade" id="addModal" tabindex="-1" aria-labelledby="addModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addModalLabel"><?php echo t('functions.add_title', 'Add Function'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_add_function.php" method="post">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="addName" class="form-label"><?php echo t('functions.name', 'Name'); ?></label>
|
||||
<input type="text" class="form-control" id="addName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="addBniGroup" class="form-label"><?php echo t('functions.group', 'Group'); ?></label>
|
||||
<select class="form-select" id="addBniGroup" name="bni_group_id" required>
|
||||
<option value=""><?php echo t('functions.select_group', 'Select a group'); ?></option>
|
||||
<?php foreach ($bni_groups as $group): ?>
|
||||
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo t('modal.close', 'Close'); ?></button>
|
||||
<button type="submit" name="add" class="btn btn-primary"><?php echo t('functions.add_button', 'Add'); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel"><?php echo t('functions.edit_title', 'Edit Function'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_update_function.php" method="post">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="editId" name="id">
|
||||
<div class="mb-3">
|
||||
<label for="editName" class="form-label"><?php echo t('functions.name', 'Name'); ?></label>
|
||||
<input type="text" class="form-control" id="editName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editBniGroup" class="form-label"><?php echo t('functions.group', 'Group'); ?></label>
|
||||
<select class="form-select" id="editBniGroup" name="bni_group_id" required>
|
||||
<option value=""><?php echo t('functions.select_group', 'Select a group'); ?></option>
|
||||
<?php foreach ($bni_groups as $group): ?>
|
||||
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo t('modal.close', 'Close'); ?></button>
|
||||
<button type="submit" name="edit" class="btn btn-primary"><?php echo t('modal.save_changes', 'Save changes'); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteModalLabel"><?php echo t('functions.delete_title', 'Delete Function'); ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php echo t('functions.delete_confirm', 'Are you sure you want to delete this function?'); ?>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo t('modal.cancel', 'Cancel'); ?></button>
|
||||
<a href="#" id="deleteLink" class="btn btn-danger"><?php echo t('functions.delete', 'Delete'); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<?php include '_footer.php'; ?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var editModal = document.getElementById('editModal');
|
||||
editModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var name = button.getAttribute('data-name');
|
||||
var bni_group_id = button.getAttribute('data-bni_group_id');
|
||||
|
||||
var modalTitle = editModal.querySelector('.modal-title');
|
||||
var idInput = editModal.querySelector('#editId');
|
||||
var nameInput = editModal.querySelector('#editName');
|
||||
var groupSelect = editModal.querySelector('#editBniGroup');
|
||||
|
||||
idInput.value = id;
|
||||
nameInput.value = name;
|
||||
groupSelect.value = bni_group_id;
|
||||
});
|
||||
|
||||
var deleteModal = document.getElementById('deleteModal');
|
||||
deleteModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var deleteLink = deleteModal.querySelector('#deleteLink');
|
||||
deleteLink.href = '_delete_function.php?id=' + id;
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$("#sortable-list").sortable({
|
||||
handle: ".handle",
|
||||
update: function(event, ui) {
|
||||
var order = $(this).sortable('toArray', {attribute: 'data-id'});
|
||||
$.post('_update_function_order.php', { order: order });
|
||||
}
|
||||
}).disableSelection();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
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'];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user