diff --git a/employees.php b/employees.php new file mode 100644 index 0000000..28bc60b --- /dev/null +++ b/employees.php @@ -0,0 +1,193 @@ +prepare("INSERT IGNORE INTO users (tenant_id, name, email, role) VALUES (?, ?, ?, 'staff')"); + $stmt->execute([$tenant_id, "$first_name $last_name", $email]); + $user_id = (int)db()->lastInsertId(); + if ($user_id === 0) { + $stmt = db()->prepare("SELECT id FROM users WHERE email = ?"); + $stmt->execute([$email]); + $user_id = (int)($stmt->fetchColumn() ?: null); + } + } + + $stmt = db()->prepare("INSERT INTO employees (tenant_id, first_name, last_name, email, position, start_date, is_limited, user_id, name) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$tenant_id, $first_name, $last_name, $email, $position, $start_date, $is_limited, $user_id, "$first_name $last_name"]); + $employee_id = (int)db()->lastInsertId(); + + if ($initial_wage > 0) { + $stmt = db()->prepare("INSERT INTO employee_wages (tenant_id, employee_id, hourly_rate, effective_date) VALUES (?, ?, ?, ?)"); + $stmt->execute([$tenant_id, $employee_id, $initial_wage, $start_date]); + } + + if (!empty($team_ids)) { + foreach ($team_ids as $tid) { + $stmt = db()->prepare("INSERT INTO employee_teams (tenant_id, employee_id, team_id) VALUES (?, ?, ?)"); + $stmt->execute([$tenant_id, $employee_id, $tid]); + } + } + + $stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)"); + $stmt->execute([$tenant_id, 'Employee Created', "Added employee: $first_name $last_name"]); + + header("Location: employees.php?success=1"); + exit; + } +} + +// Fetch Data +$employees = db()->prepare(" + SELECT e.*, + (SELECT hourly_rate FROM employee_wages WHERE employee_id = e.id ORDER BY effective_date DESC LIMIT 1) as current_wage + FROM employees e + WHERE e.tenant_id = ? + ORDER BY e.first_name, e.last_name +"); +$employees->execute([$tenant_id]); +$employeeList = $employees->fetchAll(); + +$teams = db()->prepare("SELECT * FROM teams WHERE tenant_id = ? ORDER BY name"); +$teams->execute([$tenant_id]); +$teamList = $teams->fetchAll(); + +$pageTitle = "SR&ED Manager - Employees"; +include __DIR__ . '/includes/header.php'; +?> + +
+
+

Employees

+ +
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
NamePositionTeamsWageAccessActions
No employees found.
+ prepare("SELECT t.name FROM teams t JOIN employee_teams et ON t.id = et.team_id WHERE et.employee_id = ?"); + $e_teams->execute([$e['id']]); + $t_names = $e_teams->fetchAll(PDO::FETCH_COLUMN); + foreach ($t_names as $tn) { + echo '' . htmlspecialchars($tn) . ''; + } + ?> + $/h + +
+
+
+
+ + + + + diff --git a/expenses.php b/expenses.php new file mode 100644 index 0000000..1065698 --- /dev/null +++ b/expenses.php @@ -0,0 +1,202 @@ + 0) { + $stmt = db()->prepare("INSERT INTO expenses (tenant_id, project_id, supplier_id, expense_type_id, amount, allocation_percent, entry_date, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$tenant_id, $project_id, $supplier_id, $expense_type_id, $amount, $allocation, $entry_date, $notes]); + $expense_id = (int)db()->lastInsertId(); + + // Handle File Uploads + if (!empty($_FILES['attachments']['name'][0])) { + foreach ($_FILES['attachments']['tmp_name'] as $key => $tmp_name) { + if ($_FILES['attachments']['error'][$key] === UPLOAD_ERR_OK) { + $file_name = $_FILES['attachments']['name'][$key]; + $file_size = $_FILES['attachments']['size'][$key]; + $mime_type = $_FILES['attachments']['type'][$key]; + $file_ext = pathinfo($file_name, PATHINFO_EXTENSION); + $new_file_name = uniqid() . '.' . $file_ext; + $file_path = 'uploads/' . $new_file_name; + + if (!is_dir('uploads')) mkdir('uploads', 0775, true); + if (move_uploaded_file($tmp_name, $file_path)) { + $stmt = db()->prepare("INSERT INTO attachments (tenant_id, entity_type, entity_id, file_name, file_path, file_size, mime_type) VALUES (?, 'expense', ?, ?, ?, ?, ?)"); + $stmt->execute([$tenant_id, $expense_id, $file_name, $file_path, $file_size, $mime_type]); + } + } + } + } + + $stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)"); + $stmt->execute([$tenant_id, 'Expense Logged', "Logged \$" . number_format($amount, 2) . " expense for project ID $project_id"]); + + header("Location: expenses.php?success=1"); + exit; + } +} + +// Fetch Data +$expenseEntries = db()->prepare(" + SELECT e.*, p.name as project_name, s.name as supplier_name, et.name as expense_type + FROM expenses e + JOIN projects p ON e.project_id = p.id + JOIN suppliers s ON e.supplier_id = s.id + LEFT JOIN expense_types et ON e.expense_type_id = et.id + WHERE e.tenant_id = ? + ORDER BY e.entry_date DESC, e.created_at DESC +"); +$expenseEntries->execute([$tenant_id]); +$expenseList = $expenseEntries->fetchAll(); + +$projects = db()->prepare("SELECT id, name FROM projects WHERE tenant_id = ? ORDER BY name"); +$projects->execute([$tenant_id]); +$projectList = $projects->fetchAll(); + +$suppliers = db()->prepare("SELECT * FROM suppliers WHERE tenant_id = ? ORDER BY name"); +$suppliers->execute([$tenant_id]); +$supplierList = $suppliers->fetchAll(); + +$expenseTypes = db()->prepare("SELECT * FROM expense_types WHERE tenant_id = ? ORDER BY name"); +$expenseTypes->execute([$tenant_id]); +$expenseTypeList = $expenseTypes->fetchAll(); + +$pageTitle = "SR&ED Manager - Expenses"; +include __DIR__ . '/includes/header.php'; +?> + +
+
+

Expenses

+ +
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
DateSupplierProjectAmountAllocationActions
No expenses found.
+
+ +
$ +
+
+
+ % SR&ED +
+ +
+
+
+
+ + + + + diff --git a/includes/footer.php b/includes/footer.php new file mode 100644 index 0000000..54a6965 --- /dev/null +++ b/includes/footer.php @@ -0,0 +1,35 @@ + + + + + + + diff --git a/includes/header.php b/includes/header.php new file mode 100644 index 0000000..5e7f483 --- /dev/null +++ b/includes/header.php @@ -0,0 +1,66 @@ + + + + + + + <?= $pageTitle ?? 'SR&ED Manager' ?> + + + + + + + + + +
+ SR&ED MANAGER +
+ + diff --git a/index.php b/index.php index 52551ec..784074d 100644 --- a/index.php +++ b/index.php @@ -2,325 +2,57 @@ declare(strict_types=1); require_once __DIR__ . '/db/config.php'; -// Simulate Tenant Context (Hardcoded for demo) $tenant_id = 1; -// Handle Add Employee -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_employee'])) { - $first_name = $_POST['first_name'] ?? ''; - $last_name = $_POST['last_name'] ?? ''; - $email = $_POST['email'] ?? ''; - $position = $_POST['position'] ?? ''; - $start_date = $_POST['start_date'] ?? date('Y-m-d'); - $is_limited = isset($_POST['is_limited']) ? 1 : 0; - $initial_wage = (float)($_POST['initial_wage'] ?? 0); - $team_ids = $_POST['teams'] ?? []; - - if ($first_name && $last_name) { - $user_id = null; - // If not limited, create a user account - if (!$is_limited && $email) { - $stmt = db()->prepare("INSERT IGNORE INTO users (tenant_id, name, email, role) VALUES (?, ?, ?, 'staff')"); - $stmt->execute([$tenant_id, "$first_name $last_name", $email]); - $user_id = (int)db()->lastInsertId(); - if ($user_id === 0) { // Already exists - $stmt = db()->prepare("SELECT id FROM users WHERE email = ?"); - $stmt->execute([$email]); - $user_id = (int)($stmt->fetchColumn() ?: null); - } - } - - $stmt = db()->prepare("INSERT INTO employees (tenant_id, first_name, last_name, email, position, start_date, is_limited, user_id, name) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$tenant_id, $first_name, $last_name, $email, $position, $start_date, $is_limited, $user_id, "$first_name $last_name"]); - $employee_id = (int)db()->lastInsertId(); - - // Initial Wage - if ($initial_wage > 0) { - $stmt = db()->prepare("INSERT INTO employee_wages (tenant_id, employee_id, hourly_rate, effective_date) VALUES (?, ?, ?, ?)"); - $stmt->execute([$tenant_id, $employee_id, $initial_wage, $start_date]); - } - - // Teams - if (!empty($team_ids)) { - foreach ($team_ids as $tid) { - $stmt = db()->prepare("INSERT INTO employee_teams (tenant_id, employee_id, team_id) VALUES (?, ?, ?)"); - $stmt->execute([$tenant_id, $employee_id, $tid]); - } - } - - // Log Activity - $stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)"); - $stmt->execute([$tenant_id, 'Employee Created', "Added employee: $first_name $last_name"]); - - header("Location: index.php?success=employee"); - exit; - } -} - -// Handle Add Team -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_team'])) { - $name = $_POST['name'] ?? ''; - if ($name) { - $stmt = db()->prepare("INSERT INTO teams (tenant_id, name) VALUES (?, ?)"); - $stmt->execute([$tenant_id, $name]); - header("Location: index.php?success=team"); - exit; - } -} - -// Handle Add Wage Adjustment -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_wage'])) { - $employee_id = (int)$_POST['employee_id']; - $rate = (float)$_POST['hourly_rate']; - $effective_date = $_POST['effective_date']; - - if ($employee_id && $rate > 0) { - $stmt = db()->prepare("INSERT INTO employee_wages (tenant_id, employee_id, hourly_rate, effective_date) VALUES (?, ?, ?, ?)"); - $stmt->execute([$tenant_id, $employee_id, $rate, $effective_date]); - header("Location: index.php?success=wage"); - exit; - } -} - -// Handle Add Project -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_project'])) { - $name = $_POST['name'] ?? ''; - $code = $_POST['code'] ?? ''; - $start_date = $_POST['start_date'] ?? date('Y-m-d'); - $owner_id = !empty($_POST['owner_id']) ? (int)$_POST['owner_id'] : null; - $est_completion = !empty($_POST['estimated_completion_date']) ? $_POST['estimated_completion_date'] : null; - $type = $_POST['type'] ?? 'Internal'; - $est_hours = (float)($_POST['estimated_hours'] ?? 0); - - if ($name && $code) { - $stmt = db()->prepare("INSERT INTO projects (tenant_id, name, code, start_date, owner_id, estimated_completion_date, type, estimated_hours) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$tenant_id, $name, $code, $start_date, $owner_id, $est_completion, $type, $est_hours]); - - // Log Activity - $stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)"); - $stmt->execute([$tenant_id, 'Project Created', "Added project: $name ($code)"]); - - header("Location: index.php?success=1"); - exit; - } -} - -// Handle Add Labour -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_labour'])) { - $project_id = (int)($_POST['project_id'] ?? 0); - $employee_id = (int)($_POST['employee_id'] ?? 0); - $entry_date = $_POST['entry_date'] ?? date('Y-m-d'); - $hours = (float)($_POST['hours'] ?? 0); - $labour_type_id = (int)($_POST['labour_type_id'] ?? 0); - $evidence_type_id = (int)($_POST['evidence_type_id'] ?? 0); - $notes = $_POST['notes'] ?? ''; - - if ($project_id && $employee_id && $hours > 0) { - $stmt = db()->prepare("INSERT INTO labour_entries (tenant_id, project_id, employee_id, entry_date, hours, labour_type_id, evidence_type_id, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$tenant_id, $project_id, $employee_id, $entry_date, $hours, $labour_type_id, $evidence_type_id, $notes]); - $labour_entry_id = (int)db()->lastInsertId(); - - // Handle File Uploads - if (!empty($_FILES['attachments']['name'][0])) { - foreach ($_FILES['attachments']['tmp_name'] as $key => $tmp_name) { - $file_name = $_FILES['attachments']['name'][$key]; - $file_size = $_FILES['attachments']['size'][$key]; - $mime_type = $_FILES['attachments']['type'][$key]; - $file_ext = pathinfo($file_name, PATHINFO_EXTENSION); - $new_file_name = uniqid() . '.' . $file_ext; - $file_path = 'uploads/' . $new_file_name; - - if (move_uploaded_file($tmp_name, $file_path)) { - $stmt = db()->prepare("INSERT INTO attachments (tenant_id, entity_type, entity_id, file_name, file_path, file_size, mime_type) VALUES (?, 'labour_entry', ?, ?, ?, ?, ?)"); - $stmt->execute([$tenant_id, $labour_entry_id, $file_name, $file_path, $file_size, $mime_type]); - } - } - } - - // Log Activity - $stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)"); - $stmt->execute([$tenant_id, 'Labour Added', "Logged $hours hours for employee ID $employee_id"]); - - header("Location: index.php?success=labour"); - exit; - } -} - -// Handle Add Expense -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_expense'])) { - $project_id = (int)($_POST['project_id'] ?? 0); - $supplier_id = (int)($_POST['supplier_id'] ?? 0); - $expense_type_id = (int)($_POST['expense_type_id'] ?? 0); - $amount = (float)($_POST['amount'] ?? 0); - $allocation = (float)($_POST['allocation_percent'] ?? 100); - $entry_date = $_POST['entry_date'] ?? date('Y-m-d'); - $notes = $_POST['notes'] ?? ''; - - if ($project_id && $supplier_id && $amount > 0) { - $stmt = db()->prepare("INSERT INTO expenses (tenant_id, project_id, supplier_id, expense_type_id, amount, allocation_percent, entry_date, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$tenant_id, $project_id, $supplier_id, $expense_type_id, $amount, $allocation, $entry_date, $notes]); - $expense_id = (int)db()->lastInsertId(); - - // Handle File Uploads (Centralized Attachments) - if (!empty($_FILES['attachments']['name'][0])) { - foreach ($_FILES['attachments']['tmp_name'] as $key => $tmp_name) { - if (!$_FILES['attachments']['error'][$key]) { - $file_name = $_FILES['attachments']['name'][$key]; - $file_size = $_FILES['attachments']['size'][$key]; - $mime_type = $_FILES['attachments']['type'][$key]; - $file_ext = pathinfo($file_name, PATHINFO_EXTENSION); - $new_file_name = uniqid() . '.' . $file_ext; - $file_path = 'uploads/' . $new_file_name; - - if (move_uploaded_file($tmp_name, $file_path)) { - $stmt = db()->prepare("INSERT INTO attachments (tenant_id, entity_type, entity_id, file_name, file_path, file_size, mime_type) VALUES (?, 'expense', ?, ?, ?, ?, ?)"); - $stmt->execute([$tenant_id, $expense_id, $file_name, $file_path, $file_size, $mime_type]); - } - } - } - } - - // Log Activity - $stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)"); - $stmt->execute([$tenant_id, 'Expense Logged', "Logged \$" . number_format($amount, 2) . " expense for project ID $project_id"]); - - header("Location: index.php?success=expense"); - exit; - } -} - -// Handle Add Supplier -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_supplier'])) { - $name = $_POST['name'] ?? ''; - $type = $_POST['type'] ?? 'supplier'; - $contact = $_POST['contact_info'] ?? ''; - - if ($name) { - $stmt = db()->prepare("INSERT INTO suppliers (tenant_id, name, type, contact_info) VALUES (?, ?, ?, ?)"); - $stmt->execute([$tenant_id, $name, $type, $contact]); - header("Location: index.php?success=supplier"); - exit; - } -} - -// Fetch Data -$search = $_GET['search'] ?? ''; -$status_filter = $_GET['status'] ?? ''; -$date_preset = $_GET['date_preset'] ?? ''; -$start_from = $_GET['start_from'] ?? ''; -$start_to = $_GET['start_to'] ?? ''; - -$query = " +// Fetch Highlights Data +$projectHighlights = db()->prepare(" SELECT p.*, CONCAT(e.first_name, ' ', e.last_name) as owner_name, COALESCE((SELECT SUM(hours) FROM labour_entries WHERE project_id = p.id), 0) as total_hours FROM projects p LEFT JOIN employees e ON p.owner_id = e.id - WHERE p.tenant_id = ?"; -$params = [$tenant_id]; - -if ($search) { - $query .= " AND (p.name LIKE ? OR p.code LIKE ?)"; - $params[] = "%$search%"; - $params[] = "%$search%"; -} - -if ($status_filter) { - $query .= " AND p.status = ?"; - $params[] = $status_filter; -} - -if ($date_preset && $date_preset !== 'custom') { - switch ($date_preset) { - case 'today': - $query .= " AND p.start_date = CURRENT_DATE"; - break; - case 'this_week': - $query .= " AND p.start_date >= DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) DAY)"; - break; - case 'last_week': - $query .= " AND p.start_date >= DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) + 7 DAY) AND p.start_date < DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) DAY)"; - break; - case 'this_month': - $query .= " AND p.start_date >= DATE_FORMAT(CURRENT_DATE, '%Y-%m-01')"; - break; - case 'last_month': - $query .= " AND p.start_date >= DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 MONTH), '%Y-%m-01') AND p.start_date < DATE_FORMAT(CURRENT_DATE, '%Y-%m-01')"; - break; - case 'this_year': - $query .= " AND p.start_date >= DATE_FORMAT(CURRENT_DATE, '%Y-01-01')"; - break; - case 'last_year': - $query .= " AND p.start_date >= DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 YEAR), '%Y-01-01') AND p.start_date < DATE_FORMAT(CURRENT_DATE, '%Y-01-01')"; - break; - } -} elseif ($date_preset === 'custom' && $start_from && $start_to) { - $query .= " AND p.start_date BETWEEN ? AND ?"; - $params[] = $start_from; - $params[] = $start_to; -} - -$query .= " ORDER BY p.created_at DESC"; -$projects = db()->prepare($query); -$projects->execute($params); -$projectList = $projects->fetchAll(); - -$employees = db()->prepare(" - SELECT e.*, - (SELECT hourly_rate FROM employee_wages WHERE employee_id = e.id ORDER BY effective_date DESC LIMIT 1) as current_wage - FROM employees e - WHERE e.tenant_id = ? - ORDER BY e.first_name, e.last_name + WHERE p.tenant_id = ? + ORDER BY p.created_at DESC + LIMIT 5 "); -$employees->execute([$tenant_id]); -$employeeList = $employees->fetchAll(); +$projectHighlights->execute([$tenant_id]); +$projectList = $projectHighlights->fetchAll(); -$teams = db()->prepare("SELECT * FROM teams WHERE tenant_id = ? ORDER BY name"); -$teams->execute([$tenant_id]); -$teamList = $teams->fetchAll(); - -$suppliers = db()->prepare("SELECT * FROM suppliers WHERE tenant_id = ? ORDER BY name"); -$suppliers->execute([$tenant_id]); -$supplierList = $suppliers->fetchAll(); - -$expenseTypes = db()->prepare("SELECT * FROM expense_types WHERE tenant_id = ? ORDER BY name"); -$expenseTypes->execute([$tenant_id]); -$expenseTypeList = $expenseTypes->fetchAll(); - -$labourTypes = db()->prepare("SELECT * FROM labour_types WHERE tenant_id = ? ORDER BY name"); -$labourTypes->execute([$tenant_id]); -$labourTypeList = $labourTypes->fetchAll(); - -$evidenceTypes = db()->prepare("SELECT * FROM evidence_types WHERE tenant_id = ? ORDER BY name"); -$evidenceTypes->execute([$tenant_id]); -$evidenceTypeList = $evidenceTypes->fetchAll(); - -$labourEntries = db()->prepare(" - SELECT le.*, p.name as project_name, e.name as employee_name, lt.name as labour_type, et.name as evidence_type +$labourHighlights = db()->prepare(" + SELECT le.*, p.name as project_name, e.name as employee_name FROM labour_entries le JOIN projects p ON le.project_id = p.id JOIN employees e ON le.employee_id = e.id - LEFT JOIN labour_types lt ON le.labour_type_id = lt.id - LEFT JOIN evidence_types et ON le.evidence_type_id = et.id WHERE le.tenant_id = ? ORDER BY le.entry_date DESC, le.created_at DESC + LIMIT 5 "); -$labourEntries->execute([$tenant_id]); -$labourList = $labourEntries->fetchAll(); +$labourHighlights->execute([$tenant_id]); +$labourList = $labourHighlights->fetchAll(); -$expenseEntries = db()->prepare(" - SELECT e.*, p.name as project_name, s.name as supplier_name, et.name as expense_type +$expenseHighlights = db()->prepare(" + SELECT e.*, p.name as project_name, s.name as supplier_name FROM expenses e JOIN projects p ON e.project_id = p.id JOIN suppliers s ON e.supplier_id = s.id - LEFT JOIN expense_types et ON e.expense_type_id = et.id WHERE e.tenant_id = ? ORDER BY e.entry_date DESC, e.created_at DESC + LIMIT 5 "); -$expenseEntries->execute([$tenant_id]); -$expenseList = $expenseEntries->fetchAll(); +$expenseHighlights->execute([$tenant_id]); +$expenseList = $expenseHighlights->fetchAll(); -// Fetch Chart Data +$employeeHighlights = db()->prepare(" + SELECT e.* + FROM employees e + WHERE e.tenant_id = ? + ORDER BY e.created_at DESC + LIMIT 5 +"); +$employeeHighlights->execute([$tenant_id]); +$employeeList = $employeeHighlights->fetchAll(); + +// Chart Data $chart_days = isset($_GET['chart_days']) ? (int)$_GET['chart_days'] : 7; if (!in_array($chart_days, [7, 14, 30])) $chart_days = 7; @@ -334,7 +66,6 @@ $chartDataQuery = db()->prepare(" $chartDataQuery->execute([$tenant_id, $chart_days]); $rawChartData = $chartDataQuery->fetchAll(PDO::FETCH_KEY_PAIR); -// Fill missing dates $chartLabels = []; $chartValues = []; for ($i = $chart_days - 1; $i >= 0; $i--) { @@ -347,81 +78,9 @@ $activities = db()->prepare("SELECT * FROM activity_log WHERE tenant_id = ? ORDE $activities->execute([$tenant_id]); $activityList = $activities->fetchAll(); -$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking Software'; +$pageTitle = "SR&ED Manager - Dashboard"; +include __DIR__ . '/includes/header.php'; ?> - - - - - - SR&ED Manager - Dashboard - - - - - - - - -
@@ -445,257 +104,122 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking
-
-
- - - - -
-
-
-
- Active Projects -
- - -
-
-
- - - - - - - - - - - - - - - - - $est_hours && $est_hours > 0; - ?> - - - - - - - - - - - -
Project NameOwnerTypeHours (Logged/Est)VarianceStatusActions
No projects found matching the filters.
-
- -
- - / - - - = 0 ? '+' : '') . number_format($variance, 1) ?> - - - -
-
-
-
- -
-
-
- FILTERS - -
-
-
-
- - -
-
- - -
-
- - -
-
-
- - -
-
- - -
-
-
- - Clear All -
-
-
-
-
-
- -
-
- Recent Labour Entries - +
+ +
+
+
+
Recent Projects
+ View All
- +
+ + + + + + + + - - - - - - + + + + + + +
PROJECTSTATUSHOURS
DateEmployeeProjectHoursType / EvidenceActions +
+
+
+
+
+
+ + +
+
+
+
Recent Labour
+ View All +
+
+ + + + + + - - - - - - + + +
DATEEMPLOYEEHOURS
h -
-
-
- - h
+
-
-
- Recent Expenses - + +
+
+
+
Recent Expenses
+ View All
- +
- - - - - - - + + + + - - - - - - + + +
DateSupplierProjectAmountAllocationActions
DATESUPPLIERAMOUNT

$ -
-
-
- % SR&ED -
- - $
+
-
-
- Manage Employees - + +
+
+
+
New Employees
+ View All
- +
- - - - - - + + + + - - - - - + + + @@ -706,7 +230,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking -
+
@@ -716,17 +240,17 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking
NameTeamsWageAccessActions
NAMEPOSITIONJOINED
-
- -
- prepare("SELECT t.name FROM teams t JOIN employee_teams et ON t.id = et.team_id WHERE et.employee_id = ?"); - $e_teams->execute([$e['id']]); - $t_names = $e_teams->fetchAll(PDO::FETCH_COLUMN); - foreach ($t_names as $tn) { - echo '' . htmlspecialchars($tn) . ''; - } - ?> - $/h - -
- - - - + + + + - - + + @@ -739,406 +263,37 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'SR&ED Project Tracking - - - - - - - - - - - - - - - -
TimeActionDetails
TIMEACTIONDETAILS
- - - prepare("SELECT * FROM employee_wages WHERE employee_id = ? ORDER BY effective_date DESC"); - $wages->execute([$e['id']]); - foreach ($wages->fetchAll() as $w): ?> - - - -
RateDate
$/h
-
-
- -
-
-
-
-
-
-
-
-
-
- - - - - + + diff --git a/labour.php b/labour.php new file mode 100644 index 0000000..b5c0e58 --- /dev/null +++ b/labour.php @@ -0,0 +1,208 @@ + 0) { + $stmt = db()->prepare("INSERT INTO labour_entries (tenant_id, project_id, employee_id, entry_date, hours, labour_type_id, evidence_type_id, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$tenant_id, $project_id, $employee_id, $entry_date, $hours, $labour_type_id, $evidence_type_id, $notes]); + $labour_entry_id = (int)db()->lastInsertId(); + + // Handle File Uploads + if (!empty($_FILES['attachments']['name'][0])) { + foreach ($_FILES['attachments']['tmp_name'] as $key => $tmp_name) { + if ($_FILES['attachments']['error'][$key] === UPLOAD_ERR_OK) { + $file_name = $_FILES['attachments']['name'][$key]; + $file_size = $_FILES['attachments']['size'][$key]; + $mime_type = $_FILES['attachments']['type'][$key]; + $file_ext = pathinfo($file_name, PATHINFO_EXTENSION); + $new_file_name = uniqid() . '.' . $file_ext; + $file_path = 'uploads/' . $new_file_name; + + if (!is_dir('uploads')) mkdir('uploads', 0775, true); + if (move_uploaded_file($tmp_name, $file_path)) { + $stmt = db()->prepare("INSERT INTO attachments (tenant_id, entity_type, entity_id, file_name, file_path, file_size, mime_type) VALUES (?, 'labour_entry', ?, ?, ?, ?, ?)"); + $stmt->execute([$tenant_id, $labour_entry_id, $file_name, $file_path, $file_size, $mime_type]); + } + } + } + } + + $stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)"); + $stmt->execute([$tenant_id, 'Labour Added', "Logged $hours hours for employee ID $employee_id"]); + + header("Location: labour.php?success=1"); + exit; + } +} + +// Fetch Data +$labourEntries = db()->prepare(" + SELECT le.*, p.name as project_name, e.name as employee_name, lt.name as labour_type, et.name as evidence_type + FROM labour_entries le + JOIN projects p ON le.project_id = p.id + JOIN employees e ON le.employee_id = e.id + LEFT JOIN labour_types lt ON le.labour_type_id = lt.id + LEFT JOIN evidence_types et ON le.evidence_type_id = et.id + WHERE le.tenant_id = ? + ORDER BY le.entry_date DESC, le.created_at DESC +"); +$labourEntries->execute([$tenant_id]); +$labourList = $labourEntries->fetchAll(); + +$projects = db()->prepare("SELECT id, name FROM projects WHERE tenant_id = ? ORDER BY name"); +$projects->execute([$tenant_id]); +$projectList = $projects->fetchAll(); + +$employees = db()->prepare("SELECT id, first_name, last_name FROM employees WHERE tenant_id = ? ORDER BY first_name, last_name"); +$employees->execute([$tenant_id]); +$employeeList = $employees->fetchAll(); + +$labourTypes = db()->prepare("SELECT * FROM labour_types WHERE tenant_id = ? ORDER BY name"); +$labourTypes->execute([$tenant_id]); +$labourTypeList = $labourTypes->fetchAll(); + +$evidenceTypes = db()->prepare("SELECT * FROM evidence_types WHERE tenant_id = ? ORDER BY name"); +$evidenceTypes->execute([$tenant_id]); +$evidenceTypeList = $evidenceTypes->fetchAll(); + +$pageTitle = "SR&ED Manager - Labour Tracking"; +include __DIR__ . '/includes/header.php'; +?> + +
+
+

Labour Tracking

+ +
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DateEmployeeProjectHoursType / EvidenceNotesActions
No labour entries found.
h +
+
+
+ +
+
+
+
+ + + + + diff --git a/projects.php b/projects.php new file mode 100644 index 0000000..224ae49 --- /dev/null +++ b/projects.php @@ -0,0 +1,275 @@ +prepare("INSERT INTO projects (tenant_id, name, code, start_date, owner_id, estimated_completion_date, type, estimated_hours) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$tenant_id, $name, $code, $start_date, $owner_id, $est_completion, $type, $est_hours]); + + $stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)"); + $stmt->execute([$tenant_id, 'Project Created', "Added project: $name ($code)"]); + + header("Location: projects.php?success=1"); + exit; + } +} + +// Fetch Data +$search = $_GET['search'] ?? ''; +$status_filter = $_GET['status'] ?? ''; +$date_preset = $_GET['date_preset'] ?? ''; +$start_from = $_GET['start_from'] ?? ''; +$start_to = $_GET['start_to'] ?? ''; + +$query = " + SELECT p.*, + CONCAT(e.first_name, ' ', e.last_name) as owner_name, + COALESCE((SELECT SUM(hours) FROM labour_entries WHERE project_id = p.id), 0) as total_hours + FROM projects p + LEFT JOIN employees e ON p.owner_id = e.id + WHERE p.tenant_id = ?"; +$params = [$tenant_id]; + +if ($search) { + $query .= " AND (p.name LIKE ? OR p.code LIKE ?)"; + $params[] = "%$search%"; + $params[] = "%$search%"; +} + +if ($status_filter) { + $query .= " AND p.status = ?"; + $params[] = $status_filter; +} + +if ($date_preset && $date_preset !== 'custom') { + switch ($date_preset) { + case 'today': $query .= " AND p.start_date = CURRENT_DATE"; break; + case 'this_week': $query .= " AND p.start_date >= DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) DAY)"; break; + case 'last_week': $query .= " AND p.start_date >= DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) + 7 DAY) AND p.start_date < DATE_SUB(CURRENT_DATE, INTERVAL WEEKDAY(CURRENT_DATE) DAY)"; break; + case 'this_month': $query .= " AND p.start_date >= DATE_FORMAT(CURRENT_DATE, '%Y-%m-01')"; break; + case 'last_month': $query .= " AND p.start_date >= DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 MONTH), '%Y-%m-01') AND p.start_date < DATE_FORMAT(CURRENT_DATE, '%Y-%m-01')"; break; + case 'this_year': $query .= " AND p.start_date >= DATE_FORMAT(CURRENT_DATE, '%Y-01-01')"; break; + case 'last_year': $query .= " AND p.start_date >= DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 YEAR), '%Y-01-01') AND p.start_date < DATE_FORMAT(CURRENT_DATE, '%Y-01-01')"; break; + } +} elseif ($date_preset === 'custom' && $start_from && $start_to) { + $query .= " AND p.start_date BETWEEN ? AND ?"; + $params[] = $start_from; + $params[] = $start_to; +} + +$query .= " ORDER BY p.created_at DESC"; +$projects = db()->prepare($query); +$projects->execute($params); +$projectList = $projects->fetchAll(); + +$employees = db()->prepare("SELECT id, first_name, last_name FROM employees WHERE tenant_id = ? ORDER BY first_name, last_name"); +$employees->execute([$tenant_id]); +$employeeList = $employees->fetchAll(); + +$pageTitle = "SR&ED Manager - Projects"; +include __DIR__ . '/includes/header.php'; +?> + +
+
+

Projects

+
+ + +
+
+ + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + $est_hours && $est_hours > 0; + ?> + + + + + + + + + + + +
Project NameOwnerTypeHours (Logged/Est)VarianceStatusActions
No projects found.
+
+ +
+ + / + + + = 0 ? '+' : '') . number_format($variance, 1) ?> + + + +
+
+
+
+ + + +
+
+ + + + + + + diff --git a/reports.php b/reports.php index 9230dcd..eaecc88 100644 --- a/reports.php +++ b/reports.php @@ -99,47 +99,9 @@ $labourTypes = db()->prepare("SELECT id, name FROM labour_types WHERE tenant_id $labourTypes->execute([$tenant_id]); $labourTypeList = $labourTypes->fetchAll(); +$pageTitle = "SR&ED Manager - Reports"; +include __DIR__ . '/includes/header.php'; ?> - - - - - - SR&ED Manager - Reports - - - - - - - - -
@@ -365,6 +327,4 @@ $labourTypeList = $labourTypes->fetchAll();
- - - + diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..4e9cc01 --- /dev/null +++ b/settings.php @@ -0,0 +1,246 @@ +prepare("INSERT INTO labour_types (tenant_id, name) VALUES (?, ?)"); + $stmt->execute([$tenant_id, $name]); + } elseif ($action === 'add_evidence_type' && $name) { + $stmt = db()->prepare("INSERT INTO evidence_types (tenant_id, name) VALUES (?, ?)"); + $stmt->execute([$tenant_id, $name]); + } elseif ($action === 'add_expense_type' && $name) { + $stmt = db()->prepare("INSERT INTO expense_types (tenant_id, name) VALUES (?, ?)"); + $stmt->execute([$tenant_id, $name]); + } elseif ($action === 'add_team' && $name) { + $stmt = db()->prepare("INSERT INTO teams (tenant_id, name) VALUES (?, ?)"); + $stmt->execute([$tenant_id, $name]); + } elseif ($action === 'add_supplier' && $name) { + $type = $_POST['type'] ?? 'supplier'; + $contact = $_POST['contact_info'] ?? ''; + $stmt = db()->prepare("INSERT INTO suppliers (tenant_id, name, type, contact_info) VALUES (?, ?, ?, ?)"); + $stmt->execute([$tenant_id, $name, $type, $contact]); + } + + header("Location: settings.php?success=1"); + exit; +} + +// Fetch all datasets +$labourTypes = db()->prepare("SELECT * FROM labour_types WHERE tenant_id = ? ORDER BY name"); +$labourTypes->execute([$tenant_id]); +$labourTypeList = $labourTypes->fetchAll(); + +$evidenceTypes = db()->prepare("SELECT * FROM evidence_types WHERE tenant_id = ? ORDER BY name"); +$evidenceTypes->execute([$tenant_id]); +$evidenceTypeList = $evidenceTypes->fetchAll(); + +$expenseTypes = db()->prepare("SELECT * FROM expense_types WHERE tenant_id = ? ORDER BY name"); +$expenseTypes->execute([$tenant_id]); +$expenseTypeList = $expenseTypes->fetchAll(); + +$teams = db()->prepare("SELECT * FROM teams WHERE tenant_id = ? ORDER BY name"); +$teams->execute([$tenant_id]); +$teamList = $teams->fetchAll(); + +$suppliers = db()->prepare("SELECT * FROM suppliers WHERE tenant_id = ? ORDER BY name"); +$suppliers->execute([$tenant_id]); +$supplierList = $suppliers->fetchAll(); + +$pageTitle = "SR&ED Manager - Settings"; +include __DIR__ . '/includes/header.php'; +?> + +
+
+
+
+

System Settings & Datasets

+ + Dataset updated successfully + +
+ +
+ +
+
+
+ Labour Types + +
+
+ + + + + + + +
Name
+
+
+
+ + +
+
+
+ Evidence Types + +
+
+ + + + + + + +
Name
+
+
+
+ + +
+
+
+ Expense Types + +
+
+ + + + + + + +
Name
+
+
+
+ + +
+
+
+ Teams + +
+
+ + + + + + + +
Name
+
+
+
+ + +
+
+
+ Suppliers & Contractors + +
+
+ + + + + + + + + + + + + + + + + +
NameTypeContact Info
+
+
+
+
+
+
+
+ + + 'addLabourTypeModal', 'title' => 'Add Labour Type', 'action' => 'add_labour_type'], + ['id' => 'addEvidenceTypeModal', 'title' => 'Add Evidence Type', 'action' => 'add_evidence_type'], + ['id' => 'addExpenseTypeModal', 'title' => 'Add Expense Type', 'action' => 'add_expense_type'], + ['id' => 'addTeamModal', 'title' => 'Add Team', 'action' => 'add_team'], +]; +foreach ($modals as $m): +?> + + + + + +