Add report exports

This commit is contained in:
Constantin Graf
2024-10-16 13:29:54 +02:00
committed by Constantin Graf
parent e54df74d5d
commit 64535ceea6
12 changed files with 1452 additions and 17 deletions

View File

@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace App\Service\ReportExport;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage;
use League\Csv\Writer;
/**
* @template T of Model
*/
abstract class CsvExport
{
private string $disk;
private string $filename;
private int $chunk;
/**
* @var string[]
*/
public const array HEADER = [];
/**
* @var Builder<T>
*/
private Builder $builder;
/**
* @param Builder<T> $builder
*/
public function __construct(string $disk, string $filename, Builder $builder, int $chunk)
{
$this->disk = $disk;
$this->filename = $filename;
$this->chunk = $chunk;
$this->builder = $builder;
}
/**
* @param T $model
* @return array<string, string|Carbon|null>
*/
abstract public function mapRow(Model $model): array;
public function export(): void
{
$writer = Writer::createFromPath(Storage::disk($this->disk)->path($this->filename), 'w+');
$writer->insertOne(static::HEADER);
$this->builder->chunk($this->chunk, function ($models) use ($writer): void {
foreach ($models as $model) {
$data = $this->mapRow($model);
$row = $this->convertRow($data);
$this->validateRow($row);
$writer->insertOne(array_values($row));
}
});
}
/**
* @param array<string, string|Carbon|null> $data
* @return array<string, string>
*/
private function convertRow(array $data): array
{
$convertedRow = [];
foreach ($data as $key => $value) {
if ($value instanceof Carbon) {
$convertedRow[$key] = $value->toIso8601String();
} elseif ($value === null) {
$convertedRow[$key] = '';
} else {
$convertedRow[$key] = $value;
}
}
return $convertedRow;
}
/**
* @param array<string, string> $row
*
* @throws \Exception
*/
private function validateRow(array $row): void
{
if (array_keys($row) !== self::HEADER) {
throw new \Exception('Invalid row');
}
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace App\Service\ReportExport;
use App\Models\TimeEntry;
use Illuminate\Database\Eloquent\Model;
/**
* @extends CsvExport<TimeEntry>
*/
class TimeEntriesDetailedCsvExport extends CsvExport
{
public const array HEADER = [
'id',
'user_id',
'project_id',
'task_id',
'start_time',
'end_time',
'duration',
'description',
'created_at',
'updated_at',
'deleted_at',
];
/**
* @param TimeEntry $model
*/
public function mapRow(Model $model): array
{
return [
'id' => $model->id,
'user_id' => $model->user_id,
'project_id' => $model->project_id,
'task_id' => $model->task_id,
'start_time' => $model->start,
'end_time' => $model->end,
'description' => $model->description,
'created_at' => $model->created_at,
'updated_at' => $model->updated_at,
];
}
}

View File

@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace App\Service\ReportExport;
use App\Models\TimeEntry;
use Illuminate\Database\Eloquent\Builder;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithCustomCsvSettings;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
/**
* @implements WithMapping<TimeEntry>
*/
class TimeEntriesDetailedExport implements FromQuery, WithCustomCsvSettings, WithHeadings, WithMapping
{
use Exportable;
/**
* @var Builder<TimeEntry>
*/
private Builder $builder;
/**
* @param Builder<TimeEntry> $builder
*/
public function __construct(Builder $builder)
{
$this->builder = $builder;
}
/**
* @return Builder<TimeEntry>
*/
public function query(): Builder
{
return $this->builder;
}
/**
* @return array<string, string|bool>
*/
public function getCsvSettings(): array
{
return [
'delimiter' => ',',
'use_bom' => false,
'output_encoding' => 'ISO-8859-1',
];
}
/**
* @return string[]
*/
public function headings(): array
{
return [
'Description',
'Task',
'Project',
'Client',
'User',
'Start date',
'Start time',
'End date',
'End time',
'Duration',
'Duration (decimal)',
'Billable',
'Tags',
];
}
/**
* @param TimeEntry $model
* @return array<int, string|float|null>
*/
public function map($model): array
{
$duration = $model->getDuration();
return [
$model->description,
$model->task?->name,
$model->project?->name,
$model->project?->client?->name,
$model->user->name,
$model->start->format('Y-m-d'),
$model->start->format('H:i:s'),
$model->end?->format('Y-m-d'),
$model->end?->format('H:i:s'),
$duration !== null ? (int) floor($duration->totalHours).':'.$duration->format('%I:%S') : null,
$duration?->totalHours,
$model->billable ? 'Yes' : 'No',
$model->tagsRelation->pluck('name')->implode(', '),
];
}
}