Files
onboard/app/Exports/Sheets/StatisticsEconomistData.php

297 lines
11 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Exports\Sheets;
use App\Models\User;
use Illuminate\Support\Carbon;
use Maatwebsite\Excel\Concerns\FromArray;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithTitle;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class StatisticsEconomistData implements FromArray, WithHeadings, WithMapping, WithStyles, WithTitle
{
/**
* Сводная строка + строки по отделениям, в порядке отображения на листе.
*/
private array $allRows;
public function __construct(
private string $sheetTitle,
private string $reportName,
private array $rows,
private array $dateRange,
private User $user,
private array $grandTotals = [],
private array $profitTypes = []
) {
$summary = [
['isGroupHeader' => true, 'groupName' => 'СВОДНАЯ ИНФОРМАЦИЯ (ВСЕ ОТДЕЛЕНИЯ)'],
array_merge(['isTotalRow' => true], $this->grandTotals),
];
$this->allRows = array_merge($summary, $this->rows);
}
public function title(): string
{
return $this->sheetTitle;
}
public function array(): array
{
return $this->allRows;
}
/**
* Заголовки (с вложенной структурой)
*/
public function headings(): array
{
$profitTypeLabels = array_values($this->profitTypes);
$countTypes = count($profitTypeLabels);
$topRow = array_merge(
['Отделение', 'Поступило по типу оплаты'],
array_fill(0, max($countTypes - 1, 0), ''),
['Всего']
);
$subRow = array_merge([''], $profitTypeLabels, ['']);
return [
// Шапка отчета (первые 3 строки)
[$this->reportName],
['Дата создания: '.now()->format('d.m.Y H:i:s')],
[$this->formatDateRange()],
[], // Пустая строка для отступа
// Первый уровень заголовков (с объединением)
$topRow,
// Второй уровень заголовков (детализация по типам оплаты)
$subRow,
];
}
/**
* Форматирование дат для шапки
*/
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
{
$profitTypeIds = array_keys($this->profitTypes);
// Заголовок группы (название группы отделений / "Сводная информация")
if (isset($row['isGroupHeader']) && $row['isGroupHeader']) {
return array_merge([$row['groupName']], array_fill(0, count($profitTypeIds) + 1, ''));
}
// Обычное отделение и итоговые строки имеют одинаковую форму: department/counts/total
$counts = $row['counts'] ?? [];
$cells = [$row['department'] ?? ''];
foreach ($profitTypeIds as $profitTypeId) {
$cells[] = $this->formatZero($counts[$profitTypeId] ?? 0);
}
$cells[] = $this->formatZero($row['total'] ?? 0);
return $cells;
}
/**
* Стилизация 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);
$sheet->getPageSetup()->setFitToHeight(1);
$highestRow = $sheet->getHighestRow();
$countTypes = count($this->profitTypes);
$totalColumns = $countTypes + 2; // Отделение + типы оплаты + Всего
$highestColumn = Coordinate::stringFromColumnIndex($totalColumns);
$firstTypeColumn = Coordinate::stringFromColumnIndex(2); // B
$lastTypeColumn = Coordinate::stringFromColumnIndex(1 + $countTypes);
// ОБЪЕДИНЕНИЕ ЯЧЕЕК ДЛЯ ШАПКИ (строки 1-3)
$sheet->mergeCells('A1:'.$highestColumn.'1');
$sheet->mergeCells('A2:'.$highestColumn.'2');
$sheet->mergeCells('A3:'.$highestColumn.'3');
// ОБЪЕДИНЕНИЕ ДЛЯ ОБЫЧНЫХ ЗАГОЛОВКОВ (Отделение, Всего — по одной колонке на 2 строки)
$sheet->mergeCells('A5:A6');
$sheet->mergeCells($highestColumn.'5:'.$highestColumn.'6');
// ОБЪЕДИНЕНИЕ ДЛЯ ВЛОЖЕННОГО ЗАГОЛОВКА "Поступило по типу оплаты"
$sheet->mergeCells($firstTypeColumn.'5:'.$lastTypeColumn.'5');
$sheet->setCellValue($firstTypeColumn.'5', 'Поступило по типу оплаты');
// СТИЛИ ДЛЯ ШАПКИ ОТЧЕТА (строки 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'],
],
],
]);
// СТИЛИ ДЛЯ ВСЕХ СТРОК С ДАННЫМИ
$indexData = 0;
for ($row = 7; $row <= $highestRow; $row++) {
$currentRowInData = $this->allRows[$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->allRows) - 1) {
$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(30); // Отделение
for ($colIndex = 2; $colIndex <= $totalColumns; $colIndex++) {
$sheet->getColumnDimension(Coordinate::stringFromColumnIndex($colIndex))->setWidth(16);
}
// Увеличиваем высоту строк с заголовками
$sheet->getRowDimension(5)->setRowHeight(30);
$sheet->getRowDimension(6)->setRowHeight(25);
return [];
}
/**
* Форматирование нулевых значений
*/
protected function formatZero($value)
{
if (is_null($value) || $value === '' || $value === []) {
return '—';
}
if (is_array($value)) {
return '0';
}
if (is_numeric($value) && $value == 0) {
return '0';
}
return $value;
}
}