mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-15 19:52:15 +01:00
Add pdf detailed report and placeholder for aggregate endpoint
This commit is contained in:
committed by
Constantin Graf
parent
5593d141ea
commit
b0bcc4f330
@@ -5,10 +5,13 @@ declare(strict_types=1);
|
||||
namespace App\Service\ReportExport;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\File;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use League\Csv\Writer;
|
||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||
|
||||
/**
|
||||
* @template T of Model
|
||||
@@ -31,16 +34,19 @@ abstract class CsvExport
|
||||
*/
|
||||
private Builder $builder;
|
||||
|
||||
private string $folderPath;
|
||||
|
||||
/**
|
||||
* @param Builder<T> $builder
|
||||
*/
|
||||
public function __construct(string $disk, string $filename, Builder $builder, int $chunk)
|
||||
public function __construct(string $disk, string $folderPath, string $filename, Builder $builder, int $chunk)
|
||||
{
|
||||
|
||||
$this->disk = $disk;
|
||||
$this->filename = $filename;
|
||||
$this->chunk = $chunk;
|
||||
$this->builder = $builder;
|
||||
$this->folderPath = $folderPath;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,12 +55,21 @@ abstract class CsvExport
|
||||
*/
|
||||
abstract public function mapRow(Model $model): array;
|
||||
|
||||
/**
|
||||
* @throws \League\Csv\CannotInsertRecord
|
||||
* @throws \League\Csv\Exception
|
||||
* @throws \League\Csv\UnavailableStream
|
||||
*/
|
||||
public function export(): void
|
||||
{
|
||||
$writer = Writer::createFromPath(Storage::disk($this->disk)->path($this->filename), 'w+');
|
||||
$tempDirectory = TemporaryDirectory::make();
|
||||
$writer = Writer::createFromPath($tempDirectory->path($this->filename), 'w+');
|
||||
$writer->setDelimiter(',');
|
||||
$writer->setEnclosure('"');
|
||||
$writer->setEscape('');
|
||||
$writer->insertOne(static::HEADER);
|
||||
|
||||
$this->builder->chunk($this->chunk, function ($models) use ($writer): void {
|
||||
$this->builder->chunk($this->chunk, function (Collection $models) use ($writer): void {
|
||||
foreach ($models as $model) {
|
||||
$data = $this->mapRow($model);
|
||||
$row = $this->convertRow($data);
|
||||
@@ -63,6 +78,8 @@ abstract class CsvExport
|
||||
$writer->insertOne(array_values($row));
|
||||
}
|
||||
});
|
||||
Storage::disk($this->disk)->putFileAs($this->folderPath, new File($tempDirectory->path($this->filename)), $this->filename);
|
||||
$tempDirectory->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,13 +104,11 @@ abstract class CsvExport
|
||||
|
||||
/**
|
||||
* @param array<string, string> $row
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function validateRow(array $row): void
|
||||
{
|
||||
if (array_keys($row) !== self::HEADER) {
|
||||
throw new \Exception('Invalid row');
|
||||
if (array_keys($row) !== static::HEADER) {
|
||||
throw new \LogicException('Invalid row');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,17 @@ use Illuminate\Database\Eloquent\Model;
|
||||
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',
|
||||
'Description',
|
||||
'Task',
|
||||
'Project',
|
||||
'Client',
|
||||
'User',
|
||||
'Start',
|
||||
'End',
|
||||
'Duration',
|
||||
'Duration (decimal)',
|
||||
'Billable',
|
||||
'Tags',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -31,16 +31,20 @@ class TimeEntriesDetailedCsvExport extends CsvExport
|
||||
*/
|
||||
public function mapRow(Model $model): array
|
||||
{
|
||||
$duration = $model->getDuration();
|
||||
|
||||
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,
|
||||
'Description' => $model->description,
|
||||
'Task' => $model->task?->name,
|
||||
'Project' => $model->project?->name,
|
||||
'Client' => $model->client?->name,
|
||||
'User' => $model->user->name,
|
||||
'Start' => $model->start->format('Y-m-d H:i:s'),
|
||||
'End' => $model->end?->format('Y-m-d H:i:s'),
|
||||
'Duration' => $duration !== null ? (int) floor($duration->totalHours).':'.$duration->format('%I:%S') : null,
|
||||
'Duration (decimal)' => $duration?->totalHours,
|
||||
'Billable' => $model->billable ? 'Yes' : 'No',
|
||||
'Tags' => $model->tagsRelation->pluck('name')->implode(', '),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,27 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Service\ReportExport;
|
||||
|
||||
use App\Enums\ExportFormat;
|
||||
use App\Models\TimeEntry;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use LogicException;
|
||||
use Maatwebsite\Excel\Concerns\Exportable;
|
||||
use Maatwebsite\Excel\Concerns\FromQuery;
|
||||
use Maatwebsite\Excel\Concerns\WithCustomCsvSettings;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
use Maatwebsite\Excel\Concerns\WithColumnFormatting;
|
||||
use Maatwebsite\Excel\Concerns\WithDefaultStyles;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Style;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
/**
|
||||
* @implements WithMapping<TimeEntry>
|
||||
*/
|
||||
class TimeEntriesDetailedExport implements FromQuery, WithCustomCsvSettings, WithHeadings, WithMapping
|
||||
class TimeEntriesDetailedExport implements FromQuery, ShouldAutoSize, WithColumnFormatting, WithDefaultStyles, WithHeadings, WithMapping, WithStyles
|
||||
{
|
||||
use Exportable;
|
||||
|
||||
@@ -24,12 +33,15 @@ class TimeEntriesDetailedExport implements FromQuery, WithCustomCsvSettings, Wit
|
||||
*/
|
||||
private Builder $builder;
|
||||
|
||||
private ExportFormat $exportFormat;
|
||||
|
||||
/**
|
||||
* @param Builder<TimeEntry> $builder
|
||||
*/
|
||||
public function __construct(Builder $builder)
|
||||
public function __construct(Builder $builder, ExportFormat $exportFormat)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
$this->exportFormat = $exportFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,18 +52,41 @@ class TimeEntriesDetailedExport implements FromQuery, WithCustomCsvSettings, Wit
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
public function columnFormats(): array
|
||||
{
|
||||
if ($this->exportFormat === ExportFormat::XLSX) {
|
||||
return [
|
||||
'F' => 'yyyy-mm-dd hh:mm:ss',
|
||||
'G' => 'yyyy-mm-dd hh:mm:ss',
|
||||
'I' => NumberFormat::FORMAT_NUMBER_00,
|
||||
];
|
||||
} elseif ($this->exportFormat === ExportFormat::ODS) {
|
||||
return [
|
||||
'I' => NumberFormat::FORMAT_NUMBER_00,
|
||||
];
|
||||
} else {
|
||||
throw new LogicException('Unsupported export format.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string|bool>
|
||||
* @return array<int|string, array<string, array<string, bool>>>
|
||||
*/
|
||||
public function getCsvSettings(): array
|
||||
public function styles(Worksheet $sheet): array
|
||||
{
|
||||
return [
|
||||
'delimiter' => ',',
|
||||
'use_bom' => false,
|
||||
'output_encoding' => 'ISO-8859-1',
|
||||
// Style the first row as bold text.
|
||||
1 => ['font' => ['bold' => true]],
|
||||
];
|
||||
}
|
||||
|
||||
public function defaultStyles(Style $defaultStyle)
|
||||
{
|
||||
// Configure the default styles
|
||||
return $defaultStyle->getFill(); //->setFillType(Fill::FILL_SOLID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
@@ -63,10 +98,8 @@ class TimeEntriesDetailedExport implements FromQuery, WithCustomCsvSettings, Wit
|
||||
'Project',
|
||||
'Client',
|
||||
'User',
|
||||
'Start date',
|
||||
'Start time',
|
||||
'End date',
|
||||
'End time',
|
||||
'Start',
|
||||
'End',
|
||||
'Duration',
|
||||
'Duration (decimal)',
|
||||
'Billable',
|
||||
@@ -82,20 +115,36 @@ class TimeEntriesDetailedExport implements FromQuery, WithCustomCsvSettings, Wit
|
||||
{
|
||||
$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(', '),
|
||||
];
|
||||
if ($this->exportFormat === ExportFormat::XLSX) {
|
||||
return [
|
||||
$model->description,
|
||||
$model->task?->name,
|
||||
$model->project?->name,
|
||||
$model->client?->name,
|
||||
$model->user->name,
|
||||
Date::dateTimeToExcel($model->start),
|
||||
$model->end !== null ? Date::dateTimeToExcel($model->end) : null,
|
||||
$duration !== null ? (int) floor($duration->totalHours).':'.$duration->format('%I:%S') : null,
|
||||
$duration?->totalHours,
|
||||
$model->billable ? 'Yes' : 'No',
|
||||
$model->tagsRelation->pluck('name')->implode(', '),
|
||||
];
|
||||
} elseif ($this->exportFormat === ExportFormat::ODS) {
|
||||
return [
|
||||
$model->description,
|
||||
$model->task?->name,
|
||||
$model->project?->name,
|
||||
$model->client?->name,
|
||||
$model->user->name,
|
||||
$model->start->format('Y-m-d H:i:s'),
|
||||
$model->end?->format('Y-m-d 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(', '),
|
||||
];
|
||||
} else {
|
||||
throw new LogicException('Unsupported export format.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user