Rapport-Laravel/app/Http/Controllers/IvareportController.php

968 lines
33 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Exports\IvaStyledExport;
use App\Http\Requests\LambdaRequest;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Log;
use Maatwebsite\Excel\Facades\Excel;
class IvareportController extends Controller
{
private const DOMAIN = 'iva';
private const PARTITION = 'rapport-iva';
private const REPORT_START = 'April 2026';
private const API_TIMEOUT = 120;
private const PLANNER_BRANDS = [
'Materna HMG' => 'Materna HMG',
'Ciscure' => 'Ciscure',
'Emprogest' => 'Emprogest',
];
private const KAM_BRANDS = [
'Materna HMG POB Val' => 'Materna HMG',
'Materna HCG POB Val' => 'Materna HCG',
'Materna Depot POB Val' => 'Materna Depot',
'Ciscure POB Val' => 'Ciscure',
'Emprogest POB Val' => 'Emprogest',
'Dydroeva POB Val' => 'Dydroeva',
'Exhep POB Val' => 'Exhep',
'Emgra POB Val' => 'Emgra',
'Silnafil POB Val' => 'Silnafil',
'Xgrast POB Val' => 'Xgrast',
'Orofer POB Val' => 'Orofer',
];
private const SKU_BRANDS = [
'CISCURE' => 'CISCURE',
'DYDROEVA' => 'DYDROEVA',
'EMGRA' => 'EMGRA',
'EMPROGEST' => 'EMPROGEST',
'EXHEP' => 'EXHEP',
'MATERNA DEPOT' => 'MATERNA DEPOT',
'MATERNA HCG' => 'MATERNA HCG',
'MATERNA' => 'MATERNA',
'OROFER' => 'OROFER',
'SILNAFIL' => 'SILNAFIL',
'XGRAST' => 'XGRAST',
];
private const SKU_GROUPS = [
'CISCURE' => [
'CISCURE PFS Rate',
'CISCURE 0.25 MG INJECTION Rate',
],
'DYDROEVA' => [
'DYDROEVA TABLET 10 MG Rate',
],
'EMGRA' => [
'EMGRA TABLET 10X28T Rate',
],
'EMPROGEST' => [
'EMPROGEST - 200 CAPSULE Rate',
'EMPROGEST - 400 CAPSULE Rate',
'EMPROGEST EV 200 TABLET Rate',
'EMPROGEST GEL 1.125 GM Rate',
'EMPROGEST SR 200 TABLET Rate',
'EMPROGEST SR 300 TABLET Rate',
],
'EXHEP' => [
'EXHEP 20 INJECTION Rate',
'EXHEP 40 MG INJECTION 10PFS Rate',
'EXHEP 60 MG INJECTION 10PFS Rate',
'EXHEP PEN CARTRIDGE 300MG/3ML Rate',
],
'MATERNA DEPOT' => [
'MATERNA DEPOT 3.75 MG INJECTION Rate',
],
'MATERNA HCG' => [
'MATERNA-HCG 10000 IU Rate',
'MATERNA-HCG 5000 IU Rate',
],
'MATERNA' => [
'MATERNA-HMG 150 I.U. Rate',
'MATERNA-HMG 75 I.U. Rate',
'MATERNA-HMG HP 1200 IU Rate',
'MATERNA-HMG HP 225 IU Rate',
'MATERNA-HMG HP 300 IU Rate',
],
'OROFER' => [
'OROFER S 100 INJECTION Rate',
'OROFER S 200 INJECTION 10 ML Rate',
'OROFER S INJECTION 2.5 ML Rate',
],
'SILNAFIL' => [
'SILNAFIL TABLETS Rate',
],
'XGRAST' => [
'XGRAST INJ 300 MCG/0.5 ML PFS Rate',
],
];
private const COLOR_ID = ['bg' => '00B050', 'fg' => '000000', 'class' => 'iva-th-id'];
private const COLOR_METRIC = ['bg' => 'FFFF00', 'fg' => '000000', 'class' => 'iva-th-metric'];
private const COLOR_META = ['bg' => 'E7E6E6', 'fg' => '000000', 'class' => 'iva-th-meta'];
private const COLOR_SKU = ['bg' => 'FFC000', 'fg' => '000000', 'class' => 'iva-th-sku'];
private const PLANNER_BRAND_COLORS = [
'Materna HMG' => ['bg' => 'E7E6E6', 'fg' => '000000'],
'Ciscure' => ['bg' => 'F8CBAD', 'fg' => '000000'],
'Emprogest' => ['bg' => 'DEEBF7', 'fg' => '000000'],
];
private const SKU_BRAND_COLORS = [
'CISCURE' => ['bg' => 'ED7D31', 'fg' => 'FFFFFF'],
'DYDROEVA' => ['bg' => '3D4C5F', 'fg' => 'FFFFFF'],
'EMGRA' => ['bg' => 'B4C7E7', 'fg' => '000000'],
'EMPROGEST' => ['bg' => '548235', 'fg' => 'FFFFFF'],
'EXHEP' => ['bg' => 'DEEBF7', 'fg' => '000000'],
'MATERNA DEPOT' => ['bg' => 'FFC000', 'fg' => '000000'],
'MATERNA HCG' => ['bg' => 'FFE699', 'fg' => '000000'],
'MATERNA' => ['bg' => 'E2EFDA', 'fg' => '000000'],
'OROFER' => ['bg' => 'BDD7EE', 'fg' => '000000'],
'SILNAFIL' => ['bg' => 'B25E25', 'fg' => 'FFFFFF'],
'XGRAST' => ['bg' => 'A9CE91', 'fg' => '000000'],
];
public function __construct()
{
$this->middleware('auth');
$this->middleware(function ($request, $next) {
if ((auth()->user()->domain ?? '') !== self::DOMAIN) {
abort(403);
}
return $next($request);
});
$this->_service = 'reports';
$this->_module = 'iva_reports';
$this->_viewname = 'ivareport';
$this->_title = 'Custom Dashboards';
$this->_base_url = '/ivareport';
}
public function list(Request $request)
{
$query = $request->getQueryString();
return redirect('/ivareport/planner' . ($query ? '?' . $query : ''));
}
public function planner(Request $request)
{
return $this->renderReport($request, 'planner_data', 'planner', 'Planner Data');
}
public function kamwise(Request $request)
{
return $this->renderReport($request, 'kam_wise', 'kamwise', 'KAM wise details');
}
public function drbrandwise(Request $request)
{
return $this->renderReport($request, 'dr_and_brandwise', 'drbrandwise', 'Dr & Brand Wise details');
}
public function skuwise(Request $request)
{
return $this->renderReport($request, 'sku_wise', 'skuwise', 'SKU wise Rate');
}
public function export(Request $request)
{
$typeMap = [
'planner' => ['api' => 'planner_data', 'file' => 'planner-data'],
'kamwise' => ['api' => 'kam_wise', 'file' => 'kam-wise-details'],
'drbrandwise' => ['api' => 'dr_and_brandwise', 'file' => 'dr-brand-wise-details'],
'skuwise' => ['api' => 'sku_wise', 'file' => 'sku-wise-rate'],
];
$type = $request->input('type', 'planner');
if (!isset($typeMap[$type])) {
abort(404);
}
$reportType = $typeMap[$type]['api'];
$filters = $this->resolveFilters($request, $reportType);
$payload = $this->fetchReport(
$reportType,
$filters['month'],
$filters['selected_brands'],
$filters['hierarchy_ids']
);
if ($type === 'planner') {
$table = $this->buildPlannerTable($payload['report'], $payload['metric_config'], $filters['selected_brands']);
$export = $this->buildPlannerExport($table);
} elseif ($type === 'kamwise') {
$table = $this->buildKamTable($payload['report'], $payload['metric_config'], $filters['selected_brands']);
$export = $this->buildKamExport($table);
} elseif ($type === 'drbrandwise') {
$table = $this->buildDrBrandTable($payload['report'], $filters['selected_brands']);
$export = $this->buildDrBrandExport($table);
} else {
$table = $this->buildSkuTable($payload['report'], $filters['selected_brands']);
$export = $this->buildSkuExport($table);
}
$monthSlug = preg_replace('/[^A-Za-z0-9]+/', '-', $filters['month']);
return Excel::download(new IvaStyledExport($export), $typeMap[$type]['file'] . '-' . $monthSlug . '.xlsx');
}
private function renderReport(Request $request, string $reportType, string $view, string $title)
{
$filters = $this->resolveFilters($request, $reportType);
$payload = $this->fetchReport(
$reportType,
$filters['month'],
$filters['selected_brands'],
$filters['hierarchy_ids']
);
$viewData = [
'title' => $title,
'activeTab' => $view,
'months' => $this->availableMonths(),
'selectedMonth' => $filters['month'],
'filter_hier' => $this->loadHierarchies(),
'selectedHier' => $filters['hierarchy_ids'],
'brandOptions' => $this->brandOptions($reportType),
'selectedBrands' => $filters['selected_brands'],
'filterQuery' => $this->filterQuery($filters),
'error' => $payload['error'],
'rows' => [],
'ivaFmt' => function ($val, $isPct = false) {
return self::formatNumber($val, $isPct);
},
'ivaMetricsTitle' => function ($metrics) {
return self::metricsTitle($metrics);
},
];
if ($reportType === 'planner_data') {
$viewData = array_merge($viewData, $this->buildPlannerTable($payload['report'], $payload['metric_config'], $filters['selected_brands']));
} elseif ($reportType === 'kam_wise') {
$viewData = array_merge($viewData, $this->buildKamTable($payload['report'], $payload['metric_config'], $filters['selected_brands']));
} elseif ($reportType === 'dr_and_brandwise') {
$viewData = array_merge($viewData, $this->buildDrBrandTable($payload['report'], $filters['selected_brands']));
} else {
$viewData = array_merge($viewData, $this->buildSkuTable($payload['report'], $filters['selected_brands']));
}
return view($this->_viewname . '.' . $view, $viewData);
}
private function resolveFilters(Request $request, string $reportType): array
{
$month = trim((string) $request->input('month', Carbon::now()->format('F Y')));
$validMonths = $this->availableMonths();
$validValues = array_column($validMonths, 'value');
if (!in_array($month, $validValues, true)) {
$month = Carbon::now()->format('F Y');
}
$hierarchyIds = array_values(array_filter((array) $request->input('filter_hier', [])));
$allowedBrands = array_keys($this->brandOptions($reportType));
$selectedBrands = array_values(array_intersect(
array_filter((array) $request->input('selected_brand', [])),
$allowedBrands
));
return [
'month' => $month,
'hierarchy_ids' => $hierarchyIds,
'selected_brands' => $selectedBrands,
];
}
private function availableMonths(): array
{
$months = [['value' => 'overall', 'label' => 'Overall']];
try {
$start = Carbon::createFromFormat('F Y', self::REPORT_START)->startOfMonth();
} catch (\Exception $e) {
$start = Carbon::now()->startOfMonth();
}
$end = Carbon::now()->startOfMonth();
if ($start->gt($end)) {
$start = $end->copy();
}
$cursor = $end->copy();
while ($cursor->gte($start)) {
$label = $cursor->format('F Y');
$months[] = ['value' => $label, 'label' => $label];
$cursor->subMonth();
}
return $months;
}
private function brandOptions(string $reportType): array
{
if ($reportType === 'planner_data') {
return self::PLANNER_BRANDS;
}
if ($reportType === 'sku_wise') {
return self::SKU_BRANDS;
}
return self::KAM_BRANDS;
}
private function filterQuery(array $filters): string
{
return http_build_query([
'month' => $filters['month'],
'filter_hier' => $filters['hierarchy_ids'],
'selected_brand' => $filters['selected_brands'],
]);
}
private function fetchReport(string $reportType, string $month, array $brands, array $hierIds): array
{
try {
$response = LambdaRequest::signedrequest(
'get',
'reports',
'iva_reports/brand_reports',
[
'report_type' => $reportType,
'selected_brand' => json_encode(array_values($brands)),
'month' => $month,
'hierarchy_ids' => json_encode(array_values($hierIds)),
],
self::API_TIMEOUT
);
} catch (\Throwable $e) {
Log::error('IVA custom dashboard request failed', [
'report_type' => $reportType,
'error' => $e->getMessage(),
]);
return ['report' => [], 'metric_config' => [], 'error' => 'Unable to load report. Please try again.'];
}
if (!$response || (($response->status ?? '') !== 'success')) {
$message = $response->message ?? 'Unable to load report. Please try again.';
Log::warning('IVA custom dashboard API error', [
'report_type' => $reportType,
'message' => $message,
]);
return ['report' => [], 'metric_config' => [], 'error' => $message];
}
$data = $response->data ?? null;
$report = [];
$metricConfig = [];
if ($data) {
$report = json_decode(json_encode($data->report ?? []), true) ?: [];
$metricConfig = json_decode(json_encode($data->metric_config ?? []), true) ?: [];
}
return ['report' => $report, 'metric_config' => $metricConfig, 'error' => null];
}
private function loadHierarchies(): array
{
$permissions = json_decode(auth()->user()->permissions ?? '[]');
if (!is_array($permissions)) {
$permissions = [];
}
$hierarchyId = in_array('all_data_access', $permissions, true) ? 'root' : [];
try {
$response = LambdaRequest::signedrequest(
'get',
'ums',
'hierarchy/hierarchy_below_me',
[
'partition' => self::PARTITION,
'hierarchy_id' => $hierarchyId,
]
);
} catch (\Throwable $e) {
Log::error('IVA hierarchy load failed', ['error' => $e->getMessage()]);
return [];
}
$hierarchies = $response->data->hierarchies ?? [];
if (!is_array($hierarchies)) {
$hierarchies = [];
}
usort($hierarchies, function ($a, $b) {
return ((int) ($b->level ?? 0)) <=> ((int) ($a->level ?? 0));
});
return $hierarchies;
}
private function visibleBrandKeys(array $all, array $selected): array
{
if (empty($selected)) {
return array_keys($all);
}
return array_values(array_intersect(array_keys($all), $selected));
}
private function buildPlannerTable(array $report, array $metricConfig, array $selectedBrands): array
{
$brandKeys = $this->visibleBrandKeys(self::PLANNER_BRANDS, $selectedBrands);
$brandGroups = [];
foreach ($brandKeys as $brand) {
$brandGroups[] = [
'name' => $brand,
'style' => $this->brandStyle(self::PLANNER_BRAND_COLORS, $brand, 'iva-planner-'),
'columns' => [
['key' => 'plan', 'label' => 'POB Plan (Val)'],
['key' => 'done', 'label' => 'POB Done (Val)'],
['key' => 'pct', 'label' => '% POB'],
],
];
}
$rows = [];
foreach ($report as $item) {
$item = (array) $item;
$row = [
'territory' => $item['territory'] ?? '',
'kam_emp_code' => $item['kam_emp_code'] ?? '',
'kam_name' => $item['kam_name'] ?? '',
'brands' => [],
];
foreach ($brandKeys as $brand) {
$brandData = (array) ($item[$brand] ?? []);
$metrics = (array) ($brandData['metrics'] ?? []);
$row['brands'][$brand] = [
'plan' => $brandData['POB Plan (Val)'] ?? 0,
'done' => $brandData['POB Done (Val)'] ?? 0,
'pct' => $brandData['% POB'] ?? 0,
'plan_metrics' => $this->normalizeMetrics((array) ($metrics['POB Plan (Val)'] ?? [])),
'done_metrics' => $this->normalizeMetrics((array) ($metrics['POB Done (Val)'] ?? [])),
];
}
$rows[] = $row;
}
return [
'brandGroups' => $brandGroups,
'rows' => $rows,
'skuColumns' => $this->plannerSkuColumns($metricConfig, $brandKeys, $rows),
];
}
private function plannerSkuColumns(array $metricConfig, array $brandKeys, array $rows): array
{
$byBrand = [];
foreach ($metricConfig as $entry) {
$entry = (array) $entry;
foreach ($entry as $brand => $cfg) {
if (!in_array($brand, $brandKeys, true)) {
continue;
}
$cfg = (array) $cfg;
$byBrand[$brand] = $this->skuColumnsFromPlanDone(
(array) ($cfg['POB Plan (Val)'] ?? []),
(array) ($cfg['POB Done (Val)'] ?? [])
);
}
}
foreach ($brandKeys as $brand) {
if (!empty($byBrand[$brand])) {
continue;
}
$planMetrics = [];
$doneMetrics = [];
foreach ($rows as $row) {
$planMetrics = array_merge($planMetrics, $row['brands'][$brand]['plan_metrics'] ?? []);
$doneMetrics = array_merge($doneMetrics, $row['brands'][$brand]['done_metrics'] ?? []);
}
$byBrand[$brand] = $this->skuColumnsFromPlanDone($planMetrics, $doneMetrics);
}
return $byBrand;
}
private function skuColumnsFromPlanDone(array $plans, array $dones): array
{
$planBySku = [];
foreach ($plans as $item) {
$item = (array) $item;
$attr = $item['attribute'] ?? '';
if ($attr === '') {
continue;
}
$sku = preg_replace('/ POB Plan Val$/', '', $attr);
if (!isset($planBySku[$sku])) {
$planBySku[$sku] = $attr;
}
}
$doneBySku = [];
foreach ($dones as $item) {
$item = (array) $item;
$attr = $item['attribute'] ?? '';
if ($attr === '') {
continue;
}
$sku = preg_replace('/ POB Done Val$/', '', $attr);
if (!isset($doneBySku[$sku])) {
$doneBySku[$sku] = $attr;
}
}
$skus = array_unique(array_merge(array_keys($planBySku), array_keys($doneBySku)));
$columns = [];
foreach ($skus as $sku) {
$columns[] = [
'sku' => $sku,
'plan_attr' => $planBySku[$sku] ?? ($sku . ' POB Plan Val'),
'done_attr' => $doneBySku[$sku] ?? ($sku . ' POB Done Val'),
'pct_label' => $sku . ' POB %',
];
}
return $columns;
}
private function buildPlannerExport(array $table): array
{
$brandGroups = $table['brandGroups'] ?? [];
$skuColumns = $table['skuColumns'] ?? [];
$rows = $table['rows'] ?? [];
$row1 = ['Territory', 'KAM Emp Code', 'KAM Name'];
$row2 = ['', '', ''];
$merges = ['A1:A2', 'B1:B2', 'C1:C2'];
$fills = [
'A1:C2' => self::COLOR_ID,
];
$col = 4;
foreach ($brandGroups as $group) {
$brand = $group['name'];
$skuCount = count($skuColumns[$brand] ?? []);
$span = 3 + ($skuCount * 3);
$start = $this->excelCol($col);
$end = $this->excelCol($col + $span - 1);
$merges[] = $start . '1:' . $end . '1';
$style = $group['style'] ?? $this->brandStyle(self::PLANNER_BRAND_COLORS, $brand);
$fills[$start . '1:' . $end . '1'] = $style;
$fills[$start . '2:' . $end . '2'] = self::COLOR_METRIC;
$row1[] = $brand;
for ($i = 1; $i < $span; $i++) {
$row1[] = '';
}
$row2[] = 'POB Plan (Val)';
$row2[] = 'POB Done (Val)';
$row2[] = '% POB';
foreach ($skuColumns[$brand] ?? [] as $skuCol) {
$row2[] = $skuCol['plan_attr'];
$row2[] = $skuCol['done_attr'];
$row2[] = $skuCol['pct_label'];
}
$col += $span;
}
$excelData = [$row1, $row2];
foreach ($rows as $row) {
$line = [
$row['territory'] ?? '',
$row['kam_emp_code'] ?? '',
$row['kam_name'] ?? '',
];
foreach ($brandGroups as $group) {
$brand = $group['name'];
$brandData = $row['brands'][$brand] ?? [];
$line[] = $this->excelNumber($brandData['plan'] ?? 0);
$line[] = $this->excelNumber($brandData['done'] ?? 0);
$line[] = $this->excelNumber($brandData['pct'] ?? 0, true);
$planMap = $this->metricsByAttribute($brandData['plan_metrics'] ?? []);
$doneMap = $this->metricsByAttribute($brandData['done_metrics'] ?? []);
foreach ($skuColumns[$brand] ?? [] as $skuCol) {
$planVal = $planMap[$skuCol['plan_attr']] ?? 0;
$doneVal = $doneMap[$skuCol['done_attr']] ?? 0;
$pct = ($planVal > 0) ? round(($doneVal / $planVal) * 100, 2) : 0;
$line[] = $this->excelNumber($planVal);
$line[] = $this->excelNumber($doneVal);
$line[] = $this->excelNumber($pct, true);
}
}
$excelData[] = $line;
}
return [
'data' => $excelData,
'merges' => $merges,
'fills' => $fills,
'header_rows' => 2,
'freeze' => 'D3',
];
}
private function brandStyle(array $map, string $name, string $prefix = 'iva-brand-'): array
{
$style = $map[$name] ?? ['bg' => 'D9E2F3', 'fg' => '000000'];
$slug = strtolower(trim(preg_replace('/[^a-z0-9]+/i', '-', $name), '-'));
$style['class'] = $prefix . $slug;
return $style;
}
private function excelCol(int $index): string
{
$letter = '';
while ($index > 0) {
$index--;
$letter = chr(65 + ($index % 26)) . $letter;
$index = intdiv($index, 26);
}
return $letter;
}
private function metricsByAttribute(array $metrics): array
{
$map = [];
foreach ($metrics as $metric) {
$attr = $metric['attribute'] ?? '';
if ($attr === '') {
continue;
}
$map[$attr] = $metric['units'] ?? 0;
}
return $map;
}
private function excelNumber($val, $isPct = false)
{
if ($val === null || $val === '' || !is_numeric($val)) {
return $val ?? 0;
}
$num = (float) $val;
if ($isPct) {
return round($num, 2);
}
return $num == floor($num) ? (int) $num : round($num, 2);
}
private function buildKamExport(array $table): array
{
$brandColumns = $table['brandColumns'] ?? [];
$skuColumns = $table['skuColumns'] ?? [];
$rows = $table['rows'] ?? [];
$headers = ['Territory', 'KAM Emp Code', 'KAM Name', 'Total Brand POB Val'];
$fills = [
'A1:C1' => self::COLOR_ID,
'D1:D1' => self::COLOR_METRIC,
];
$col = 5;
foreach ($brandColumns as $brandCol) {
$key = $brandCol['key'];
$headers[] = $key;
$fills[$this->excelCol($col) . '1'] = self::COLOR_METRIC;
$col++;
foreach ($skuColumns[$key] ?? [] as $attr) {
$headers[] = $attr;
$fills[$this->excelCol($col) . '1'] = self::COLOR_SKU;
$col++;
}
}
$excelData = [$headers];
foreach ($rows as $row) {
$line = [
$row['territory'] ?? '',
$row['kam_emp_code'] ?? '',
$row['kam_name'] ?? '',
$this->excelNumber($row['total'] ?? 0),
];
foreach ($brandColumns as $brandCol) {
$key = $brandCol['key'];
$line[] = $this->excelNumber($row['values'][$key] ?? 0);
$metricMap = $this->metricsByAttribute($row['metrics'][$key] ?? []);
foreach ($skuColumns[$key] ?? [] as $attr) {
$line[] = $this->excelNumber($metricMap[$attr] ?? 0);
}
}
$excelData[] = $line;
}
return [
'data' => $excelData,
'merges' => [],
'fills' => $fills,
'header_rows' => 1,
'freeze' => 'D2',
];
}
private function buildDrBrandExport(array $table): array
{
$brandColumns = $table['brandColumns'] ?? [];
$rows = $table['rows'] ?? [];
$headers = ['Dr Name', 'Dr UID', 'Dr SBU', 'Total Brand POB Val'];
foreach ($brandColumns as $col) {
$headers[] = $col['key'];
}
$lastCol = $this->excelCol(count($headers));
$fills = [
'A1:A1' => self::COLOR_ID,
'B1:C1' => self::COLOR_META,
'D1:' . $lastCol . '1' => self::COLOR_METRIC,
];
$excelData = [$headers];
foreach ($rows as $row) {
$line = [
$row['dr_name'] ?? '',
$row['dr_uid'] ?? '',
$row['dr_sbu'] ?? '',
$this->excelNumber($row['total'] ?? 0),
];
foreach ($brandColumns as $col) {
$line[] = $this->excelNumber($row['values'][$col['key']] ?? 0);
}
$excelData[] = $line;
}
return [
'data' => $excelData,
'merges' => [],
'fills' => $fills,
'header_rows' => 1,
'freeze' => 'B2',
];
}
private function buildSkuExport(array $table): array
{
$groups = $table['skuGroups'] ?? [];
$rows = $table['rows'] ?? [];
$row1 = ['Dr Name'];
$row2 = [''];
$merges = ['A1:A2'];
$fills = [
'A1:A2' => self::COLOR_ID,
];
$col = 2;
foreach ($groups as $group) {
$span = max(1, count($group['columns'] ?? []));
$start = $this->excelCol($col);
$end = $this->excelCol($col + $span - 1);
$merges[] = $start . '1:' . $end . '1';
$fills[$start . '1:' . $end . '1'] = $group['style'] ?? self::COLOR_METRIC;
$fills[$start . '2:' . $end . '2'] = self::COLOR_METRIC;
$row1[] = $group['name'];
for ($i = 1; $i < $span; $i++) {
$row1[] = '';
}
foreach ($group['columns'] as $skuCol) {
$row2[] = $skuCol['key'];
}
$col += $span;
}
$excelData = [$row1, $row2];
foreach ($rows as $row) {
$line = [$row['dr_name'] ?? ''];
foreach ($groups as $group) {
foreach ($group['columns'] as $skuCol) {
$line[] = $this->excelNumber($row['values'][$skuCol['key']] ?? 0, true);
}
}
$excelData[] = $line;
}
return [
'data' => $excelData,
'merges' => $merges,
'fills' => $fills,
'header_rows' => 2,
'freeze' => 'B3',
];
}
private function buildKamTable(array $report, array $metricConfig, array $selectedBrands): array
{
$brandKeys = $this->visibleBrandKeys(self::KAM_BRANDS, $selectedBrands);
$attrToColumn = [];
foreach ($metricConfig as $cfg) {
$cfg = (array) $cfg;
$attr = $cfg['attribute'] ?? '';
$column = $cfg['column'] ?? '';
if ($attr !== '' && $column !== '') {
$attrToColumn[$attr] = $column;
}
}
$skuColumns = [];
foreach ($brandKeys as $key) {
$skuColumns[$key] = [];
}
foreach ($metricConfig as $cfg) {
$cfg = (array) $cfg;
$attr = $cfg['attribute'] ?? '';
$column = $cfg['column'] ?? '';
if ($attr === '' || !in_array($column, $brandKeys, true)) {
continue;
}
if (!in_array($attr, $skuColumns[$column], true)) {
$skuColumns[$column][] = $attr;
}
}
$columns = [];
foreach ($brandKeys as $key) {
$columns[] = ['key' => $key, 'label' => self::KAM_BRANDS[$key]];
}
$rows = [];
foreach ($report as $item) {
$item = (array) $item;
$metricsByColumn = [];
foreach ((array) ($item['metrics'] ?? []) as $metric) {
$metric = (array) $metric;
$column = $attrToColumn[$metric['attribute'] ?? ''] ?? '';
if ($column === '') {
continue;
}
$metricsByColumn[$column][] = [
'attribute' => $metric['attribute'] ?? '',
'units' => $metric['units'] ?? 0,
];
}
$values = [];
foreach ($brandKeys as $key) {
$values[$key] = $item[$key] ?? 0;
}
$rows[] = [
'territory' => $item['territory'] ?? '',
'kam_emp_code' => $item['kam_emp_code'] ?? '',
'kam_name' => $item['kam_name'] ?? '',
'total' => $item['Total Brand POB Val'] ?? 0,
'values' => $values,
'metrics' => $metricsByColumn,
];
}
return ['brandColumns' => $columns, 'rows' => $rows, 'skuColumns' => $skuColumns];
}
private function buildDrBrandTable(array $report, array $selectedBrands): array
{
$brandKeys = $this->visibleBrandKeys(self::KAM_BRANDS, $selectedBrands);
$columns = [];
foreach ($brandKeys as $key) {
$columns[] = ['key' => $key, 'label' => self::KAM_BRANDS[$key]];
}
$rows = [];
foreach ($report as $item) {
$item = (array) $item;
$values = [];
foreach ($brandKeys as $key) {
$values[$key] = $item[$key] ?? 0;
}
$rows[] = [
'dr_name' => $item['Dr Name'] ?? '',
'dr_uid' => $item['Dr UID'] ?? '',
'dr_sbu' => $item['Dr SBU'] ?? '',
'total' => $item['Total Brand POB Val'] ?? 0,
'values' => $values,
];
}
return ['brandColumns' => $columns, 'rows' => $rows];
}
private function buildSkuTable(array $report, array $selectedBrands): array
{
$brandKeys = $this->visibleBrandKeys(self::SKU_BRANDS, $selectedBrands);
$groups = [];
foreach ($brandKeys as $brand) {
$columns = [];
foreach (self::SKU_GROUPS[$brand] as $column) {
$columns[] = [
'key' => $column,
'label' => preg_replace('/ Rate$/', '', $column),
];
}
$groups[] = [
'name' => $brand,
'style' => $this->brandStyle(self::SKU_BRAND_COLORS, $brand, 'iva-sku-'),
'columns' => $columns,
];
}
$rows = [];
foreach ($report as $item) {
$item = (array) $item;
$values = [];
foreach ($groups as $group) {
foreach ($group['columns'] as $col) {
$values[$col['key']] = $item[$col['key']] ?? 0;
}
}
$rows[] = [
'dr_name' => $item['Dr Name'] ?? '',
'values' => $values,
];
}
return ['skuGroups' => $groups, 'rows' => $rows];
}
private function normalizeMetrics(array $metrics): array
{
$normalized = [];
foreach ($metrics as $metric) {
$metric = (array) $metric;
$normalized[] = [
'attribute' => $metric['attribute'] ?? '',
'units' => $metric['units'] ?? 0,
];
}
return $normalized;
}
private static function formatNumber($val, $isPct = false): string
{
if ($val === null || $val === '') {
return '';
}
if (!is_numeric($val)) {
return (string) $val;
}
$num = (float) $val;
if ($isPct) {
if ($num == 0.0) {
return '0';
}
return number_format($num, 2);
}
if (floor($num) == $num) {
return number_format($num);
}
return number_format($num, 2);
}
private static function metricsTitle($metrics): string
{
if (empty($metrics) || !is_array($metrics)) {
return '';
}
$lines = [];
foreach ($metrics as $metric) {
$metric = (array) $metric;
$attr = $metric['attribute'] ?? '';
$units = $metric['units'] ?? 0;
$lines[] = trim($attr . ': ' . self::formatNumber($units));
}
return implode("\n", $lines);
}
}