data = $data; $this->dateRange = $dateRange; $this->reportName = $reportName; } /** * @return \Illuminate\Support\Collection */ public function collection() { return collect($this->data); } /** * Заголовки (с вложенной структурой) */ public function headings(): array { return [ // Шапка отчета (первые 3 строки) [$this->reportName], ['Дата создания: ' . now()->format('d.m.Y H:i:s')], [$this->formatDateRange()], [], // Пустая строка для отступа // Первый уровень заголовков (с объединением) [ 'Отделение', 'Кол-во коек', 'Поступило', // Будет объединено с 4 колонками '', '', '', // Пустые для заполнения 'Выбыло', 'Состоит', 'Ср. койко-день', 'Пред. опер. койко-день', '% загруженности', '% смертности', 'Операции', // Будет объединено с 2 колонками '', // Пустые для заполнения 'Умерло', 'Мед. персонал' ], // Второй уровень заголовков (детализация) [ '', '', 'Всего', 'План', 'Экстр', 'Перевод', '', '', '', '', '', '', 'Э', 'П', '', '' ] ]; } /** * Форматирование дат для шапки */ protected function formatDateRange(): string { if (isset($this->dateRange[0]) && isset($this->dateRange[1])) { $startAt = Carbon::create($this->dateRange[0])->format('d.m.Y H:i'); $endAt = Carbon::create($this->dateRange[1])->format('d.m.Y H:i'); return 'Период: ' . $startAt . ' - ' . $endAt; } return 'Период: За весь период'; } /** * Маппинг данных для каждой строки */ public function map($row): array { // Заголовок группы (Хирургические отделения, Терапевтические отделения) if (isset($row['isGroupHeader']) && $row['isGroupHeader']) { return [ $row['groupName'], // Название группы '', '', '', '', '', '', '', '', '', '', '', '', '' ]; } // Итоговая строка if (isset($row['isTotalRow']) && $row['isTotalRow']) { return [ $row['department'], $row['beds'] ?? '', $this->formatZero($row['recipients']['all'] ?? 0), $this->formatZero($row['recipients']['plan'] ?? 0), $this->formatZero($row['recipients']['emergency'] ?? 0), $this->formatZero($row['recipients']['transferred'] ?? 0), $this->formatZero($row['outcome'] ?? 0), $this->formatZero($row['consist'] ?? 0), $this->formatZero($row['averageBedDays'] ?? 0), $this->formatZero($row['preoperativeDays'] ?? 0), $this->formatZero($row['percentLoadedBeds'] ?? 0), $this->formatZero($row['overallLethality'] ?? 0), $this->formatZero($row['surgical']['emergency'] ?? 0), $this->formatZero($row['surgical']['plan'] ?? 0), $this->formatZero($row['deceased'] ?? 0), $this->formatZero($row['countStaff'] ?? 0) ]; } // Обычное отделение return [ $row['department'] ?? '', $this->formatZero($row['beds'] ?? 0), $this->formatZero($row['recipients']['all'] ?? 0), $this->formatZero($row['recipients']['plan'] ?? 0), $this->formatZero($row['recipients']['emergency'] ?? 0), $this->formatZero($row['recipients']['transferred'] ?? 0), $this->formatZero($row['outcome'] ?? 0), $this->formatZero($row['consist'] ?? 0), $this->formatZero($row['averageBedDays'] ?? 0), $this->formatZero($row['preoperativeDays'] ?? 0), $this->formatZero($row['percentLoadedBeds'] ?? 0), $this->formatZero($row['lethality'] ?? 0), $this->formatZero($row['surgical']['emergency'] ?? 0), $this->formatZero($row['surgical']['plan'] ?? 0), $this->formatZero($row['deceased'] ?? 0), $this->formatZero($row['countStaff'] ?? 0) ]; } /** * Стилизация Excel файла */ public function styles(Worksheet $sheet) { // Поля (уменьшаем для экономии места) $sheet->getPageMargins()->setLeft(0.2); $sheet->getPageMargins()->setRight(0.2); $sheet->getPageMargins()->setTop(0.2); $sheet->getPageMargins()->setBottom(0.2); $sheet->getPageSetup()->setPaperSize(PageSetup::PAPERSIZE_A4); $sheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); $sheet->getPageSetup()->setFitToPage(true); $sheet->getPageSetup()->setFitToWidth(1); // 1 страница в ширину $sheet->getPageSetup()->setFitToHeight(1); // 1 страница в высоту (опционально) // $sheet->getPageSetup()->setScale(90); $highestRow = $sheet->getHighestRow(); $highestColumn = $sheet->getHighestColumn(); // ОБЪЕДИНЕНИЕ ЯЧЕЕК ДЛЯ ШАПКИ (строки 1-3) $sheet->mergeCells('A1:' . $highestColumn . '1'); // Наименование $sheet->mergeCells('A2:' . $highestColumn . '2'); // Дата создания $sheet->mergeCells('A3:' . $highestColumn . '3'); // Временной интервал // ОБЪЕДИНЕНИЕ ДЛЯ ОБЫЧНЫХ ЗАГОЛОВКОВ $sheet->mergeCells('A5:A6'); $sheet->mergeCells('B5:B6'); $sheet->mergeCells('G5:G6'); $sheet->mergeCells('H5:H6'); $sheet->mergeCells('I5:I6'); $sheet->mergeCells('J5:J6'); $sheet->mergeCells('K5:K6'); $sheet->mergeCells('L5:L6'); $sheet->mergeCells('O5:O6'); $sheet->mergeCells('P5:P6'); // ОБЪЕДИНЕНИЕ ДЛЯ ВЛОЖЕННЫХ ЗАГОЛОВКОВ // Строка 5 (первый уровень заголовков) $sheet->mergeCells('C5:F5'); // Объединяем "Поступило" (колонки C, D, E, F) $sheet->mergeCells('M5:N5'); // Объединяем "Операции" (колонки M, N) // Устанавливаем значения для объединенных ячеек $sheet->setCellValue('C5', 'Поступило'); $sheet->setCellValue('M5', 'Операции'); // СТИЛИ ДЛЯ ШАПКИ ОТЧЕТА (строки 1-3) $sheet->getStyle('A1:A3')->applyFromArray([ 'font' => [ 'bold' => true, 'size' => 14, ], 'alignment' => [ 'horizontal' => Alignment::HORIZONTAL_LEFT, 'vertical' => Alignment::VERTICAL_CENTER, ], ]); // СТИЛИ ДЛЯ ЗАГОЛОВКОВ (строки 5 и 6) $sheet->getStyle('A5:' . $highestColumn . '6')->applyFromArray([ 'font' => [ 'bold' => true, 'color' => ['argb' => '000000'], 'size' => 11, ], 'alignment' => [ 'horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER, 'wrapText' => true, ], 'borders' => [ 'allBorders' => [ 'borderStyle' => Border::BORDER_THIN, 'color' => ['argb' => '2d2d30'], ], ], ]); // ДОПОЛНИТЕЛЬНЫЕ СТИЛИ ДЛЯ ОБЪЕДИНЕННЫХ ЗАГОЛОВКОВ $sheet->getStyle('C5')->applyFromArray([ 'font' => ['bold' => true, 'color' => ['argb' => '000000']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], ]); $sheet->getStyle('M5')->applyFromArray([ 'font' => ['bold' => true, 'color' => ['argb' => '000000']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], ]); // СТИЛИ ДЛЯ ВСЕХ СТРОК С ДАННЫМИ $indexData = 0; for ($row = 7; $row <= $highestRow; $row++) { $cellValue = $sheet->getCell('A' . $row)->getValue(); $currentRowInData = $this->data[$indexData]; // Заголовки групп (Хирургические отделения, Терапевтические отделения) if (array_key_exists('isGroupHeader', $currentRowInData)) { $sheet->getStyle('A' . $row . ':' . $highestColumn . $row)->applyFromArray([ 'font' => [ 'bold' => true, 'size' => 11, ], 'fill' => [ 'fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'D9E1F2'], // Светло-синий ], ]); // Объединяем ячейки для группы $sheet->mergeCells('A' . $row . ':' . $highestColumn . $row); } // Итоговые строки (ИТОГО:) if (array_key_exists('isTotalRow', $currentRowInData)) { $sheet->getStyle('A' . $row . ':' . $highestColumn . $row)->applyFromArray([ 'font' => [ 'bold' => true, ], 'fill' => [ 'fillType' => Fill::FILL_SOLID, 'startColor' => ['argb' => 'F2F2F2'], // Серый ], 'borders' => [ 'top' => [ 'borderStyle' => Border::BORDER_MEDIUM, 'color' => ['argb' => '000000'], ], ], ]); } if ($indexData < count($this->data)) $indexData++; } // ГРАНИЦЫ ДЛЯ ВСЕЙ ТАБЛИЦЫ $sheet->getStyle('A5:' . $highestColumn . $highestRow)->applyFromArray([ 'borders' => [ 'allBorders' => [ 'borderStyle' => Border::BORDER_THIN, 'color' => ['argb' => '000000'], ], ], ]); // ВЫРАВНИВАНИЕ ДЛЯ ЧИСЛОВЫХ КОЛОНОК $sheet->getStyle('B7:' . $highestColumn . $highestRow)->applyFromArray([ 'alignment' => [ 'horizontal' => Alignment::HORIZONTAL_CENTER, ], ]); // ВЫРАВНИВАНИЕ ДЛЯ НАЗВАНИЙ ОТДЕЛЕНИЙ $sheet->getStyle('A7:A' . $highestRow)->applyFromArray([ 'alignment' => [ 'horizontal' => Alignment::HORIZONTAL_LEFT, 'vertical' => Alignment::VERTICAL_CENTER, ], ]); // Устанавливаем ширину колонок $sheet->getColumnDimension('A')->setWidth(25); // Отделение $sheet->getColumnDimension('B')->setWidth(8); // Кол-во коек $sheet->getColumnDimension('C')->setWidth(8); // Всего $sheet->getColumnDimension('D')->setWidth(8); // План $sheet->getColumnDimension('E')->setWidth(8); // Экстр $sheet->getColumnDimension('F')->setWidth(10); // Перевод $sheet->getColumnDimension('G')->setWidth(10); // Выбыло $sheet->getColumnDimension('H')->setWidth(10); // Состоит $sheet->getColumnDimension('I')->setWidth(10); // Ср. койко-день $sheet->getColumnDimension('J')->setWidth(10); // Пред. опер. койко-день $sheet->getColumnDimension('K')->setWidth(10); // % загруженности $sheet->getColumnDimension('L')->setWidth(8); // % летальности $sheet->getColumnDimension('M')->setWidth(8); // Операции Э $sheet->getColumnDimension('N')->setWidth(8); // Операции П $sheet->getColumnDimension('O')->setWidth(10); // Умерло $sheet->getColumnDimension('P')->setWidth(10); // Мед. персонал // Увеличиваем высоту строк с заголовками $sheet->getRowDimension(5)->setRowHeight(30); $sheet->getRowDimension(6)->setRowHeight(25); // Добавляем примечание внизу // $this->addFootnote($sheet, $highestRow, $highestColumn); return []; } /** * Форматирование нулевых значений */ protected function formatZero($value) { // Если значение null или пустая строка - возвращаем "0" if (is_null($value) || $value === '' || $value === []) { return '—'; } // Если это массив (для recipients или surgical) - такого не должно быть, // но на всякий случай обработаем if (is_array($value)) { return '0'; } // Если это число с плавающей точкой и оно равно 0 if (is_numeric($value) && $value == 0) { return '0'; } return $value; } /** * Добавить примечание в конец файла */ public function addFootnote(Worksheet $sheet, int $startRow, string $highestColumn): void { $footnoteRow = $startRow + 2; // Отступаем 2 строки от таблицы $sheet->setCellValue('A' . $footnoteRow, 'ПРИМЕЧАНИЕ:'); $sheet->setCellValue('B' . $footnoteRow, '• % загруженности актуален на дату формирования отчёта (' . now()->format('d.m.Y') . ')'); $sheet->mergeCells('B' . $footnoteRow . ':' . $highestColumn . $footnoteRow); $sheet->setCellValue('B' . ($footnoteRow + 1), '• Поступление, выбытие, операции — за указанный в шапке период'); $sheet->mergeCells('B' . ($footnoteRow + 1) . ':' . $highestColumn . ($footnoteRow + 1)); // Стили для примечания $sheet->getStyle('A' . $footnoteRow . ':' . $highestColumn . ($footnoteRow + 1))->applyFromArray([ 'font' => [ 'italic' => true, 'size' => 9, 'color' => ['argb' => '666666'], ], 'alignment' => [ 'vertical' => Alignment::VERTICAL_CENTER, ], ]); // Жирный шрифт для слова "ПРИМЕЧАНИЕ" $sheet->getStyle('A' . $footnoteRow)->applyFromArray([ 'font' => [ 'bold' => true, 'color' => ['argb' => '000000'], ], ]); } }