From 2029781d8cbb96e4fde6b685b6b9b4c9d65a738d Mon Sep 17 00:00:00 2001 From: Kendrick Chan Date: Tue, 8 May 2018 03:56:20 +0800 Subject: [PATCH] Initial commit for consolidated dispatch time and service reports #99 --- src/Controller/ReportController.php | 439 +++++++++++++++++++++++ templates/report/dispatch-time.html.twig | 125 +++++++ templates/report/range-picker.html.twig | 20 ++ templates/report/service.html.twig | 125 +++++++ 4 files changed, 709 insertions(+) create mode 100644 src/Controller/ReportController.php create mode 100644 templates/report/dispatch-time.html.twig create mode 100644 templates/report/range-picker.html.twig create mode 100644 templates/report/service.html.twig diff --git a/src/Controller/ReportController.php b/src/Controller/ReportController.php new file mode 100644 index 00000000..8bd57e8c --- /dev/null +++ b/src/Controller/ReportController.php @@ -0,0 +1,439 @@ +denyAccessUnlessGranted('report.view', null, 'No access.'); + + $params = $this->initParameters('report_dispatch_time'); + + return $this->render('report/dispatch-time.html.twig', $params); + } + + public function dispatchTimeSubmit(Request $req) + { + // initialize error list + $error_array = []; + + $start_date = $req->request->get('start_date'); + $end_date = $req->request->get('end_date'); + + // validate dates + $dates = $this->validateDateRange($start_date, $end_date, $error_array); + + // check if any errors were found + if (!empty($error_array)) + { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + else + { + // return response + return $this->json([ + 'success' => true, + 'url' => $this->generateUrl('report_dispatch_time_generate', [ + 'start_date' => $dates['start'], + 'end_date' => $dates['end'] + ]) + ]); + } + } + + public function dispatchTimeGenerate(Request $req, $start_date, $end_date) + { + // get dates + $date_format = "Y-m-d"; + $sd = DateTime::createFromFormat($date_format, $start_date); + $ed = DateTime::createFromFormat($date_format, $end_date); + + // format filename + $filename = "consolidated-dispatch-time-" . $sd->format('Ymd') . '-' . $ed->format('Ymd') . ".csv"; + + // build response + $response = new StreamedResponse(); + + $response->setCallback(function() use ($sd, $ed) { + $handle = fopen('php://output', 'w+'); + + // make table headers + $headers = ['Agent']; + + // add date interval headers + $period = $this->generateDateHeaders($sd, $ed, $headers); + + $headers[] = 'Grand Total'; + + // set table headers + fputcsv($handle, $headers); + + $em = $this->getDoctrine()->getManager(); + $conn = $em->getConnection(); + + // get all users + $sql = "SELECT * FROM user ORDER BY first_name ASC"; + $stmt = $conn->prepare($sql); + $stmt->execute(); + $users = $stmt->fetchAll(); + + // initiate + $tallies = []; + $jo_counts = []; + $averages = []; + $col_jo_counts = []; + $col_totals = []; + + // get all job orders + $sql = "SELECT * FROM job_order WHERE date_schedule IS NOT NULL AND date_assign IS NOT NULL AND date_assign BETWEEN ? AND ?"; + $stmt = $conn->prepare($sql); + $stmt->bindValue(1, $sd->format("Y-m-d")); + $stmt->bindValue(2, $ed->format("Y-m-d")); + $stmt->execute(); + $job_orders = $stmt->fetchAll(); + + foreach ($job_orders as $job_order) + { + $date_schedule = DateTime::createFromFormat("Y-m-d H:i:s", $job_order['date_schedule']); + $date_assign = DateTime::createFromFormat("Y-m-d H:i:s", $job_order['date_assign']); + $date_assign_formatted = $date_assign->format("Y-m-d"); + + // get difference between times + $date_schedule_ts = $date_schedule->getTimestamp(); + $date_assign_ts = $date_assign->getTimestamp(); + $diff_mins = ($date_assign_ts - $date_schedule_ts) / 60; + + // add to minutes tally + if (!isset($tallies[$job_order['assign_user_id']][$date_assign_formatted])) + $tallies[$job_order['assign_user_id']][$date_assign_formatted] = $diff_mins; + else + $tallies[$job_order['assign_user_id']][$date_assign_formatted] += $diff_mins; + + // append to jo counter + if (!isset($jo_counts[$job_order['assign_user_id']][$date_assign_formatted])) + $jo_counts[$job_order['assign_user_id']][$date_assign_formatted] = 1; + else + $jo_counts[$job_order['assign_user_id']][$date_assign_formatted]++; + } + + // append to csv + foreach ($users as $user) + { + // check if this user is an agent + $sql = "SELECT * FROM user_role WHERE user_id = ? AND (role_id = ? OR role_id = ?)"; + $stmt = $conn->prepare($sql); + $stmt->bindValue(1, $user['id']); + $stmt->bindValue(2, "ROLE_AGENT_TIER_1"); + $stmt->bindValue(3, "ROLE_AGENT_TIER_2"); + $stmt->execute(); + $roles = $stmt->fetchAll(); + + // only include if user is agent + if (count($roles) > 0) + { + $total_mins = 0; + $total_jos = 0; + + $full_name = implode(" ", [ + $user['first_name'], + $user['last_name'] + ]); + + $rowdata = [$full_name]; + + foreach ($period as $date) + { + $date_formatted = $date->format("Y-m-d"); + + // get average per date for this user + if (isset($tallies[$user['id']][$date_formatted]) && isset($jo_counts[$user['id']][$date_formatted])) + { + $date_tally = $tallies[$user['id']][$date_formatted]; + $jo_tally = $jo_counts[$user['id']][$date_formatted]; + $rowdata[] = $date_tally / $jo_tally; + } + else + { + $date_tally = 0; + $jo_tally = 0; + $rowdata[] = 0; + } + + + // add to totals + $total_mins += $date_tally; + $total_jos += $jo_tally; + + // add to column totals + if (!isset($col_totals[$date_formatted])) + $col_totals[$date_formatted] = $date_tally; + else + $col_totals[$date_formatted] += $date_tally; + + // append to column jo counter + if (!isset($col_jo_counts[$date_formatted])) + $col_jo_counts[$date_formatted] = $jo_tally; + else + $col_jo_counts[$date_formatted] += $jo_tally; + } + + // add total to rowdata + $rowdata[] = $total_jos > 0 ? $total_mins / $total_jos : 0; + + fputcsv($handle, $rowdata); + } + } + + // include column totals row + $totals_row = ['GRAND TOTAL']; + + foreach ($period as $date) + { + $date_formatted = $date->format("Y-m-d"); + $totals_row[] = $col_jo_counts[$date_formatted] > 0 ? $col_totals[$date_formatted] / $col_jo_counts[$date_formatted] : 0; + } + + // add overall total + $overall_jo_total = count($job_orders); + $totals_row[] = $overall_jo_total > 0 ? array_sum($col_totals) / $overall_jo_total : 0; + + fputcsv($handle, $totals_row); + + fclose($handle); + }); + + // add response headers + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); + $response->headers->set('Content-Disposition', 'attachment; filename="' . $filename); + + // send + $response->send(); + } + + public function serviceForm(Request $req) + { + $this->denyAccessUnlessGranted('report.view', null, 'No access.'); + + $params = $this->initParameters('report_service'); + + return $this->render('report/service.html.twig', $params); + } + + public function serviceSubmit(Request $req) + { + // initialize error list + $error_array = []; + + $start_date = $req->request->get('start_date'); + $end_date = $req->request->get('end_date'); + + // validate dates + $dates = $this->validateDateRange($start_date, $end_date, $error_array); + + // check if any errors were found + if (!empty($error_array)) + { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + else + { + // return response + return $this->json([ + 'success' => true, + 'url' => $this->generateUrl('report_service_generate', [ + 'start_date' => $dates['start'], + 'end_date' => $dates['end'] + ]) + ]); + } + } + + public function serviceGenerate(Request $req, $start_date, $end_date) + { + // get dates + $date_format = "Y-m-d"; + $sd = DateTime::createFromFormat($date_format, $start_date); + $ed = DateTime::createFromFormat($date_format, $end_date); + + // format filename + $filename = "service-" . $sd->format('Ymd') . '-' . $ed->format('Ymd') . ".csv"; + + // build response + $response = new StreamedResponse(); + + $response->setCallback(function() use ($sd, $ed) { + $handle = fopen('php://output', 'w+'); + + // make table headers + $headers = [ + 'Hub', + 'Branch' + ]; + + // add date interval headers + $period = $this->generateDateHeaders($sd, $ed, $headers); + + $headers[] = 'GRAND TOTAL'; + + // set table headers + fputcsv($handle, $headers); + + $em = $this->getDoctrine()->getManager(); + $conn = $em->getConnection(); + + // get all hubs + $sql = "SELECT * FROM hub ORDER BY name ASC"; + $stmt = $conn->prepare($sql); + $stmt->execute(); + $hubs = $stmt->fetchAll(); + + $tallies = []; + $col_totals = []; + + // get all job orders + $sql = "SELECT * FROM job_order WHERE date_schedule BETWEEN ? AND ?"; + $stmt = $conn->prepare($sql); + $stmt->bindValue(1, $sd->format("Y-m-d")); + $stmt->bindValue(2, $ed->format("Y-m-d")); + $stmt->execute(); + $job_orders = $stmt->fetchAll(); + + foreach ($job_orders as $job_order) + { + $date_sched = DateTime::createFromFormat("Y-m-d H:i:s", $job_order['date_schedule']); + $date_sched_formatted = $date_sched->format("Y-m-d"); + + if (!isset($tallies[$job_order['hub_id']][$date_sched_formatted])) + $tallies[$job_order['hub_id']][$date_sched_formatted] = 1; + else + $tallies[$job_order['hub_id']][$date_sched_formatted]++; + } + + // append to csv + foreach ($hubs as $hub) + { + $total = 0; + + $rowdata = [ + $hub['name'], + $hub['branch'] + ]; + + foreach ($period as $date) + { + $date_formatted = $date->format("Y-m-d"); + $date_tally = $tallies[$hub['id']][$date_formatted] ?? 0; + $rowdata[] = $date_tally; + $total += $date_tally; + + // add to column totals + if (!isset($col_totals[$date_formatted])) + $col_totals[$date_formatted] = $date_tally; + else + $col_totals[$date_formatted] += $date_tally; + } + + // add total to rowdata + $rowdata[] = $total; + + fputcsv($handle, $rowdata); + } + + // include column totals row + $totals_row = ['GRAND TOTAL', '']; + + foreach ($period as $date) + $totals_row[] = $col_totals[$date->format("Y-m-d")]; + + // add overall total + $totals_row[] = array_sum($col_totals); + + fputcsv($handle, $totals_row); + + fclose($handle); + }); + + // add response headers + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); + $response->headers->set('Content-Disposition', 'attachment; filename="' . $filename); + + // send + $response->send(); + } + + protected function validateDateRange($start_date, $end_date, &$error_array) + { + $date_format = "d M Y"; + $error = false; + + if (empty($start_date)) + { + $error = true; + $error_array['start_date'] = 'No start date specified.'; + } + else + { + $sd = DateTime::createFromFormat($date_format, $start_date); + + if (!$sd || $sd->format($date_format) != $start_date) + { + $error = true; + $error_array['start_date'] = 'Invalid start date specified.'; + } + } + + if (empty($end_date)) + { + $error = true; + $error_array['end_date'] = 'No end date specified.'; + } + else + { + $ed = DateTime::createFromFormat($date_format, $end_date); + + if (!$ed || $ed->format($date_format) != $end_date) + { + $error = true; + $error_array['end_date'] = 'Invalid end date specified.'; + } + } + + return $error ? false : [ + 'start' => $sd->format("Y-m-d"), + 'end' => $ed->format("Y-m-d") + ]; + } + + protected function generateDateHeaders($start_date, $end_date, &$headers) + { + $interval = DateInterval::createFromDateString('1 day'); + $period = new DatePeriod($start_date, $interval, $end_date); + + foreach ($period as $date) + $headers[] = $date->format('M j, Y'); + + return $period; + } +} diff --git a/templates/report/dispatch-time.html.twig b/templates/report/dispatch-time.html.twig new file mode 100644 index 00000000..bdce5711 --- /dev/null +++ b/templates/report/dispatch-time.html.twig @@ -0,0 +1,125 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Reports

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ Consolidated Dispatch Time +

+
+
+
+
+ + {% include 'report/range-picker.html.twig' %} + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/report/range-picker.html.twig b/templates/report/range-picker.html.twig new file mode 100644 index 00000000..b7559882 --- /dev/null +++ b/templates/report/range-picker.html.twig @@ -0,0 +1,20 @@ +
+
+
+

+ Date Range +

+
+
+
+ + + to + + +
+ + +
+
+
\ No newline at end of file diff --git a/templates/report/service.html.twig b/templates/report/service.html.twig new file mode 100644 index 00000000..b9203188 --- /dev/null +++ b/templates/report/service.html.twig @@ -0,0 +1,125 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Reports

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ Service +

+
+
+
+
+ + {% include 'report/range-picker.html.twig' %} + +
+
+
+
+ +
+
+
+
+
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %}