'DPI', '1781688609-af666416-0aa2-4bc4-b7c1-c09a09de153b' => 'MDI', '1781688715-c52dbfa2-1c07-4235-83b6-09e2d2783a7c' => 'DPI+MDI', ]; private const REPORT_TYPES = [ 'dpi' => [ 'label' => 'DPI', 'metrics' => [ '1781688574-0659e182-8771-4f7b-b3c8-eb6f45cf5e61', '1781688715-c52dbfa2-1c07-4235-83b6-09e2d2783a7c', ], 'quarters_only' => false, ], 'mdi' => [ 'label' => 'MDI', 'metrics' => [ '1781688609-af666416-0aa2-4bc4-b7c1-c09a09de153b', '1781688715-c52dbfa2-1c07-4235-83b6-09e2d2783a7c', ], 'quarters_only' => false, ], 'quarter' => [ 'label' => 'Quarter', // Metrics used only to discover TMs; UI/export show quarter columns only. 'metrics' => [ '1781688574-0659e182-8771-4f7b-b3c8-eb6f45cf5e61', '1781688609-af666416-0aa2-4bc4-b7c1-c09a09de153b', '1781688715-c52dbfa2-1c07-4235-83b6-09e2d2783a7c', ], 'quarters_only' => true, ], ]; private ?string $accessToken = null; private ?object $tokenUser = null; public function __construct() { $this->_service = 'reports'; $this->_module = 'contact_metric_monthly'; $this->_viewname = 'adherencepackratioreport'; $this->_title = 'Adherence Pack Ratio Report'; $this->_base_url = '/adherencepackratioreport'; } public function list(Request $request) { return redirect($this->buildUrl('/report', $request)); } public function report(Request $request) { $token = $this->assertTeam2Access($request); $type = $this->resolveReportType($request); $perPage = $this->resolvePerPage($request); $search = $this->resolveSearchTerm($request); $payload = $this->buildReportPayload($request, false, $perPage, $search); $response = LambdaRequest::getrequest( $this->_service, $this->_module . '/tm_adherence_pack_ratio', $payload, $token, 300 ); $report = $response->data ?? (object) []; [$tms, $data, $pagination] = $this->resolveReportPage($report, $request, $perPage, $search); $typeLabel = self::REPORT_TYPES[$type]['label']; $quartersOnly = $this->isQuartersOnly($type); return $this->view('report')->with([ 'title' => $this->_title . ' (' . $typeLabel . ')', 'base_url' => $this->_base_url, 'token' => $token, 'type' => $type, 'typeLabel' => $typeLabel, 'quartersOnly' => $quartersOnly, 'months' => $quartersOnly ? [] : ($report->months ?? []), 'metrics' => $quartersOnly ? [] : ($report->metrics ?? []), 'quarters' => $report->quarters ?? [], 'quarter_data' => $report->quarter_data ?? (object) [], 'tms' => $tms, 'data' => $quartersOnly ? (object) [] : $data, 'pagination' => $pagination, 'perPage' => $perPage, 'search' => $search, ]); } public function export(Request $request) { $token = $this->assertTeam2Access($request); $type = $this->resolveReportType($request); $search = $this->resolveSearchTerm($request); $payload = $this->buildReportPayload($request, true, 50, $search); $response = LambdaRequest::getrequest( $this->_service, $this->_module . '/tm_adherence_pack_ratio', $payload, $token, 300 ); $report = $response->data ?? (object) []; $quartersOnly = $this->isQuartersOnly($type); $months = $quartersOnly ? [] : ($report->months ?? []); $metrics = $quartersOnly ? [] : ($report->metrics ?? []); $quarters = $report->quarters ?? []; $quarterData = $report->quarter_data ?? (object) []; $tms = $this->filterTmsBySearch($this->normalizeTmsList($report->tms ?? null), $search); $data = $quartersOnly ? (object) [] : ($report->data ?? (object) []); $headerRow1 = ['TM Employee Code', 'TM Name', 'Zone', 'Region', 'Area', 'Territory']; $excelData = []; if ($quartersOnly) { foreach ($quarters as $quarter) { $headerRow1[] = $this->quarterDisplayLabel($quarter); } $excelData[] = $headerRow1; foreach ($tms as $tm) { $row = [ $tm->employee_code ?? '', $tm->name ?? $tm->id, $tm->zone ?? '', $tm->region ?? '', $tm->area ?? '', $tm->territory ?? '', ]; $tmQuarterData = is_object($quarterData) ? ($quarterData->{$tm->id} ?? null) : null; foreach ($quarters as $quarter) { $quarterKey = $quarter->key ?? ''; $value = is_object($tmQuarterData) ? ($tmQuarterData->{$quarterKey} ?? null) : null; $row[] = $value !== null && $value !== '' ? $value : '-'; } $excelData[] = $row; } } else { $quartersAfterMonth = []; foreach ($quarters as $quarter) { $afterMonth = $quarter->after_month ?? null; if ($afterMonth) { $quartersAfterMonth[$afterMonth][] = $quarter; } } $headerRow2 = array_fill(0, 6, ''); foreach ($months as $month) { $monthKey = $month->key ?? ''; $headerRow1[] = $month->label ?? $monthKey; $headerRow1 = array_merge($headerRow1, array_fill(0, max(count($metrics) - 1, 0), '')); foreach ($metrics as $metric) { $headerRow2[] = $metric->label ?? $metric->id; } foreach ($quartersAfterMonth[$monthKey] ?? [] as $quarter) { $headerRow1[] = $quarter->label ?? $quarter->key; $headerRow2[] = ''; } } $excelData = [$headerRow1, $headerRow2]; foreach ($tms as $tm) { $row = [ $tm->employee_code ?? '', $tm->name ?? $tm->id, $tm->zone ?? '', $tm->region ?? '', $tm->area ?? '', $tm->territory ?? '', ]; $tmQuarterData = is_object($quarterData) ? ($quarterData->{$tm->id} ?? null) : null; foreach ($months as $month) { $monthKey = $month->key ?? ''; $tmData = $data->{$tm->id} ?? null; $monthData = is_object($tmData) ? ($tmData->{$monthKey} ?? null) : null; foreach ($metrics as $metric) { $metricId = $metric->id ?? ''; $value = is_object($monthData) ? ($monthData->{$metricId} ?? null) : null; $row[] = $value !== null && $value !== '' ? $value : '-'; } foreach ($quartersAfterMonth[$monthKey] ?? [] as $quarter) { $quarterKey = $quarter->key ?? ''; $value = is_object($tmQuarterData) ? ($tmQuarterData->{$quarterKey} ?? null) : null; $row[] = $value !== null && $value !== '' ? $value : '-'; } } $excelData[] = $row; } } $typeLabel = strtolower(self::REPORT_TYPES[$type]['label']); return Excel::download(new InvoicesExport($excelData), "adherence-pack-ratio-report-{$typeLabel}.xlsx"); } protected function resolveReportType(Request $request): string { $type = strtolower(trim((string) $request->query('type', 'dpi'))); if (!isset(self::REPORT_TYPES[$type])) { $type = 'dpi'; } return $type; } protected function isQuartersOnly(string $type): bool { return !empty(self::REPORT_TYPES[$type]['quarters_only']); } protected function quarterDisplayLabel($quarter): string { $q = is_object($quarter) ? ($quarter->q ?? null) : ($quarter['q'] ?? null); if ($q) { return 'Q' . $q . ' %'; } $label = is_object($quarter) ? ($quarter->label ?? $quarter->key ?? '') : ($quarter['label'] ?? $quarter['key'] ?? ''); $label = preg_replace('/\s*Ratio$/i', '', (string) $label) ?: (string) $label; $label = trim((string) $label); if ($label === '') { return ''; } if (preg_match('/%\s*$/', $label)) { return $label; } return $label . ' %'; } protected function metricsForType(string $type): array { $ids = self::REPORT_TYPES[$type]['metrics'] ?? self::REPORT_TYPES['dpi']['metrics']; $metrics = []; foreach ($ids as $id) { $metrics[$id] = self::ALL_METRICS[$id] ?? $id; } return $metrics; } protected function resolveReportPage(object $report, Request $request, int $perPage, string $search = ''): array { $tms = $this->normalizeTmsList($report->tms ?? null); if ($search !== '') { $tms = $this->filterTmsBySearch($tms, $search); } $data = $report->data ?? (object) []; $page = max(1, (int) $request->input('page', 1)); $apiPagination = $report->pagination ?? null; $apiPaginated = is_object($apiPagination) && isset($apiPagination->total_pages) && (int) $apiPagination->total_pages >= 0 && ($search !== '' || count($tms) <= $perPage); if ($apiPaginated) { $apiPagination->page = (int) ($apiPagination->page ?? $page); $apiPagination->per_page = (int) ($apiPagination->per_page ?? $perPage); return [$tms, $data, $apiPagination]; } $total = count($tms); $totalPages = max(1, (int) ceil($total / $perPage)); $page = min($page, $totalPages); $offset = ($page - 1) * $perPage; $pagedTms = array_slice($tms, $offset, $perPage); $pagedData = (object) []; foreach ($pagedTms as $tm) { $tmId = is_object($tm) ? ($tm->id ?? null) : null; if ($tmId && isset($data->{$tmId})) { $pagedData->{$tmId} = $data->{$tmId}; } } $pagination = (object) [ 'page' => $page, 'per_page' => $perPage, 'total' => $total, 'total_pages' => $totalPages, ]; return [$pagedTms, $pagedData, $pagination]; } protected function resolveSearchTerm(Request $request): string { return trim((string) $request->query('search', '')); } protected function filterTmsBySearch(array $tms, string $search): array { if ($search === '') { return $tms; } $needle = mb_strtolower($search); $words = preg_split('/\s+/u', $needle, -1, PREG_SPLIT_NO_EMPTY); if (empty($words)) { return $tms; } return array_values(array_filter($tms, function ($tm) use ($words) { if (!is_object($tm)) { return false; } $haystack = $this->tmSearchHaystack($tm); foreach ($words as $word) { if (!str_contains($haystack, $word)) { return false; } } return true; })); } protected function tmSearchHaystack(object $tm): string { $parts = [ $tm->name ?? $tm->id ?? '', $tm->employee_code ?? '', $tm->zone ?? '', $tm->region ?? '', $tm->area ?? '', $tm->territory ?? '', ]; $normalized = []; foreach ($parts as $part) { $value = trim((string) $part); if ($value === '' || $value === '-') { continue; } $normalized[] = mb_strtolower($value); } return preg_replace('/\s+/u', ' ', implode(' ', $normalized)); } protected function normalizeTmsList($raw): array { if (is_array($raw)) { return array_values($raw); } if (is_object($raw)) { return array_values((array) $raw); } return []; } protected function buildReportPayload(Request $request, bool $fetchAll = false, int $perPage = 50, string $search = ''): array { $type = $this->resolveReportType($request); $metrics = $this->metricsForType($type); $payload = [ 'partition' => self::PARTITION, 'metric_ids' => json_encode(array_keys($metrics)), 'metric_labels' => json_encode($metrics), 'ratio_metric_id' => self::RATIO_METRIC_ID, 'start_month' => self::REPORT_START, 'type' => $type, ]; if ($search !== '') { $payload['search'] = $search; } if ($fetchAll) { $payload['all'] = '1'; } else { $payload['page'] = max(1, (int) $request->input('page', 1)); $payload['per_page'] = $perPage; } return $payload; } protected function resolvePerPage(Request $request): int { $allowed = [25, 50, 100, 200]; $default = 50; $raw = $request->query('per_page'); if ($raw !== null && $raw !== '') { $perPage = (int) $raw; if (!in_array($perPage, $allowed, true)) { $perPage = $default; } $request->session()->put('adherence_pack_ratio_report_per_page', $perPage); return $perPage; } if ($request->session()->has('adherence_pack_ratio_report_per_page')) { $perPage = (int) $request->session()->get('adherence_pack_ratio_report_per_page'); if (in_array($perPage, $allowed, true)) { return $perPage; } } return $default; } protected function assertTeam2Access(Request $request): string { $token = $this->resolveAccessToken($request); if (!$token) { abort(401, 'Access token is required'); } $user = $this->getUserFromToken($token); if (!$user || ($user->domain ?? '') !== 'team2') { abort(403); } $this->accessToken = $token; $this->tokenUser = $user; return $token; } protected function resolveAccessToken(Request $request): ?string { $token = trim((string) ($request->input('token') ?? $request->header('Authorization') ?? '')); if ($token === '' && $request->session()->has('adherence_pack_ratio_report_token')) { $token = (string) $request->session()->get('adherence_pack_ratio_report_token'); } if ($token !== '') { $request->session()->put('adherence_pack_ratio_report_token', $token); } return $token !== '' ? $token : null; } protected function getUserFromToken(string $token): ?object { $parts = explode('.', $token); if (count($parts) < 2) { return null; } $payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/'))); if (!is_object($payload)) { return null; } return $payload->user ?? null; } protected function buildUrl(string $path, Request $request): string { $perPage = $this->resolvePerPage($request); $query = array_filter([ 'token' => $request->input('token') ?: $request->session()->get('adherence_pack_ratio_report_token'), 'type' => $this->resolveReportType($request), 'page' => $request->input('page'), 'per_page' => $perPage, 'search' => $this->resolveSearchTerm($request), ], function ($value) { return $value !== null && $value !== ''; }); $url = $this->_base_url . $path; if (!empty($query)) { $url .= '?' . http_build_query($query); } return $url; } }