447 lines
14 KiB
PHP
447 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Exports\ZenovaReportExport;
|
|
use App\Helpers\ZenovaReportAccess;
|
|
use App\Http\Requests\LambdaRequest;
|
|
use Illuminate\Http\Request;
|
|
use Maatwebsite\Excel\Facades\Excel;
|
|
|
|
class ZenovaReportController extends Controller
|
|
{
|
|
public function __construct()
|
|
{
|
|
$this->middleware('auth');
|
|
$this->middleware(function ($request, $next) {
|
|
if (!ZenovaReportAccess::canView()) {
|
|
abort(403);
|
|
}
|
|
|
|
return $next($request);
|
|
});
|
|
$this->_service = 'reports';
|
|
$this->_module = 'zenova_reports';
|
|
$this->_viewname = 'zenovareport';
|
|
$this->_title = 'Custom Dashboard';
|
|
$this->_base_url = '/zenovareport';
|
|
}
|
|
|
|
public function list(Request $request)
|
|
{
|
|
return redirect('/zenovareport/dashboard');
|
|
}
|
|
|
|
public function dashboard(Request $request)
|
|
{
|
|
$level = $this->level($request);
|
|
$month = $this->month($request);
|
|
$built = $this->build($level, $month);
|
|
|
|
return view('zenovareport.dashboard', [
|
|
'title' => $this->_title,
|
|
'level' => $level,
|
|
'month' => $month,
|
|
'months' => $this->months(),
|
|
'groups' => $built['groups'],
|
|
'body' => $built['body'],
|
|
'totals' => $built['totals'],
|
|
'error' => $built['error'],
|
|
]);
|
|
}
|
|
|
|
public function export(Request $request)
|
|
{
|
|
$level = $this->level($request);
|
|
$month = $this->month($request);
|
|
$built = $this->build($level, $month);
|
|
|
|
if ($built['error']) {
|
|
return redirect('/zenovareport/dashboard?level=' . $level . '&month=' . urlencode($month))->with('error', $built['error']);
|
|
}
|
|
|
|
return Excel::download(
|
|
new ZenovaReportExport($built['groups'], $built['raw_body'], $built['raw_totals']),
|
|
'glipiq-sitazit-level-' . $level . '.xlsx'
|
|
);
|
|
}
|
|
|
|
private function level(Request $request): int
|
|
{
|
|
$level = (int) $request->input('level', 3);
|
|
|
|
return ($level >= 1 && $level <= 5) ? $level : 3;
|
|
}
|
|
|
|
private function months(): array
|
|
{
|
|
$now = now()->startOfMonth();
|
|
$startYear = $now->month >= 4 ? $now->year : $now->year - 1;
|
|
$cursor = now()->setDate($startYear, 4, 1)->startOfMonth();
|
|
$months = [];
|
|
while ($cursor->lte($now)) {
|
|
$months[$cursor->format('Y-m')] = $cursor->format('F Y');
|
|
$cursor->addMonth();
|
|
}
|
|
|
|
return $months;
|
|
}
|
|
|
|
private function month(Request $request): string
|
|
{
|
|
$months = $this->months();
|
|
$selected = (string) $request->input('month', '');
|
|
if (isset($months[$selected])) {
|
|
return $selected;
|
|
}
|
|
|
|
return (string) array_key_last($months);
|
|
}
|
|
|
|
private function build(int $level, string $month): array
|
|
{
|
|
$response = LambdaRequest::signedrequest(
|
|
'get',
|
|
'reports',
|
|
'zenova_reports/glipiqsitazit_report',
|
|
['level' => (string) $level, 'month' => $month],
|
|
120
|
|
);
|
|
|
|
$rows = [];
|
|
$error = null;
|
|
if (!$response || ($response->status ?? '') !== 'success') {
|
|
$error = $response->message ?? 'Unable to load the Custom Dashboard.';
|
|
} else {
|
|
$rows = is_array($response->data ?? null) ? $response->data : [];
|
|
}
|
|
|
|
$rows = $this->sortRows($rows);
|
|
|
|
$groups = $this->groups($rows);
|
|
$columns = [];
|
|
foreach ($groups as $group) {
|
|
foreach ($group['columns'] as $column) {
|
|
$columns[] = $column;
|
|
}
|
|
}
|
|
|
|
$body = [];
|
|
$rawBody = [];
|
|
$rawTotals = array_fill(0, count($columns), 0);
|
|
foreach ($rows as $row) {
|
|
$line = [];
|
|
$rawLine = [];
|
|
foreach ($columns as $index => $column) {
|
|
$raw = $this->rawValue($row, $column);
|
|
if ($column['type'] !== 'text') {
|
|
$rawTotals[$index] += $raw;
|
|
}
|
|
$rawLine[] = $raw;
|
|
$line[] = $this->formatValue($raw, $column['type']);
|
|
}
|
|
$body[] = $line;
|
|
$rawBody[] = $rawLine;
|
|
}
|
|
|
|
$totals = [];
|
|
$rawTotalLine = [];
|
|
if (!empty($rows)) {
|
|
$fsos = $this->sumKey($rows, 'Demography & Details', 'No. of FSOs');
|
|
$glipiqActive = $this->sumKey($rows, 'Demography & Details', 'Glipiq Active FSOs');
|
|
$sitazitActive = $this->sumKey($rows, 'Demography & Details', 'Sitazit Active FSOs');
|
|
foreach ($columns as $index => $column) {
|
|
if ($column['type'] === 'text') {
|
|
$label = !empty($column['is_total']) ? 'Total' : '';
|
|
$totals[] = $label;
|
|
$rawTotalLine[] = $label;
|
|
continue;
|
|
}
|
|
if ($column['key'] === 'Glipiq Active FSO %') {
|
|
$percent = $fsos ? ($glipiqActive / $fsos) * 100 : 0;
|
|
$totals[] = $this->formatValue($percent, 'percent');
|
|
$rawTotalLine[] = $percent;
|
|
continue;
|
|
}
|
|
if ($column['key'] === 'Sitazit Active FSO %') {
|
|
$percent = $fsos ? ($sitazitActive / $fsos) * 100 : 0;
|
|
$totals[] = $this->formatValue($percent, 'percent');
|
|
$rawTotalLine[] = $percent;
|
|
continue;
|
|
}
|
|
$totals[] = $this->formatValue($rawTotals[$index], $column['type']);
|
|
$rawTotalLine[] = $rawTotals[$index];
|
|
}
|
|
}
|
|
|
|
return [
|
|
'groups' => $groups,
|
|
'body' => $body,
|
|
'totals' => $totals,
|
|
'raw_body' => $rawBody,
|
|
'raw_totals' => $rawTotalLine,
|
|
'error' => $error,
|
|
];
|
|
}
|
|
|
|
private function sortRows(array $rows): array
|
|
{
|
|
usort($rows, function ($left, $right) {
|
|
$zoneLeft = $this->zoneName($left);
|
|
$zoneRight = $this->zoneName($right);
|
|
if ($zoneLeft === '' && $zoneRight !== '') {
|
|
return 1;
|
|
}
|
|
if ($zoneRight === '' && $zoneLeft !== '') {
|
|
return -1;
|
|
}
|
|
$zone = strcasecmp($zoneLeft, $zoneRight);
|
|
if ($zone !== 0) {
|
|
return $zone;
|
|
}
|
|
$users = $this->fsoCount($right) <=> $this->fsoCount($left);
|
|
if ($users !== 0) {
|
|
return $users;
|
|
}
|
|
|
|
return strcasecmp($this->rowName($left), $this->rowName($right));
|
|
});
|
|
|
|
return $rows;
|
|
}
|
|
|
|
private function zoneName($row): string
|
|
{
|
|
$parents = (array) ($row->parent_positions ?? []);
|
|
foreach ($parents as $key => $value) {
|
|
if (strcasecmp((string) $key, 'Zone') === 0 && $value !== null && $value !== '') {
|
|
return (string) $value;
|
|
}
|
|
}
|
|
|
|
$demo = (array) ($row->{'Demography & Details'} ?? []);
|
|
foreach ($demo as $key => $value) {
|
|
if (strcasecmp((string) $key, 'Name of Zone') === 0 && $value !== null && $value !== '') {
|
|
return (string) $value;
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
private function fsoCount($row): float
|
|
{
|
|
$value = $row->{'Demography & Details'}->{'No. of FSOs'} ?? 0;
|
|
|
|
return is_numeric($value) ? (float) $value : 0;
|
|
}
|
|
|
|
private function rowName($row): string
|
|
{
|
|
$demo = (array) ($row->{'Demography & Details'} ?? []);
|
|
foreach ($demo as $key => $value) {
|
|
if (strpos((string) $key, 'Name of ') === 0) {
|
|
return (string) $value;
|
|
}
|
|
}
|
|
|
|
return (string) ($row->position_name ?? '');
|
|
}
|
|
|
|
private function groups(array $rows): array
|
|
{
|
|
$nameKey = 'Name of Region';
|
|
foreach ($rows as $row) {
|
|
$demo = (array) ($row->{'Demography & Details'} ?? []);
|
|
foreach (array_keys($demo) as $key) {
|
|
if (strpos($key, 'Name of ') === 0) {
|
|
$nameKey = $key;
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
$demoColumns = $this->geoColumns($nameKey);
|
|
$currentType = strtolower(preg_replace('/^Name of /', '', $nameKey));
|
|
if (!in_array($currentType, ['zone', 'region', 'area'], true)) {
|
|
$nameColumn = $this->column($nameKey, 'Demography & Details', $nameKey, 'text');
|
|
$nameColumn['is_total'] = true;
|
|
$demoColumns[] = $nameColumn;
|
|
}
|
|
foreach ([
|
|
'No. of FSOs' => 'number',
|
|
'No. of DSMs' => 'number',
|
|
'Glipiq Active FSOs' => 'number',
|
|
'Glipiq Active FSO %' => 'percent',
|
|
'Sitazit Active FSOs' => 'number',
|
|
'Sitazit Active FSO %' => 'percent',
|
|
] as $key => $type) {
|
|
$demoColumns[] = $this->column($key, 'Demography & Details', $key, $type);
|
|
}
|
|
|
|
$dayMetrics = ['GLIPIQ Total Rx', 'ABSI Camp Count', 'Patients Screened Count', 'Sitazit Total RX'];
|
|
$periodMetrics = [
|
|
'GLIPIQ Total Rx',
|
|
'GLIPIQ New Conversion',
|
|
'ABSI Camp Count',
|
|
'Patients Screened Count',
|
|
'GLIPIQ Total POB Units',
|
|
'Sitazit Total RX',
|
|
'Sitazit New Conversions',
|
|
'Sitazit Total POB Units',
|
|
];
|
|
|
|
$groups = [
|
|
['label' => 'Demography & Details (Data as per Yesterday)', 'tone' => 'demo', 'columns' => $demoColumns],
|
|
];
|
|
|
|
$first = isset($rows[0]) ? $rows[0] : null;
|
|
|
|
foreach ([
|
|
'day5' => 'Day 5 Status',
|
|
'day4' => 'Day 4 Status',
|
|
'day3' => 'Day 3 Status',
|
|
'day2' => 'Day 2 Status',
|
|
] as $section => $title) {
|
|
$groups[] = [
|
|
'label' => $this->periodLabel($title, $this->sectionField($first, $section, 'date')),
|
|
'tone' => 'day',
|
|
'columns' => $this->metricColumns($section, $dayMetrics),
|
|
];
|
|
}
|
|
|
|
$groups[] = [
|
|
'label' => $this->periodLabel('Day 1 / Yesterday', $this->sectionField($first, 'day1', 'date')),
|
|
'tone' => 'yesterday',
|
|
'columns' => $this->metricColumns('day1', $dayMetrics),
|
|
];
|
|
$groups[] = [
|
|
'label' => $this->periodLabel('Today', $this->sectionField($first, 'today', 'date')),
|
|
'tone' => 'today',
|
|
'columns' => $this->metricColumns('today', $periodMetrics),
|
|
];
|
|
$groups[] = [
|
|
'label' => $this->periodLabel('MTD', $this->sectionField($first, 'mtd', 'period')),
|
|
'tone' => 'mtd',
|
|
'columns' => $this->metricColumns('mtd', $periodMetrics),
|
|
];
|
|
$groups[] = [
|
|
'label' => $this->periodLabel('YTD', $this->sectionField($first, 'ytd', 'period')),
|
|
'tone' => 'ytd',
|
|
'columns' => $this->metricColumns('ytd', $periodMetrics),
|
|
];
|
|
|
|
return $groups;
|
|
}
|
|
|
|
private function geoColumns($nameKey)
|
|
{
|
|
$currentType = preg_replace('/^Name of /', '', $nameKey);
|
|
$columns = [];
|
|
foreach (['Zone', 'Region', 'Area'] as $type) {
|
|
if (strcasecmp($currentType, $type) === 0) {
|
|
$column = $this->column($type, 'Demography & Details', $nameKey, 'text');
|
|
$column['is_total'] = true;
|
|
} else {
|
|
$column = $this->column($type, 'parent_positions', $type, 'text');
|
|
}
|
|
if (in_array($type, ['Zone', 'Region'], true)) {
|
|
$column['sticky'] = strtolower($type);
|
|
}
|
|
$columns[] = $column;
|
|
}
|
|
|
|
return $columns;
|
|
}
|
|
|
|
private function column(string $label, string $section, string $key, string $type): array
|
|
{
|
|
return [
|
|
'label' => $label,
|
|
'section' => $section,
|
|
'key' => $key,
|
|
'type' => $type,
|
|
];
|
|
}
|
|
|
|
private function metricColumns(string $section, array $metrics): array
|
|
{
|
|
$columns = [];
|
|
foreach ($metrics as $metric) {
|
|
$columns[] = $this->column($metric, $section, $metric, 'number');
|
|
}
|
|
|
|
return $columns;
|
|
}
|
|
|
|
private function periodLabel(string $title, string $period): string
|
|
{
|
|
return $period !== '' ? $title . ' (' . $period . ')' : $title;
|
|
}
|
|
|
|
private function sectionField($row, $section, $field)
|
|
{
|
|
if (!is_object($row) || !isset($row->{$section}) || !is_object($row->{$section})) {
|
|
return '';
|
|
}
|
|
|
|
$value = $row->{$section}->{$field} ?? '';
|
|
|
|
return $value === null ? '' : (string) $value;
|
|
}
|
|
|
|
private function rawValue($row, array $column)
|
|
{
|
|
if ($column['section'] === 'parent_positions') {
|
|
$parents = (array) ($row->parent_positions ?? []);
|
|
foreach ($parents as $key => $parentValue) {
|
|
if (strcasecmp((string) $key, 'Country') === 0) {
|
|
continue;
|
|
}
|
|
if (strcasecmp((string) $key, $column['key']) === 0) {
|
|
return ($parentValue === null || $parentValue === '') ? '' : (string) $parentValue;
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
$section = $row->{$column['section']} ?? null;
|
|
$value = is_object($section) ? ($section->{$column['key']} ?? null) : null;
|
|
if ($column['type'] === 'text') {
|
|
return ($value === null || $value === '') ? '-' : (string) $value;
|
|
}
|
|
|
|
return is_numeric($value) ? (float) $value : 0;
|
|
}
|
|
|
|
private function sumKey(array $rows, string $section, string $key): float
|
|
{
|
|
$total = 0;
|
|
foreach ($rows as $row) {
|
|
$value = $row->{$section}->{$key} ?? 0;
|
|
if (is_numeric($value)) {
|
|
$total += (float) $value;
|
|
}
|
|
}
|
|
|
|
return $total;
|
|
}
|
|
|
|
private function formatValue($value, $type)
|
|
{
|
|
if ($type === 'text') {
|
|
return (string) $value;
|
|
}
|
|
$number = (float) $value;
|
|
if ($type === 'percent') {
|
|
return number_format($number, 1) . '%';
|
|
}
|
|
if (floor($number) == $number) {
|
|
return number_format($number, 0);
|
|
}
|
|
|
|
return number_format($number, 2);
|
|
}
|
|
}
|