Settings > Invoice to start with, or one you enter (your
* tax-deduction rate, say). In miles or kilometres, as set there. Miles count as
* entered on each trip (the whole trip - both ways for a round trip), the
* same as the Trips list and the dashboard. A trip's date opens it on the
* Trips list; its ticket and site show under the purpose.
*/
require_once "includes/inc_report.php";
reportEnforce('mileage');
$period = reportPeriod('this_year');
$client_id = reportClientId();
$agent_id = reportAgentId();
// The rate starts at the mileage rate in Admin > Settings > Invoice
$rate = isset($_GET['rate']) ? round(max(0, floatval(str_replace(',', '.', (string) $_GET['rate']))), 4) : tripRules()['rate'];
$unit = ucfirst(tripUnit());
$unit_one = tripUnitOne();
$where = "trip_archived_at IS NULL AND " . reportRangeSql('trip_date', $period)
. reportClientSql('trip_client_id', $client_id)
. ($agent_id ? " AND trip_user_id = $agent_id" : '');
$trips = [];
$sql = mysqli_query($mysqli, "SELECT trip_id, trip_date, trip_purpose, trip_source, trip_destination, trip_miles, round_trip,
trip_user_id, user_name, user_avatar, trip_client_id, client_name, client_abbreviation,
trip_location_id, location_name, trip_ticket_id, ticket_prefix, ticket_number
FROM trips
LEFT JOIN users ON trip_user_id = user_id
LEFT JOIN clients ON trip_client_id = client_id
LEFT JOIN locations ON location_id = trip_location_id
LEFT JOIN tickets ON ticket_id = trip_ticket_id
WHERE $where
ORDER BY trip_date DESC, trip_id DESC");
while ($row = mysqli_fetch_assoc($sql)) {
$row['miles'] = floatval($row['trip_miles']);
$row['value'] = $rate ? $row['miles'] * $rate : null;
$trips[] = $row;
}
$miles = array_sum(array_column($trips, 'miles'));
$prev_miles = dashboardSum("SELECT SUM(trip_miles) FROM trips WHERE trip_archived_at IS NULL AND " . reportRangeSql('trip_date', $period, true)
. reportClientSql('trip_client_id', $client_id) . ($agent_id ? " AND trip_user_id = $agent_id" : ''));
// Grouped by agent and by client
$by_agent = [];
$by_client = [];
foreach ($trips as $trip) {
$a = intval($trip['trip_user_id']);
$by_agent[$a] = $by_agent[$a] ?? ['user_id' => $a, 'agent' => $trip['user_name'] ?? '', 'avatar' => $trip['user_avatar'] ?? '', 'trips' => 0, 'miles' => 0.0];
$by_agent[$a]['trips']++;
$by_agent[$a]['miles'] += $trip['miles'];
$c = intval($trip['trip_client_id']);
$by_client[$c] = $by_client[$c] ?? ['client_id' => $c, 'client' => $c ? $trip['client_name'] : 'No client (our own)', 'abbr' => $trip['client_abbreviation'] ?? '', 'trips' => 0, 'miles' => 0.0];
$by_client[$c]['trips']++;
$by_client[$c]['miles'] += $trip['miles'];
}
foreach ($by_agent as &$row) {
$row['value'] = $rate ? $row['miles'] * $rate : null;
}
unset($row);
foreach ($by_client as &$row) {
$row['value'] = $rate ? $row['miles'] * $rate : null;
}
unset($row);
usort($by_agent, function ($a, $b) { return $b['miles'] <=> $a['miles']; });
usort($by_client, function ($a, $b) { return $b['miles'] <=> $a['miles']; });
$value_column = ['label' => 'At ' . ($rate ? tripRateText($rate) : 'your rate'), 'type' => 'money', 'csv_label' => 'Amount'];
$trip_columns = [
'trip_date' => ['label' => 'Date', 'type' => 'date', 'class' => 'text-nowrap', 'html' => function ($row) {
// Opens the trip's quick look on the Trips list
return '' . escapeHtml(date('M j, Y', strtotime($row['trip_date']))) . '';
}],
'user_name' => ['label' => 'Agent', 'html' => function ($row) {
return reportAgentCell($row['trip_user_id'], $row['user_name'] ?? '', $row['user_avatar'] ?? '', 'No one');
}],
'client_name' => ['label' => 'Client', 'html' => function ($row) {
return reportClientCell($row['trip_client_id'], $row['client_name'] ?? '', $row['client_abbreviation'] ?? '');
}],
'trip_purpose' => ['label' => 'Purpose', 'class' => 'itflow-report-text', 'html' => function ($row) {
$for = [];
if ($row['ticket_number'] !== null && intval($row['trip_ticket_id'])) {
$for[] = '' . escapeHtml(ticketReference($row)) . '';
}
if ($row['location_name'] !== null && intval($row['trip_location_id'])) {
$for[] = '' . escapeHtml($row['location_name']);
}
return escapeHtml($row['trip_purpose']) . ($for ? '
' . implode(' · ', $for) . '
' : '');
}],
'route' => ['label' => 'From → to', 'class' => 'small text-body-secondary', 'csv' => function ($row) {
return $row['trip_source'] . ' → ' . $row['trip_destination'];
}, 'html' => function ($row) {
return escapeHtml($row['trip_source']) . ' ' . escapeHtml($row['trip_destination']);
}],
'round_trip' => ['label' => 'Round trip', 'csv' => function ($row) {
return intval($row['round_trip']) ? 'Yes' : 'No';
}, 'html' => function ($row) {
return intval($row['round_trip']) ? '' : '';
}, 'class' => 'text-center'],
'miles' => ['label' => $unit, 'type' => 'decimal', 'decimals' => 1],
];
if ($rate) {
$trip_columns['value'] = $value_column;
}
// The CSV has the site and ticket in columns of their own
$trip_csv_columns = array_slice($trip_columns, 0, 4, true) + [
'location_name' => ['label' => 'Site', 'csv' => function ($row) {
return intval($row['trip_location_id']) ? (string) $row['location_name'] : '';
}],
'ticket' => ['label' => 'Ticket', 'csv' => function ($row) {
return $row['ticket_number'] !== null && intval($row['trip_ticket_id']) ? ticketReference($row) : '';
}],
] + array_slice($trip_columns, 4, null, true);
$agent_columns = [
'agent' => ['label' => 'Agent', 'html' => function ($row) {
return reportAgentCell($row['user_id'], $row['agent'], $row['avatar'], 'No one');
}],
'trips' => ['label' => 'Trips', 'type' => 'count'],
'miles' => ['label' => $unit, 'type' => 'decimal', 'decimals' => 1],
];
$client_columns = [
'client' => ['label' => 'Client', 'html' => function ($row) {
return $row['client_id'] ? reportClientCell($row['client_id'], $row['client'], $row['abbr']) : 'No client (our own)';
}],
'trips' => ['label' => 'Trips', 'type' => 'count'],
'miles' => ['label' => $unit, 'type' => 'decimal', 'decimals' => 1],
];
if ($rate) {
$agent_columns['value'] = $value_column;
$client_columns['value'] = $value_column;
}
$totals = ['trips' => count($trips), 'miles' => $miles, 'value' => $rate ? $miles * $rate : null];
$name = 'Mileage ' . $period['start'] . ' to ' . $period['end'];
if (reportExporting('agents')) {
reportCsv("$name by agent", $agent_columns, $by_agent, ['agent' => 'Total'] + $totals);
}
if (reportExporting('clients')) {
reportCsv("$name by client", $client_columns, $by_client, ['client' => 'Total'] + $totals);
}
if (reportExporting()) {
reportCsv($name, $trip_csv_columns, $trips, ['trip_date' => 'Total', 'miles' => $miles, 'value' => $totals['value']]);
}
$buckets = reportBuckets($period);
$miles_series = reportSeries("SELECT " . reportBucketSql('trip_date', $buckets['unit']) . " AS b, SUM(trip_miles) AS v FROM trips WHERE $where GROUP BY b", $buckets['keys']);
require_once "includes/inc_report_layout.php";
$rate_control = '';
?>
$period,
'years' => reportPastYears("SELECT DISTINCT YEAR(trip_date) AS y FROM trips"),
'client' => $client_id,
'agent' => $agent_id,
'controls' => $rate_control,
'export' => ['trips' => 'Every trip', 'agents' => 'By agent', 'clients' => 'By client'],
]); ?>
= reportStats([
['label' => $unit, 'value' => number_format($miles, 1), 'sub' => reportChangeHtml($miles, $prev_miles, 'up', $period['compare'])],
['label' => 'Trips', 'value' => reportCount(count($trips)), 'sub' => count($trips) ? reportCount(count(array_filter(array_column($trips, 'round_trip')))) . '
round trips' : ''],
['label' => 'Average trip', 'value' => count($trips) ? number_format($miles / count($trips), 1) . ' ' . tripUnit(true) : '-', 'sub' => '
' . $unit . ' a trip'],
['label' => $rate ? 'At ' . tripRateText($rate) : 'Reimbursement', 'value' => $rate ? reportMoney($miles * $rate) : '-',
'sub' => $rate ? '
' . $unit . ' × your rate' : '
Enter a rate a ' . $unit_one . ' above'],
]) ?>
= reportSectionOpen($unit . ' driven', escapeHtml($period['range'])) ?>
= reportChart([
'type' => 'bar',
'labels' => $buckets['labels'],
'series' => [['label' => $unit, 'data' => $miles_series, 'tone' => 'accent']],
'format' => 'decimal',
'legend' => false,
], 'sm', $unit . ' driven over the period') ?>
= reportSectionOpen('By agent', '', 'h-100 mb-0') ?>
= reportTable($agent_columns, $by_agent, ['empty' => 'No trips in this period.', 'totals' => ['agent' => 'Total'] + $totals]) ?>
= reportSectionOpen('By client', '', 'h-100 mb-0') ?>
= reportTable($client_columns, $by_client, ['empty' => 'No trips in this period.', 'totals' => ['client' => 'Total'] + $totals]) ?>
= reportSectionOpen('Every trip', reportCount(count($trips)) . ' trip' . (count($trips) === 1 ? '' : 's'), 'itflow-report-long') ?>
= reportTable($trip_columns, $trips, ['empty' => 'No trips in this period.', 'limit' => 250, 'export_table' => 'trips', 'totals' => ['trip_date' => 'Total', 'miles' => $miles, 'value' => $totals['value']]]) ?>
= $unit ?> count as entered on each trip - the whole trip, both ways for a round trip. Archived trips are left out. The rate starts at the mileage rate= $session_is_admin ? ' in
Settings' : '' ?>, and paying agents back is Pay Mileage on
Trips.