24h by using hours > 24) */ function secondsToHmsString($seconds) { $seconds = (int) max(0, $seconds); $hours = intdiv($seconds, 3600); $minutes = intdiv($seconds % 3600, 60); $secs = $seconds % 60; return sprintf('%02d:%02d:%02d', $hours, $minutes, $secs); } /** * 15-minute round up, return decimal hours in 0.25 increments * Examples: * 1 min => 0.25 * 16 min => 0.50 * 61 min => 1.25 */ function secondsToQuarterHourDecimal($seconds) { $seconds = (int) max(0, $seconds); if ($seconds === 0) return 0.00; $quarters = (int) ceil($seconds / 900); // 900 seconds = 15 minutes return $quarters * 0.25; } $year = isset($_GET['year']) ? (int) $_GET['year'] : (int) date('Y'); $month = isset($_GET['month']) ? (int) $_GET['month'] : (int) date('m'); if ($month < 1 || $month > 12) $month = (int) date('m'); $billable_only = (isset($_GET['billable_only']) && (int) $_GET['billable_only'] === 1) ? 1 : 0; // Used for Year dropdown $sql_ticket_years = mysqli_query($mysqli, "SELECT DISTINCT YEAR(ticket_created_at) AS ticket_year FROM tickets ORDER BY ticket_year DESC"); // Billable filter (adjust field name if yours differs) $billable_sql = $billable_only ? " AND t.ticket_billable = 1 " : ""; /** * IMPORTANT: * This sums time worked ONLY for replies within the selected month/year * by filtering on tr.ticket_reply_created_at. * If your column name differs, replace ticket_reply_created_at accordingly. */ $stmt = $mysqli->prepare(" SELECT c.client_id, c.client_name, t.ticket_id, t.ticket_prefix, t.ticket_number, t.ticket_subject, SEC_TO_TIME(COALESCE(SUM(TIME_TO_SEC(tr.ticket_reply_time_worked)), 0)) AS ticket_time_hms, COALESCE(SUM(TIME_TO_SEC(tr.ticket_reply_time_worked)), 0) AS ticket_time_seconds FROM tickets t INNER JOIN clients c ON c.client_id = t.ticket_client_id LEFT JOIN ticket_replies tr ON tr.ticket_reply_ticket_id = t.ticket_id AND tr.ticket_reply_time_worked IS NOT NULL AND YEAR(tr.ticket_reply_created_at) = ? AND MONTH(tr.ticket_reply_created_at) = ? WHERE c.client_archived_at IS NULL $billable_sql GROUP BY t.ticket_id HAVING ticket_time_seconds > 0 ORDER BY c.client_name ASC, t.ticket_number ASC, t.ticket_id ASC "); $stmt->bind_param("ii", $year, $month); $stmt->execute(); $result = $stmt->get_result(); ?>

Ticket By Client () Billable Only

>
Ticket Time Worked Billable (hrs)
Total for ( tickets)
No tickets with time worked found for this month.
Total for ( tickets)
Grand Total ( tickets)
Billed hours are calculated per ticket by rounding that ticket’s worked time up to the nearest 15 minutes (0.25 hours).