*/ private $abmRows; /** @var array */ private $rbmRows; /** @var array */ private $tmRows; /** @var array> */ private $dayTemplate; private $month; public function __construct($data, string $month) { $this->abmRows = $this->listRows($data, 'abmReport'); $this->rbmRows = $this->listRows($data, 'rbmReport'); $this->tmRows = $this->listRows($data, 'tmReport'); $this->dayTemplate = $this->dayTemplate(); $this->month = $month; } public function download(): StreamedResponse { $spreadsheet = $this->spreadsheet(); $filename = $this->filename(); return response()->streamDownload(function () use ($spreadsheet) { $writer = new Xlsx($spreadsheet); $writer->save('php://output'); $spreadsheet->disconnectWorksheets(); }, $filename, [ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ]); } /** * @return array> */ public function sections(): array { return [ $this->abmSection(), $this->regionSection(), $this->territorySection(), ]; } public function filename(): string { $slug = trim((string) preg_replace('/[^A-Za-z0-9]+/', '-', $this->month), '-'); return 'Adherence-Pack-POB-Drive-' . ($slug !== '' ? $slug : 'report') . '.xlsx'; } public function spreadsheet(): Spreadsheet { $spreadsheet = new Spreadsheet(); $abm = $spreadsheet->getActiveSheet(); $abm->setTitle('Details ABM wise'); $this->writeAbmSheet($abm); $region = $spreadsheet->createSheet(); $region->setTitle('Region Daily Winners'); $this->writeRegionSheet($region); $territory = $spreadsheet->createSheet(); $territory->setTitle('Territory Daily Winners'); $this->writeTerritorySheet($territory); $spreadsheet->setActiveSheetIndex(0); return $spreadsheet; } private function writeAbmSheet($sheet): void { $identity = [ 'ZBM', 'RBM', 'ABM HQ', 'HQ Div', 'ABM Name', 'Emp ID', 'Reporting Manager (RBM)', 'Email', 'Approved Count per ABM', 'Remarks', ]; $summary = [ 'Working Days', 'Daily Target (POBs = Approved Count)', 'Month Target (POBs)', 'Total TMs Qualified (≥₹15k POB)', 'Achievement %', 'Total Chemists Qualified (≥₹15k POB)', 'Days Daily Target Met', 'Target Status', ]; $summaryHelp = [ 'No. of Total W', 'Count of Active TMs', 'No. of Total W x Count of Active TMs', 'Sum of count of all Days', 'Total TMs Qualified / Month Target (POBs)', 'No. of Unique Chemists with ≥₹15k POB Value', 'No. of Days on which Daily Target is achieved', 'If Month Target achieved = Achieved, If Month Target not achieved = In Progress', ]; $dayStart = count($identity) + 1; $summaryStart = $dayStart + count($this->dayTemplate); $lastCol = $summaryStart + count($summary) - 1; $sheet->setCellValue('A1', 'Adherence Pack POB Drive – ABM – ' . $this->month); $sheet->mergeCells('A1:' . $this->col($lastCol) . '1'); $sheet->setCellValue('A3', 'Legend: pink = vacant HQ; green day = working day; grey day = holiday.'); $sheet->mergeCells('A3:' . $this->col($lastCol) . '3'); $sheet->setCellValue('A5', 'Data of no. of chemists with Adherence Pack POB ≥ ₹15,000'); $sheet->setCellValue('J5', 'Day type (W / H) →'); foreach ($this->dayTemplate as $index => $day) { $sheet->setCellValue($this->col($dayStart + $index) . '5', $day['type'] === 'H' ? 'H' : 'W'); } $help = [ 'Zone', 'Region', 'Area', 'as it is', 'ABM Emp Name', 'ABM Emp Code', 'RBM Name', 'ABM Email ID', 'Count of Active TMs', 'Blank Remarks Column', ]; $this->writeValues($sheet, 6, $help); if (count($this->dayTemplate) > 0) { $sheet->setCellValue($this->col($dayStart) . '6', 'No. of TMs with at least 1 Chemist with ≥₹15k POB Value'); if (count($this->dayTemplate) > 1) { $sheet->mergeCells($this->col($dayStart) . '6:' . $this->col($summaryStart - 1) . '6'); } } $this->writeValues($sheet, 6, $summaryHelp, $summaryStart); $headers = $identity; foreach ($this->dayTemplate as $day) { $headers[] = $this->dayHeader($day['label']); } $headers = array_merge($headers, $summary); $this->writeValues($sheet, 7, $headers); $this->styleHeaderRow($sheet, 7, $lastCol); $this->paintDayTypes($sheet, 5, $dayStart, false); $this->paintDayHeaders($sheet, 7, $dayStart, false); $rowNumber = 8; foreach ($this->abmRows as $row) { $values = []; foreach ($identity as $key) { $values[] = $this->field($row, $key); } $dayMap = $this->dayMap($this->field($row, 'days')); foreach ($this->dayTemplate as $day) { $values[] = $this->dayValue($dayMap[$day['date']] ?? $day, 'value'); } foreach ($summary as $key) { $values[] = $this->field($row, $key); } $this->writeValues($sheet, $rowNumber, $values); $this->paintDataDays($sheet, $rowNumber, $dayStart, false); if ($this->isVacantName($this->field($row, 'ABM HQ')) || $this->isVacantName($this->field($row, 'ABM Name'))) { $this->paintRow($sheet, $rowNumber, $lastCol, self::PINK); } $rowNumber++; } $this->finishSheet($sheet, 7, max(7, $rowNumber - 1), $lastCol, $dayStart); $sheet->freezePane('A8'); } private function writeRegionSheet($sheet): void { $columns = $this->regionColumns(); $lastCol = 3 + count($columns); $sheet->setCellValue('A1', 'Region-wise Daily Winner – Highest no. of chemists with Adherence Pack POB ≥ ₹15,000 – ' . $this->month); $sheet->mergeCells('A1:' . $this->col($lastCol) . '1'); $typeRow = ['', '', '']; $helpRow = ['', '', '']; $headerRow = ['Region (RBM)', 'RBM Name', 'RBM Employee Code']; $pairHelp = 0; foreach ($columns as $column) { if ($column['kind'] === 'total-tm' || $column['kind'] === 'total-chemist') { $typeRow[] = 'Sum of count of all Days'; $helpRow[] = $column['kind'] === 'total-tm' ? 'No. of TMs with at least 1 Chemist with ≥₹15k POB Value' : 'No. of Unique Chemists with ≥₹15k POB Value'; $headerRow[] = $column['header']; continue; } $typeRow[] = $column['type'] === 'H' ? 'H' : 'W'; if ($pairHelp === 0 && $column['kind'] === 'tm') { $helpRow[] = 'No. of TMs with at least 1 Chemist with ≥₹15k POB Value'; $pairHelp = 1; } elseif ($pairHelp === 1 && $column['kind'] === 'chemist') { $helpRow[] = 'No. of Unique Chemists with ≥₹15k POB Value'; $pairHelp = 2; } else { $helpRow[] = ''; } $headerRow[] = $column['header']; } $this->writeValues($sheet, 2, $typeRow); $this->writeValues($sheet, 3, $helpRow); $this->writeValues($sheet, 4, $headerRow); if (count($columns) >= 2 && $columns[count($columns) - 1]['kind'] === 'total-chemist') { $sheet->mergeCells($this->col($lastCol - 1) . '2:' . $this->col($lastCol) . '2'); } $this->styleHeaderRow($sheet, 4, $lastCol); $this->paintRegionDayMarks($sheet, $columns); $rowNumber = 5; foreach ($this->rbmRows as $row) { $dayMap = $this->dayMap($this->field($row, 'days')); $values = [ $this->field($row, 'Region (RBM)'), $this->field($row, 'RBM Name'), $this->field($row, 'RBM Employee Code'), ]; foreach ($columns as $column) { if ($column['kind'] === 'total-tm') { $values[] = $this->field($row, 'Total TMs Qualified (≥₹15k POB)'); continue; } if ($column['kind'] === 'total-chemist') { $values[] = $this->field($row, 'Total Chemists Qualified (≥₹15k POB)'); continue; } $day = $dayMap[$column['date']] ?? $column['day']; $values[] = $column['kind'] === 'holiday' ? 'Holiday' : $this->dayValue($day, $column['kind'] === 'chemist' ? 'chemistsQualified' : 'value'); } $this->writeValues($sheet, $rowNumber, $values); $this->paintRegionDataDays($sheet, $rowNumber, $columns); if ($this->isVacantName($values[0]) || $this->isVacantName($values[1])) { $this->paintRow($sheet, $rowNumber, $lastCol, self::PINK); } $rowNumber++; } $this->finishSheet($sheet, 4, max(4, $rowNumber - 1), $lastCol, 4); $sheet->freezePane('A5'); } private function writeTerritorySheet($sheet): void { $dayStart = 4; $totalCol = $dayStart + count($this->dayTemplate); $sheet->setCellValue('A1', 'Territory-wise Daily Winner – Highest no. of chemists with Adherence Pack POB ≥ ₹15,000 – ' . $this->month); $sheet->mergeCells('A1:' . $this->col($totalCol) . '1'); $typeRow = ['', '', '']; foreach ($this->dayTemplate as $day) { $typeRow[] = $day['type'] === 'H' ? 'H' : 'W'; } $this->writeValues($sheet, 3, $typeRow); if (count($this->dayTemplate) > 0) { $sheet->setCellValue('D4', 'No. of Unique Chemists with ≥₹15k POB Value'); } $sheet->setCellValue($this->col($totalCol) . '4', 'Sum of all Days of No. of Unique Chemists with ≥₹15k POB Value'); $headers = ['Territory', 'TM Name', 'TM Employee Code']; foreach ($this->dayTemplate as $day) { $headers[] = $this->dayHeader($day['label']); } $headers[] = 'Total Chemists Qualified (≥₹15k POB)'; $this->writeValues($sheet, 5, $headers); $this->styleHeaderRow($sheet, 5, $totalCol); $this->paintDayTypes($sheet, 3, $dayStart, true); $this->paintDayHeaders($sheet, 5, $dayStart, true); $rowNumber = 6; foreach ($this->tmRows as $row) { $dayMap = $this->dayMap($this->field($row, 'days')); $values = [ $this->field($row, 'Territory'), $this->field($row, 'TM Name'), $this->field($row, 'TM Employee Code'), ]; foreach ($this->dayTemplate as $day) { $values[] = $this->dayValue($dayMap[$day['date']] ?? $day, 'value'); } $values[] = $this->field($row, 'Total Chemists Qualified (≥₹15k POB)'); $this->writeValues($sheet, $rowNumber, $values); $this->paintDataDays($sheet, $rowNumber, $dayStart, true); if ($this->isVacantName($values[0]) || $this->isVacantName($values[1])) { $this->paintRow($sheet, $rowNumber, $totalCol, self::PINK); } $rowNumber++; } $this->finishSheet($sheet, 5, max(5, $rowNumber - 1), $totalCol, $dayStart); $sheet->freezePane('A6'); } /** * @return array */ private function abmSection(): array { $identity = [ 'ZBM', 'RBM', 'ABM HQ', 'HQ Div', 'ABM Name', 'Emp ID', 'Reporting Manager (RBM)', 'Email', 'Approved Count per ABM', 'Remarks', ]; $summary = [ 'Working Days', 'Daily Target (POBs = Approved Count)', 'Month Target (POBs)', 'Total TMs Qualified (≥₹15k POB)', 'Achievement %', 'Total Chemists Qualified (≥₹15k POB)', 'Days Daily Target Met', 'Target Status', ]; $dayTypes = array_fill(0, count($identity), ''); $headers = $identity; foreach ($this->dayTemplate as $day) { $dayTypes[] = $day['type'] === 'H' ? 'H' : 'W'; $headers[] = $this->dayHeader($day['label']); } $dayTypes = array_merge($dayTypes, array_fill(0, count($summary), '')); $headers = array_merge($headers, $summary); $rows = []; foreach ($this->abmRows as $row) { $cells = []; foreach ($identity as $key) { $cells[] = $this->field($row, $key); } $dayMap = $this->dayMap($this->field($row, 'days')); foreach ($this->dayTemplate as $day) { $cells[] = $this->dayValue($dayMap[$day['date']] ?? $day, 'value'); } foreach ($summary as $key) { $cells[] = $this->field($row, $key); } $rows[] = [ 'cells' => $cells, 'vacant' => $this->isVacantName($this->field($row, 'ABM HQ')) || $this->isVacantName($this->field($row, 'ABM Name')), ]; } return [ 'id' => 'abm', 'title' => 'Details ABM wise – ' . $this->month, 'dayTypes' => $dayTypes, 'headers' => $headers, 'rows' => $rows, ]; } /** * @return array */ private function regionSection(): array { $columns = $this->regionColumns(); $dayTypes = ['', '', '']; $headers = ['Region (RBM)', 'RBM Name', 'RBM Employee Code']; foreach ($columns as $column) { $dayTypes[] = $column['type'] === 'H' ? 'H' : ($column['type'] === 'W' ? 'W' : ''); $headers[] = $column['header']; } $rows = []; foreach ($this->rbmRows as $row) { $dayMap = $this->dayMap($this->field($row, 'days')); $cells = [ $this->field($row, 'Region (RBM)'), $this->field($row, 'RBM Name'), $this->field($row, 'RBM Employee Code'), ]; foreach ($columns as $column) { if ($column['kind'] === 'total-tm') { $cells[] = $this->field($row, 'Total TMs Qualified (≥₹15k POB)'); continue; } if ($column['kind'] === 'total-chemist') { $cells[] = $this->field($row, 'Total Chemists Qualified (≥₹15k POB)'); continue; } $day = $dayMap[$column['date']] ?? $column['day']; $cells[] = $column['kind'] === 'holiday' ? 'Holiday' : $this->dayValue($day, $column['kind'] === 'chemist' ? 'chemistsQualified' : 'value'); } $rows[] = [ 'cells' => $cells, 'vacant' => $this->isVacantName($cells[0]) || $this->isVacantName($cells[1]), ]; } return [ 'id' => 'region', 'title' => 'Region Daily Winners – ' . $this->month, 'dayTypes' => $dayTypes, 'headers' => $headers, 'rows' => $rows, ]; } /** * @return array */ private function territorySection(): array { $dayTypes = ['', '', '']; $headers = ['Territory', 'TM Name', 'TM Employee Code']; foreach ($this->dayTemplate as $day) { $dayTypes[] = $day['type'] === 'H' ? 'H' : 'W'; $headers[] = $this->dayHeader($day['label']); } $dayTypes[] = ''; $headers[] = 'Total Chemists Qualified (≥₹15k POB)'; $rows = []; foreach ($this->tmRows as $row) { $dayMap = $this->dayMap($this->field($row, 'days')); $cells = [ $this->field($row, 'Territory'), $this->field($row, 'TM Name'), $this->field($row, 'TM Employee Code'), ]; foreach ($this->dayTemplate as $day) { $cells[] = $this->dayValue($dayMap[$day['date']] ?? $day, 'value'); } $cells[] = $this->field($row, 'Total Chemists Qualified (≥₹15k POB)'); $rows[] = [ 'cells' => $cells, 'vacant' => $this->isVacantName($cells[0]) || $this->isVacantName($cells[1]), ]; } return [ 'id' => 'territory', 'title' => 'Territory Daily Winners – ' . $this->month, 'dayTypes' => $dayTypes, 'headers' => $headers, 'rows' => $rows, ]; } /** * @return array> */ private function regionColumns(): array { $columns = []; foreach ($this->dayTemplate as $day) { $header = $this->dayHeader($day['label']); if ($day['type'] === 'H') { $columns[] = [ 'kind' => 'holiday', 'type' => 'H', 'date' => $day['date'], 'header' => $header, 'day' => $day, ]; continue; } $columns[] = [ 'kind' => 'tm', 'type' => 'W', 'date' => $day['date'], 'header' => $header, 'day' => $day, ]; $columns[] = [ 'kind' => 'chemist', 'type' => 'W', 'date' => $day['date'], 'header' => $header, 'day' => $day, ]; } $columns[] = ['kind' => 'total-tm', 'type' => '', 'header' => 'Total TMs Qualified (≥₹15k POB)']; $columns[] = ['kind' => 'total-chemist', 'type' => '', 'header' => 'Total Chemists Qualified (≥₹15k POB)']; return $columns; } /** * @return array> */ private function dayTemplate(): array { foreach ([$this->abmRows, $this->rbmRows, $this->tmRows] as $rows) { if ($rows === []) { continue; } $days = $this->orderedDays($this->field($rows[0], 'days')); if ($days !== []) { return $days; } } return []; } /** * @param mixed $days * @return array> */ private function orderedDays($days): array { if (is_object($days)) { $days = (array) $days; } if (!is_array($days)) { return []; } $list = []; foreach ($days as $label => $day) { if (is_object($day)) { $day = (array) $day; } if (!is_array($day)) { continue; } $list[] = [ 'label' => (string) $label, 'date' => (string) ($day['date'] ?? ''), 'type' => (string) ($day['type'] ?? 'W'), 'value' => $day['value'] ?? 0, 'chemistsQualified' => $day['chemistsQualified'] ?? 0, ]; } usort($list, function ($a, $b) { return strcmp($a['date'], $b['date']); }); return $list; } /** * @param mixed $days * @return array> */ private function dayMap($days): array { $map = []; foreach ($this->orderedDays($days) as $day) { if ($day['date'] !== '') { $map[$day['date']] = $day; } } return $map; } /** * @param array $day * @return mixed */ private function dayValue(array $day, string $field) { if (($day['type'] ?? '') === 'H' || ($day[$field] ?? null) === 'Holiday') { return 'Holiday'; } $value = $day[$field] ?? 0; return is_numeric($value) ? 0 + $value : $value; } private function dayHeader(string $label): string { $parts = preg_split('/\s+/', trim($label), 2); if (is_array($parts) && count($parts) === 2) { return $parts[0] . "\n" . $parts[1]; } return $label; } /** * @param mixed $data * @return array */ private function listRows($data, string $key): array { $rows = $this->field($data, $key); if (is_object($rows)) { $rows = (array) $rows; } return is_array($rows) ? array_values($rows) : []; } /** * @param mixed $row * @return mixed */ private function field($row, string $key) { if (is_array($row)) { return $row[$key] ?? ''; } if (is_object($row) && isset($row->{$key})) { return $row->{$key}; } return ''; } private function isVacantName($name): bool { return stripos(trim((string) $name), 'vacant') !== false; } /** * @param array $values */ private function writeValues($sheet, int $row, array $values, int $startColumn = 1): void { foreach ($values as $offset => $value) { $sheet->setCellValue($this->col($startColumn + $offset) . $row, $value); } } private function paintDayTypes($sheet, int $row, int $startColumn, bool $singleColumn): void { foreach ($this->dayTemplate as $index => $day) { $this->paintCell($sheet, $this->col($startColumn + $index) . $row, $day['type'] === 'H' ? self::GREY : self::GREEN); } if (!$singleColumn) { return; } } private function paintDayHeaders($sheet, int $row, int $startColumn, bool $singleColumn): void { foreach ($this->dayTemplate as $index => $day) { $this->paintCell($sheet, $this->col($startColumn + $index) . $row, $day['type'] === 'H' ? self::GREY : self::GREEN); } if ($singleColumn) { return; } } private function paintDataDays($sheet, int $row, int $startColumn, bool $singleColumn): void { foreach ($this->dayTemplate as $index => $day) { if ($day['type'] === 'H') { $this->paintCell($sheet, $this->col($startColumn + $index) . $row, self::GREY); } } if ($singleColumn) { return; } } /** * @param array> $columns */ private function paintRegionDayMarks($sheet, array $columns): void { foreach ($columns as $index => $column) { if ($column['kind'] === 'total-tm' || $column['kind'] === 'total-chemist') { continue; } $cell = $this->col(4 + $index); $color = $column['type'] === 'H' ? self::GREY : self::GREEN; $this->paintCell($sheet, $cell . '2', $color); $this->paintCell($sheet, $cell . '4', $color); } } /** * @param array> $columns */ private function paintRegionDataDays($sheet, int $row, array $columns): void { foreach ($columns as $index => $column) { if (($column['type'] ?? '') === 'H') { $this->paintCell($sheet, $this->col(4 + $index) . $row, self::GREY); } } } private function paintRow($sheet, int $row, int $lastColumn, string $color): void { $sheet->getStyle('A' . $row . ':' . $this->col($lastColumn) . $row)->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setARGB($color); } private function paintCell($sheet, string $cell, string $color): void { $sheet->getStyle($cell)->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setARGB($color); } private function styleHeaderRow($sheet, int $row, int $lastColumn): void { $range = 'A' . $row . ':' . $this->col($lastColumn) . $row; $sheet->getStyle($range)->getFont()->setBold(true); $sheet->getStyle($range)->getAlignment()->setWrapText(true)->setVertical(Alignment::VERTICAL_CENTER)->setHorizontal(Alignment::HORIZONTAL_CENTER); $sheet->getRowDimension($row)->setRowHeight(32); } private function finishSheet($sheet, int $headerRow, int $lastRow, int $lastColumn, int $dayStart): void { $range = 'A1:' . $this->col($lastColumn) . $lastRow; $sheet->getStyle($range)->getAlignment()->setVertical(Alignment::VERTICAL_CENTER); $sheet->getStyle('A' . $headerRow . ':' . $this->col($lastColumn) . $lastRow)->getBorders()->getAllBorders()->setBorderStyle(Border::BORDER_THIN); $sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14); for ($column = 1; $column <= $lastColumn; $column++) { $width = $column < $dayStart ? 22 : 14; $sheet->getColumnDimension($this->col($column))->setWidth($width); } } private function col(int $index): string { return Coordinate::stringFromColumnIndex($index); } }