498 lines
17 KiB
PHP
498 lines
17 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Exports\InvoicesExport;
|
|
use App\Http\Requests\LambdaRequest;
|
|
use Illuminate\Http\Request;
|
|
use Maatwebsite\Excel\Facades\Excel;
|
|
|
|
class AdherencepackratioreportController extends Controller
|
|
{
|
|
private const PARTITION = 'rapport-team2';
|
|
|
|
private const REPORT_START = 'April 2026';
|
|
|
|
private const RATIO_METRIC_ID = '1784522317-547f27be-1da3-4912-983e-120b49b0841a';
|
|
|
|
private const ALL_METRICS = [
|
|
'1781688574-0659e182-8771-4f7b-b3c8-eb6f45cf5e61' => '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;
|
|
}
|
|
}
|