From 203b161e82d6e2d679f826eef7ad9b883bc4b34e Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 4 Mar 2026 18:33:08 -0500 Subject: [PATCH] Add Billing Time Increment Option in Client Ticket Time Detail Report, this option will later be available globally --- agent/reports/client_ticket_time_detail.php | 124 +++++++++++++------- 1 file changed, 80 insertions(+), 44 deletions(-) diff --git a/agent/reports/client_ticket_time_detail.php b/agent/reports/client_ticket_time_detail.php index bfafacf4..802a4df4 100644 --- a/agent/reports/client_ticket_time_detail.php +++ b/agent/reports/client_ticket_time_detail.php @@ -2,7 +2,7 @@ require_once "includes/inc_all_reports.php"; -enforceUserPermission('module_support'); +enforceUserPermission('module_sales'); /** * Convert seconds to "HH:MM:SS" (supports totals > 24h by using hours > 24) @@ -21,10 +21,21 @@ function secondsToHmsString($seconds) { function secondsToDecimalHours($seconds) { $seconds = (int) max(0, $seconds); if ($seconds === 0) return 0.00; - return round($seconds / 3600, 2); } +/** + * Round UP seconds to the nearest increment (in seconds). + */ +function secondsRoundUpToIncrement($seconds, $increment_seconds) { + $seconds = (int) max(0, $seconds); + $increment_seconds = (int) max(1, $increment_seconds); + + if ($seconds === 0) return 0; + + return (int) (ceil($seconds / $increment_seconds) * $increment_seconds); +} + /** * Validate YYYY-MM-DD */ @@ -32,6 +43,16 @@ function isValidDateYmd($s) { return is_string($s) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $s); } +/** + * Billing increment options + * Key = hours (string), Value = increment seconds + */ +$billing_increment_options = [ + '0.1' => 6 * 60, // 6 minutes + '0.25' => 15 * 60, // 15 minutes [DEFAULT] + '0.5' => 30 * 60, // 30 minutes +]; + // Default range: current month $from = isset($_GET['from']) ? $_GET['from'] : date('Y-m-01'); $to = isset($_GET['to']) ? $_GET['to'] : date('Y-m-t'); @@ -45,6 +66,14 @@ $to_dt = $to . " 23:59:59"; $billable_only = (isset($_GET['billable_only']) && (int)$_GET['billable_only'] === 1) ? 1 : 0; +// Billing increment selection (default 0.25) +$billing_increment_key = isset($_GET['billing_increment']) ? (string)$_GET['billing_increment'] : '0.25'; +if (!array_key_exists($billing_increment_key, $billing_increment_options)) { + $billing_increment_key = '0.25'; +} +$billing_increment_seconds = $billing_increment_options[$billing_increment_key]; +$billing_increment_minutes = (int) round($billing_increment_seconds / 60); + // Ticket-level billable flag (same as your original report) $billable_sql = $billable_only ? " AND t.ticket_billable = 1 " : ""; @@ -120,6 +149,21 @@ $result = $stmt->get_result(); +
+ + +
+ +
+ +
+
get_result();
-
- -
+ @@ -157,9 +197,9 @@ $result = $stmt->get_result(); Ticket Total for @@ -178,13 +218,16 @@ $result = $stmt->get_result(); $client_ticket_count = 0; $client_time_seconds = 0; - $client_billed_hours = 0.0; + + // Billable seconds are based on rounding each reply UP to the chosen increment + $client_billable_seconds = 0; $ticket_time_seconds = 0; + $ticket_billable_seconds = 0; $grand_ticket_count = 0; $grand_time_seconds = 0; - $grand_billed_hours = 0.0; + $grand_billable_seconds = 0; $had_rows = false; @@ -203,18 +246,14 @@ $result = $stmt->get_result(); $reply_seconds = (int)$r['reply_time_seconds']; $reply_hms = secondsToHmsString($reply_seconds); + // Rounded-up billable seconds for THIS reply + $reply_billable_seconds = secondsRoundUpToIncrement($reply_seconds, $billing_increment_seconds); + // Reply content: escape for safety, keep line breaks readable $reply_content_raw = $r['reply_content'] ?? ''; - // Remove all HTML tags completely $reply_content_clean = strip_tags($reply_content_raw); - - // Normalize line breaks (convert CRLF/CR to LF) $reply_content_clean = str_replace(["\r\n", "\r"], "\n", $reply_content_clean); - - // Collapse excessive blank lines (more than 2 into 2) $reply_content_clean = preg_replace("/\n{3,}/", "\n\n", $reply_content_clean); - - // Escape safely for output $reply_content_html = nl2br(nullable_htmlentities(trim($reply_content_clean))); // Close out previous client if client changed @@ -222,25 +261,24 @@ $result = $stmt->get_result(); // Close out previous ticket (if any) if ($current_ticket_id !== null) { - $ticket_billed = $printTicketSubtotalRow($current_ticket_label_html, $ticket_time_seconds); - $client_billed_hours += $ticket_billed; - $grand_billed_hours += $ticket_billed; + $printTicketSubtotalRow($current_ticket_label_html, $ticket_time_seconds, $ticket_billable_seconds); $ticket_time_seconds = 0; + $ticket_billable_seconds = 0; $current_ticket_id = null; $current_ticket_label_html = null; echo ''; } - // Client subtotal + // Client subtotal (billable based on sum of rounded replies across all tickets) ?> Total for ( tickets) - + get_result(); // Reset client totals $client_ticket_count = 0; $client_time_seconds = 0; - $client_billed_hours = 0.0; + $client_billable_seconds = 0; } // Client header @@ -269,16 +307,13 @@ $result = $stmt->get_result(); // Ticket changed: close previous ticket subtotal if ($current_ticket_id !== null && $ticket_id !== $current_ticket_id) { - $ticket_billed = $printTicketSubtotalRow($current_ticket_label_html, $ticket_time_seconds); - - // Add billed totals once per ticket - $client_billed_hours += $ticket_billed; - $grand_billed_hours += $ticket_billed; + $printTicketSubtotalRow($current_ticket_label_html, $ticket_time_seconds, $ticket_billable_seconds); echo ''; - // Reset ticket accumulator + // Reset ticket accumulators $ticket_time_seconds = 0; + $ticket_billable_seconds = 0; $current_ticket_id = null; $current_ticket_label_html = null; } @@ -300,7 +335,7 @@ $result = $stmt->get_result(); @@ -311,15 +346,19 @@ $result = $stmt->get_result(); - + get_result(); } else { // Close last ticket subtotal if ($current_ticket_id !== null) { - $ticket_billed = $printTicketSubtotalRow($current_ticket_label_html, $ticket_time_seconds); - $client_billed_hours += $ticket_billed; - $grand_billed_hours += $ticket_billed; - + $printTicketSubtotalRow($current_ticket_label_html, $ticket_time_seconds, $ticket_billable_seconds); echo ''; } @@ -347,7 +383,7 @@ $result = $stmt->get_result(); Total for ( tickets) - + @@ -358,7 +394,7 @@ $result = $stmt->get_result(); Grand Total ( tickets) - + get_result(); This report shows only ticket replies with time worked within the selected date range. - Ticket “Billable (hrs)” totals are calculated by summing reply time per ticket within the range, - then rounding that ticket total up to the nearest 15 minutes (0.25 hours). + “Billable (hrs)” is calculated by rounding each reply up to the nearest minutes ( hours), + then summing those rounded values for ticket/client/grand totals.
Reply content is displayed under each reply timestamp.