'Replace after 3 years', 4 => 'Replace after 4 years', 5 => 'Replace after 5 years', 6 => 'Replace after 6 years', 7 => 'Replace after 7 years']; $years = isset($year_choices[intval($_GET['years'] ?? 0)]) ? intval($_GET['years']) : 5; $limit_days = $years * 365.25; $client_id = reportClientId(); $type = trim((string) ($_GET['type'] ?? '')); $assets = reportLifecycleAssets($client_id, $type); $empty = ['assets' => 0, 'dated' => 0, 'age_total' => 0, 'old' => 0, 'undated' => 0, 'expired' => 0, 'ending' => 0, 'os' => 0]; $totals = $empty; $by_type = []; $by_client = []; $candidates = []; $os_names = []; foreach ($assets as &$asset) { $hardware = $asset['asset_type'] !== 'Virtual Machine'; $asset['old'] = $asset['age_days'] !== null && $asset['age_days'] >= $limit_days; $asset['expired'] = $asset['warranty'] && $asset['warranty']['days'] < 0; $asset['ending'] = $asset['warranty'] && $asset['warranty']['days'] >= 0 && $asset['warranty']['days'] <= 90; $t = $asset['asset_type'] ?: 'Other'; $c = intval($asset['asset_client_id']); $by_type[$t] = $by_type[$t] ?? ['type' => $t] + $empty; $by_client[$c] = $by_client[$c] ?? ['client_id' => $c, 'client' => $asset['client_name'], 'abbr' => $asset['client_abbreviation']] + $empty; foreach ([&$totals, &$by_type[$t], &$by_client[$c]] as &$group) { $group['assets']++; if ($asset['age_days'] !== null) { $group['dated']++; $group['age_total'] += $asset['age_days']; } elseif ($hardware) { $group['undated']++; } $group['old'] += $asset['old'] ? 1 : 0; $group['expired'] += $asset['expired'] ? 1 : 0; $group['ending'] += $asset['ending'] ? 1 : 0; $group['os'] += $asset['os_eol'] !== '' ? 1 : 0; } unset($group); if ($asset['os_eol'] !== '') { $os_names[$asset['os_eol']] = ($os_names[$asset['os_eol']] ?? 0) + 1; } if ($asset['old'] || $asset['os_eol'] !== '') { $candidates[] = $asset; } } unset($asset); $average = function ($group) { return $group['dated'] ? $group['age_total'] / $group['dated'] : null; }; foreach ([&$by_type, &$by_client] as &$groups) { foreach ($groups as &$group) { $group['average'] = $average($group); } unset($group); } unset($groups); $totals['average'] = $average($totals); uasort($by_type, function ($a, $b) { return $b['assets'] <=> $a['assets']; }); uasort($by_client, function ($a, $b) { return $b['old'] + $b['os'] <=> $a['old'] + $a['os'] ?: $b['assets'] <=> $a['assets']; }); usort($candidates, function ($a, $b) { return ($b['age_days'] ?? -1) <=> ($a['age_days'] ?? -1); }); arsort($os_names); $count_cell = function ($key, $tone) { return function ($row) use ($key, $tone) { return $row[$key] ? '' . reportCount($row[$key]) . '' : '–'; }; }; $figure_columns = [ 'assets' => ['label' => 'In use', 'type' => 'count'], 'average' => ['label' => 'Average age', 'type' => 'decimal', 'csv_label' => 'Average age (years)', 'csv' => function ($row) { return $row['average'] === null ? '' : number_format($row['average'] / 365.25, 1, '.', ''); }, 'html' => function ($row) { return $row['average'] === null ? '–' : reportYears($row['average']); }], 'old' => ['label' => "Over $years years", 'type' => 'count', 'html' => $count_cell('old', 'danger')], 'expired' => ['label' => 'Out of warranty', 'type' => 'count', 'html' => $count_cell('expired', 'danger')], 'ending' => ['label' => 'Ends in 90 days', 'type' => 'count', 'title' => 'The warranty ends in the next 90 days', 'html' => $count_cell('ending', 'warning')], 'os' => ['label' => 'Unsupported OS', 'type' => 'count', 'html' => $count_cell('os', 'danger')], 'undated' => ['label' => 'No date', 'type' => 'count', 'title' => 'Hardware with neither a purchase nor an install date', 'html' => function ($row) { return $row['undated'] ? '' . reportCount($row['undated']) . '' : '–'; }], ]; $type_columns = ['type' => ['label' => 'Type', 'html' => function ($row) { return '' . '' . escapeHtml($row['type']) . ''; }]] + $figure_columns; $client_columns = ['client' => ['label' => 'Client', 'html' => function ($row) { return reportClientCell($row['client_id'], $row['client'], $row['abbr'], 'assets.php'); }]] + $figure_columns; $warranty_pill = function ($warranty) { if (!$warranty) { return 'No date'; } $tone = $warranty['tone']; return '' . escapeHtml($warranty['short']) . ''; }; $candidate_columns = [ 'asset_name' => ['label' => 'Asset', 'html' => function ($row) { return reportAssetCell($row); }], 'client_name' => ['label' => 'Client', 'html' => function ($row) { return reportClientCell($row['asset_client_id'], $row['client_name'], $row['client_abbreviation'], 'assets.php'); }], 'asset_type' => ['label' => 'Type'], 'model' => ['label' => 'Make and model', 'csv' => function ($row) { return trim($row['asset_make'] . ' ' . $row['asset_model']); }, 'html' => function ($row) { return '' . escapeHtml(trim($row['asset_make'] . ' ' . $row['asset_model'])) . ''; }], 'asset_os' => ['label' => 'Operating system', 'html' => function ($row) { if ($row['os_eol'] === '') { return '' . escapeHtml($row['asset_os']) . ''; } return '' . escapeHtml($row['asset_os']) . ' '; }], 'age_days' => ['label' => 'Age', 'type' => 'decimal', 'csv_label' => 'Age (years)', 'csv' => function ($row) { return $row['age_days'] === null ? '' : number_format($row['age_days'] / 365.25, 1, '.', ''); }, 'html' => function ($row) { return $row['age_days'] === null ? '–' : '' . reportYears($row['age_days']) . ''; }], 'warranty' => ['label' => 'Warranty', 'sort' => false, 'csv' => function ($row) { return $row['warranty'] ? $row['warranty']['date'] : ''; }, 'csv_label' => 'Warranty ends', 'html' => function ($row) use ($warranty_pill) { return $warranty_pill($row['warranty']); }], 'contact_name' => ['label' => 'Used by', 'html' => function ($row) { return $row['contact_name'] ? '' . escapeHtml($row['contact_name']) . '' : '-'; }], 'location_name' => ['label' => 'Location', 'screen' => false], 'asset_status' => ['label' => 'Status', 'screen' => false], ]; if (reportExporting('types')) { reportCsv('Assets by Type', $type_columns, array_values($by_type), ['type' => 'Total'] + $totals); } if (reportExporting('clients')) { reportCsv('Assets by Client', $client_columns, array_values($by_client), ['client' => 'Total'] + $totals); } if (reportExporting()) { reportCsv('Replacement Candidates', $candidate_columns, $candidates); } require_once "includes/inc_report_layout.php"; // The age profile - a bar a year, red from the replacement age $age_labels = []; $age_counts = []; $age_tones = []; $top = max($years + 2, 8); for ($y = 0; $y < $top; $y++) { $age_labels[] = $y === $top - 1 ? "$y+ yrs" : "$y-" . ($y + 1) . ' yrs'; $age_counts[$y] = 0; $age_tones[] = $y >= $years ? 'danger' : ($y === $years - 1 ? 'warning' : 'accent'); } foreach ($assets as $asset) { if ($asset['age_days'] !== null) { $age_counts[min($top - 1, intdiv($asset['age_days'], 365))]++; } } if ($totals['undated']) { $age_labels[] = 'No date'; $age_counts[] = $totals['undated']; $age_tones[] = 'muted'; } // Coming due - hardware reaching the replacement age, by quarter for the next two years $quarters = []; $cursor = new DateTime('first day of this month'); $cursor->setDate(intval($cursor->format('Y')), intval((ceil($cursor->format('n') / 3) - 1) * 3 + 1), 1); for ($q = 0; $q < 8; $q++) { $key = $cursor->format('Y') . '-Q' . ceil($cursor->format('n') / 3); $quarters[$key] = ['label' => 'Q' . ceil($cursor->format('n') / 3) . ' ' . $cursor->format('y'), 'count' => 0]; $cursor->modify('+3 months'); } foreach ($assets as $asset) { if ($asset['age_date'] !== null && !$asset['old']) { $due = new DateTime($asset['age_date']); $due->modify("+$years years"); $key = $due->format('Y') . '-Q' . ceil($due->format('n') / 3); if (isset($quarters[$key])) { $quarters[$key]['count']++; } } } $coming = array_sum(array_column($quarters, 'count')); $warranty_counts = ['Covered' => 0, 'Ends within 90 days' => 0, 'Expired' => 0, 'No date' => 0]; foreach ($assets as $asset) { if ($asset['asset_type'] === 'Virtual Machine') { continue; } $warranty_counts[!$asset['warranty'] ? 'No date' : ($asset['expired'] ? 'Expired' : ($asset['ending'] ? 'Ends within 90 days' : 'Covered'))]++; } $type_choices = ['' => 'Every type']; $sql = mysqli_query($mysqli, "SELECT DISTINCT asset_type FROM assets WHERE asset_archived_at IS NULL AND asset_type != '' " . reportClientSql('asset_client_id', 0) . " ORDER BY asset_type"); while ($row = mysqli_fetch_assoc($sql)) { $type_choices[$row['asset_type']] = $row['asset_type']; } if ($type !== '' && !isset($type_choices[$type])) { $type_choices[$type] = $type; } $dated_share = $totals['dated'] ? reportShare($totals['old'], $totals['dated'], 0) : null; ?>