first commit
This commit is contained in:
17
app/Reports/BaseExport.php
Normal file
17
app/Reports/BaseExport.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Reports;
|
||||
|
||||
use Carbon\CarbonInterval;
|
||||
|
||||
abstract class BaseExport
|
||||
{
|
||||
protected function formatDuration(int $seconds): string
|
||||
{
|
||||
if ($seconds === 0) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return CarbonInterval::seconds($seconds)->cascade()->forHumans(['short' => true]);
|
||||
}
|
||||
}
|
||||
206
app/Reports/DashboardExport.php
Normal file
206
app/Reports/DashboardExport.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace App\Reports;
|
||||
|
||||
use App\Contracts\AppReport;
|
||||
use App\Enums\DashboardSortBy;
|
||||
use App\Enums\SortDirection;
|
||||
use App\Helpers\ReportHelper;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterval;
|
||||
use Carbon\CarbonPeriod;
|
||||
use Exception;
|
||||
use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\Exportable;
|
||||
use Maatwebsite\Excel\Concerns\WithDefaultStyles;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Style;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class DashboardExport extends AppReport implements FromCollection, WithMapping, ShouldAutoSize, WithHeadings, WithStyles, WithDefaultStyles
|
||||
{
|
||||
use Exportable;
|
||||
|
||||
private array $periodDates;
|
||||
private string $colsDateFormat;
|
||||
private readonly CarbonPeriod $period;
|
||||
|
||||
public function __construct(
|
||||
private readonly ?array $users,
|
||||
private readonly ?array $projects,
|
||||
private readonly Carbon $startAt,
|
||||
private readonly Carbon $endAt,
|
||||
private readonly string $companyTimezone,
|
||||
private readonly string $userTimezone,
|
||||
private readonly DashboardSortBy|null $sortBy = null,
|
||||
private readonly SortDirection|null $sortDirection = null,
|
||||
)
|
||||
{
|
||||
$this->period = CarbonPeriod::create(
|
||||
$this->startAt->clone()->setTimezone($this->userTimezone),
|
||||
$this->endAt->clone()->setTimezone($this->userTimezone)
|
||||
);
|
||||
$this->periodDates = $this->getPeriodDates($this->period);
|
||||
}
|
||||
|
||||
public function collection(array|null $where = null): Collection
|
||||
{
|
||||
$that = $this;
|
||||
|
||||
$reportCollection = $this->queryReport($where)->map(static function ($interval) use ($that) {
|
||||
$start = Carbon::make($interval->start_at);
|
||||
|
||||
$interval->duration = Carbon::make($interval->end_at)?->diffInSeconds($start);
|
||||
$interval->from_midnight = $start?->diffInSeconds($start?->copy()->startOfDay());
|
||||
|
||||
$interval->durationByDay = ReportHelper::getIntervalDurationByDay(
|
||||
$interval,
|
||||
$that->companyTimezone,
|
||||
$that->userTimezone
|
||||
);
|
||||
$interval->durationAtSelectedPeriod = ReportHelper::getIntervalDurationInPeriod(
|
||||
$that->period,
|
||||
$interval->durationByDay
|
||||
);
|
||||
|
||||
return $interval;
|
||||
})->groupBy('user_id');
|
||||
|
||||
if ($this->sortBy && $this->sortDirection) {
|
||||
$sortBy = match ($this->sortBy) {
|
||||
DashboardSortBy::USER_NAME => 'full_name',
|
||||
DashboardSortBy::WORKED => 'durationAtSelectedPeriod',
|
||||
};
|
||||
$sortDirection = match ($this->sortDirection) {
|
||||
SortDirection::ASC => false,
|
||||
SortDirection::DESC => true,
|
||||
};
|
||||
|
||||
if ($this->sortBy === DashboardSortBy::USER_NAME) {
|
||||
$reportCollection = $reportCollection->sortBy(
|
||||
fn($interval) => $interval[0][$sortBy],
|
||||
SORT_NATURAL,
|
||||
$sortDirection
|
||||
);
|
||||
} else {
|
||||
$reportCollection = $reportCollection->sortBy(
|
||||
fn($interval) => $interval->sum($sortBy),
|
||||
SORT_NATURAL,
|
||||
$sortDirection
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $reportCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $row
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function map($row): array
|
||||
{
|
||||
$that = $this;
|
||||
return $row->groupBy('user_id')->map(
|
||||
static function ($collection) use ($that) {
|
||||
$interval = CarbonInterval::seconds($collection->sum('durationAtSelectedPeriod'));
|
||||
|
||||
return array_merge(
|
||||
array_values($collection->first()->only(['full_name'])),
|
||||
[
|
||||
$interval->cascade()->forHumans(['short' => true]),
|
||||
round($interval->totalHours, 3),
|
||||
...$that->intervalsByDay($collection)
|
||||
]
|
||||
);
|
||||
}
|
||||
)->all();
|
||||
}
|
||||
|
||||
private function intervalsByDay(Collection $intervals): array
|
||||
{
|
||||
$intervalsByDay = [];
|
||||
|
||||
foreach ($this->periodDates as $date) {
|
||||
$workedAtDate = $intervals->sum(fn($item) => (
|
||||
$item->durationByDay[$date] ?? 0
|
||||
));
|
||||
$intervalsByDay[] = round(CarbonInterval::seconds($workedAtDate)->totalHours, 3);
|
||||
}
|
||||
|
||||
|
||||
return $intervalsByDay;
|
||||
}
|
||||
|
||||
private function queryReport(array|null $where = null): Collection
|
||||
{
|
||||
$query = ReportHelper::getBaseQuery(
|
||||
$this->users,
|
||||
$this->startAt,
|
||||
$this->endAt,
|
||||
[
|
||||
'time_intervals.start_at',
|
||||
'time_intervals.activity_fill',
|
||||
'time_intervals.mouse_fill',
|
||||
'time_intervals.keyboard_fill',
|
||||
'time_intervals.end_at',
|
||||
'time_intervals.is_manual',
|
||||
'users.email as user_email',
|
||||
]
|
||||
)->whereIn('project_id', $this->projects);
|
||||
|
||||
if (!is_null($where)) {
|
||||
$query = $query->where($where);
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'User Name',
|
||||
'Hours',
|
||||
'Hours (decimal)',
|
||||
...collect($this->periodDates)->map(fn($date) => Carbon::parse($date)->format('y-m-d'))
|
||||
];
|
||||
}
|
||||
|
||||
private function getPeriodDates($period): array
|
||||
{
|
||||
$dates = [];
|
||||
foreach ($period as $date) {
|
||||
$dates[] = $date->format(ReportHelper::$dateFormat);
|
||||
}
|
||||
return $dates;
|
||||
}
|
||||
|
||||
public function styles(Worksheet $sheet): array
|
||||
{
|
||||
return [
|
||||
1 => ['font' => ['bold' => true]],
|
||||
'A' => ['alignment' => ['horizontal' => Alignment::HORIZONTAL_LEFT]]
|
||||
];
|
||||
}
|
||||
|
||||
public function getReportId(): string
|
||||
{
|
||||
return 'dashboard_report';
|
||||
}
|
||||
|
||||
public function getLocalizedReportName(): string
|
||||
{
|
||||
return __('Dashboard_Report');
|
||||
}
|
||||
|
||||
public function defaultStyles(Style $defaultStyle)
|
||||
{
|
||||
return ['alignment' => ['horizontal' => Alignment::HORIZONTAL_RIGHT]];
|
||||
}
|
||||
}
|
||||
26
app/Reports/DummySheetExport.php
Normal file
26
app/Reports/DummySheetExport.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace App\Reports;
|
||||
|
||||
use Maatwebsite\Excel\Concerns\FromArray;
|
||||
|
||||
|
||||
|
||||
class DummySheetExport implements FromArray
|
||||
{
|
||||
protected $title;
|
||||
|
||||
public function __construct($title = 'Empty Sheet')
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
public function array(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
}
|
||||
158
app/Reports/PlannedTimeReportExport.php
Normal file
158
app/Reports/PlannedTimeReportExport.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\Reports;
|
||||
|
||||
use App\Contracts\AppReport;
|
||||
use App\Models\Project;
|
||||
use App\Models\CronTaskWorkers;
|
||||
use Carbon\CarbonInterval;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\Exportable;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use Settings;
|
||||
|
||||
class PlannedTimeReportExport extends AppReport implements FromCollection, WithMapping, ShouldAutoSize, WithHeadings, WithStyles
|
||||
{
|
||||
use Exportable;
|
||||
|
||||
public function __construct(
|
||||
private readonly ?array $projects,
|
||||
) {
|
||||
}
|
||||
|
||||
public function collection(): Collection
|
||||
{
|
||||
return collect([
|
||||
'reportData' => $this->queryReport(),
|
||||
'reportDate' => Settings::scope('core.reports')->get('planned_time_report_date', null)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Collection|string $row
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function map($row): array
|
||||
{
|
||||
if (is_string($row)) {
|
||||
return [
|
||||
[],
|
||||
['', "Created at $row", '', '', '']
|
||||
];
|
||||
}
|
||||
|
||||
$reportArr = [];
|
||||
|
||||
foreach ($row->toArray() as $project) {
|
||||
foreach ($project['tasks'] as $task) {
|
||||
foreach ($task['workers'] as $worker) {
|
||||
$reportArr[] = [
|
||||
$project['name'],
|
||||
$task['task_name'],
|
||||
$worker['user']['full_name'],
|
||||
CarbonInterval::seconds($worker['duration'])->cascade()->forHumans(),
|
||||
round(CarbonInterval::seconds($worker['duration'])->totalHours, 3)
|
||||
];
|
||||
}
|
||||
if ($task['estimate'] > 0) {
|
||||
$reportArr[] = [
|
||||
[],
|
||||
"Estimate for {$task['task_name']}",
|
||||
'',
|
||||
CarbonInterval::seconds($task['estimate'])->cascade()->forHumans(),
|
||||
round(CarbonInterval::seconds($task['estimate'])->totalHours, 3),
|
||||
];
|
||||
}
|
||||
if (count($task['workers']) > 1) {
|
||||
$reportArr[] = [
|
||||
[],
|
||||
"Subtotal for {$task['task_name']}",
|
||||
'',
|
||||
CarbonInterval::seconds($task['total_spent_time'])->cascade()->forHumans(),
|
||||
round(CarbonInterval::seconds($task['total_spent_time'])->totalHours, 3),
|
||||
];
|
||||
$reportArr[] = [];
|
||||
}
|
||||
}
|
||||
if ($project['total_spent_time'] > 0) {
|
||||
$reportArr[] = [
|
||||
"Subtotal for {$project['name']}",
|
||||
'',
|
||||
'',
|
||||
CarbonInterval::seconds($project['total_spent_time'])->cascade()->forHumans(),
|
||||
round(CarbonInterval::seconds($project['total_spent_time'])->totalHours, 3),
|
||||
];
|
||||
$reportArr[] = [];
|
||||
$reportArr[] = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $reportArr;
|
||||
}
|
||||
|
||||
private function queryReport(): Collection
|
||||
{
|
||||
$taskWorkersTable = (new CronTaskWorkers())->table;
|
||||
return Project::with(
|
||||
[
|
||||
'tasks' => static function (HasMany $query) use ($taskWorkersTable) {
|
||||
$query->select('id', 'task_name', 'due_date', 'estimate', 'project_id')
|
||||
->withSum(['workers as total_spent_time' => static function (EloquentBuilder $query) use ($taskWorkersTable) {
|
||||
$query->where("$taskWorkersTable.created_by_cron", true);
|
||||
}], 'duration')
|
||||
->withCasts(['total_spent_time' => 'integer'])
|
||||
->orderBy('total_spent_time', 'desc');
|
||||
},
|
||||
'tasks.workers' => static function (HasMany $query) use ($taskWorkersTable) {
|
||||
$query->select('id', 'user_id', 'task_id', 'duration')
|
||||
->where("$taskWorkersTable.created_by_cron", true);
|
||||
},
|
||||
'tasks.workers.user:id,full_name,email',
|
||||
]
|
||||
)
|
||||
->withSum(['workers as total_spent_time' => static function (EloquentBuilder $query) use ($taskWorkersTable) {
|
||||
$query->where("$taskWorkersTable.created_by_cron", true);
|
||||
}], 'duration')
|
||||
->withCasts(['total_spent_time' => 'integer'])
|
||||
->whereIn('id', $this->projects)->get();
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'Project Name',
|
||||
'Task Name',
|
||||
'User Name',
|
||||
'Hours',
|
||||
'Hours (decimal)',
|
||||
];
|
||||
}
|
||||
|
||||
public function styles(Worksheet $sheet): array
|
||||
{
|
||||
return [
|
||||
1 => ['font' => ['bold' => true], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER]],
|
||||
];
|
||||
}
|
||||
|
||||
public function getReportId(): string
|
||||
{
|
||||
return 'planned-time_report';
|
||||
}
|
||||
|
||||
public function getLocalizedReportName(): string
|
||||
{
|
||||
return __('PlannedTime_Report');
|
||||
}
|
||||
}
|
||||
259
app/Reports/ProjectMultiSheetExport.php
Normal file
259
app/Reports/ProjectMultiSheetExport.php
Normal file
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
namespace App\Reports;
|
||||
|
||||
use App\Models\Project;
|
||||
use Maatwebsite\Excel\Concerns\FromArray;
|
||||
use Maatwebsite\Excel\Concerns\WithCharts;
|
||||
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use Carbon\Carbon;
|
||||
use Maatwebsite\Excel\Concerns\WithColumnWidths;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
|
||||
class ProjectMultiSheetExport extends BaseExport implements FromArray, WithTitle, WithCharts, WithHeadings, WithColumnWidths
|
||||
{
|
||||
private $data;
|
||||
private $project;
|
||||
private $projectName;
|
||||
private $countDate;
|
||||
private $periodDates;
|
||||
private $reportData;
|
||||
private $showTasksChart;
|
||||
private $showUsersChart;
|
||||
|
||||
const COLUMN_FIRST = 'B';
|
||||
const OFFSET_CHART = [10, 30];
|
||||
const POSITIONS_CHART = [['A8', 'D38'], ['E8', 'H38']];
|
||||
const TEXT_USER = 'Worked by all users';
|
||||
const TEXT_USER_INDIVIDUALLY = 'Worked by all users individually';
|
||||
|
||||
public function __construct(array $collection, $id, array $periodDates)
|
||||
{
|
||||
$this->data = $collection['reportCharts'];
|
||||
$this->project = $id;
|
||||
$this->periodDates = $periodDates;
|
||||
$this->projectName = Project::find($id)->name;
|
||||
$this->reportData = $collection['reportData'];
|
||||
$this->countDate = count($this->periodDates);
|
||||
}
|
||||
|
||||
public function columnWidths(): array
|
||||
{
|
||||
$columnWidths = ['A' => 45];
|
||||
$currentColumn = 2;
|
||||
while ($currentColumn <= $this->countDate + 1) {
|
||||
$columnWidths[Coordinate::stringFromColumnIndex($currentColumn)] = 25;
|
||||
$currentColumn++;
|
||||
}
|
||||
|
||||
return $columnWidths;
|
||||
}
|
||||
|
||||
public function array(): array
|
||||
{
|
||||
if (isset($this->data['total_spent_time_day']['datasets'])) {
|
||||
foreach ($this->data['total_spent_time_day']['datasets'] as $projectId => $project) {
|
||||
if ($projectId !== $this->project)
|
||||
continue;
|
||||
|
||||
$resultRow = [];
|
||||
$resultRow[] = $project['label'] ?? '';
|
||||
$this->projectName = $project['label'] ?? '';
|
||||
if (isset($project['data'])) {
|
||||
foreach ($project['data'] as $date => $time) {
|
||||
$resultRow[] = $this->formatDuration($time);
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->data['total_spent_time_day_and_users_separately']['datasets'])) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = 'user name';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$result[] = $resultRow;
|
||||
|
||||
foreach ($this->data['total_spent_time_day_and_users_separately']['datasets'] as $projectId => $userTask) {
|
||||
if ($projectId !== $this->project)
|
||||
continue;
|
||||
foreach ($userTask as $userId => $user) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = $user['label'] ?? '';
|
||||
if (isset($user['data'])) {
|
||||
foreach ($user['data'] as $date => $time) {
|
||||
$resultRow[] = $this->formatDuration($time);
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->reportData)) {
|
||||
foreach ($this->reportData as $projectId => $project) {
|
||||
if ($projectId !== $this->project)
|
||||
continue;
|
||||
$resultRow = [];
|
||||
$resultRow[] = 'Project name';
|
||||
$resultRow[] = 'Create at';
|
||||
$resultRow[] = 'Description';
|
||||
$resultRow[] = 'Important';
|
||||
$result[] = $resultRow;
|
||||
|
||||
$resultRow = [];
|
||||
$resultRow[] = $project['name'] ?? '';
|
||||
$resultRow[] = $project['created_at'] ?? '';
|
||||
$resultRow[] = $project['description'] ?? '';
|
||||
$resultRow[] = $project['important'] ?? '';
|
||||
$result[] = $resultRow;
|
||||
|
||||
$resultRow = [];
|
||||
$resultRow[] = 'Task information';
|
||||
$resultRow[] = 'Task name';
|
||||
$resultRow[] = 'Status';
|
||||
$resultRow[] = 'Due date';
|
||||
$resultRow[] = 'Time estimat';
|
||||
$resultRow[] = 'Descriptione';
|
||||
$result[] = $resultRow;
|
||||
|
||||
if (isset($project['tasks'])) {
|
||||
foreach ($project['tasks'] as $taskId => $task) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = '';
|
||||
$resultRow[] = $task['task_name'] ?? '';
|
||||
$resultRow[] = $task['status'] ?? '';
|
||||
$resultRow[] = $task['due_date'] ?? 'Отсутствует';
|
||||
$resultRow[] = $task['estimate'] ?? 'Отсутствует';
|
||||
$resultRow[] = $task['description'] ?? '';
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($projectTasks['users'])) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = 'User Name';
|
||||
$resultRow[] = 'User Email';
|
||||
$resultRow[] = 'Total time';
|
||||
$result[] = $resultRow;
|
||||
|
||||
foreach ($projectTasks['users'] as $taskId => $user) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = $user['full_name'] ?? '';
|
||||
$resultRow[] = $user['email'] ?? '';
|
||||
$resultRow[] = $user['total_spent_time_by_user'] ?? 'Отсутствует';
|
||||
if (isset($user['workers_day'])) {
|
||||
foreach ($user['workers_day'] as $date => $time)
|
||||
$resultRow[] = 'Data ' . $date . ' time: ' . $time;
|
||||
}
|
||||
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function charts()
|
||||
{
|
||||
$createDataSeries = function ($label, $categories, $values) {
|
||||
return new DataSeries(
|
||||
DataSeries::TYPE_LINECHART,
|
||||
DataSeries::GROUPING_STANDARD,
|
||||
[0],
|
||||
$label,
|
||||
$categories,
|
||||
$values
|
||||
);
|
||||
};
|
||||
|
||||
$createChart = function ($name, $title, $position, $offset, $startColumn, $endColumn, $rowCount, $columnLast) use ($createDataSeries) {
|
||||
$series = [];
|
||||
if (empty($rowCount)) {
|
||||
$label = [new DataSeriesValues('String', "'" . $this->title() . "'" . '!A2', null, 1)];
|
||||
$categories = [new DataSeriesValues('String', "'" . $this->title() . "'" . "!{$startColumn}1:{$endColumn}1", null, $columnLast)];
|
||||
$values = [new DataSeriesValues('Number', "'" . $this->title() . "'" . "!{$startColumn}2:{$endColumn}2", null, $columnLast)];
|
||||
$series[] = $createDataSeries($label, $categories, $values);
|
||||
} else {
|
||||
for ($i = $rowCount[0]; $i < $rowCount[1]; $i++) {
|
||||
$label = [new DataSeriesValues('String', "'" . $this->title() . "'" . '!A' . $i, null, $i)];
|
||||
$categories = [new DataSeriesValues('String', "'" . $this->title() . "'" . "!{$startColumn}1:{$endColumn}1", null, $columnLast)];
|
||||
$values = [new DataSeriesValues('Number', "'" . $this->title() . "'" . "!{$startColumn}" . $i . ":!{$endColumn}" . $i, null, $columnLast)];
|
||||
$series[] = $createDataSeries($label, $categories, $values);
|
||||
}
|
||||
}
|
||||
|
||||
$plot = new PlotArea(null, $series);
|
||||
$legend = new Legend();
|
||||
$chart = new Chart($name, new Title($title), $legend, $plot);
|
||||
$chart->setTopLeftPosition($position[0]);
|
||||
$chart->setTopLeftOffset($offset[0], $offset[0]);
|
||||
$chart->setBottomRightPosition($position[1]);
|
||||
$chart->setBottomRightOffset($offset[1], $offset[1]);
|
||||
return $chart;
|
||||
};
|
||||
|
||||
$columnNumber = $this->countDate;
|
||||
$charts = [];
|
||||
$columnLast = Coordinate::stringFromColumnIndex($columnNumber + 1);
|
||||
$rowCounts = $this->rowCount();
|
||||
if ($this->showTasksChart)
|
||||
$charts[] = $createChart(static::TEXT_USER, static::TEXT_USER, static::POSITIONS_CHART[0], static::OFFSET_CHART, static::COLUMN_FIRST, $columnLast, [], $columnNumber);
|
||||
|
||||
if ($this->showUsersChart)
|
||||
$charts[] = $createChart(static::TEXT_USER_INDIVIDUALLY, static::TEXT_USER_INDIVIDUALLY, static::POSITIONS_CHART[1], static::OFFSET_CHART, static::COLUMN_FIRST, $columnLast, [4, $rowCounts + 4], $columnNumber);
|
||||
|
||||
return $charts;
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'Project Name',
|
||||
...collect($this->periodDates)->map(fn($date) => Carbon::parse($date)->format('y-m-d'))
|
||||
];
|
||||
}
|
||||
|
||||
protected function rowCount()
|
||||
{
|
||||
$count = 0;
|
||||
if (isset($this->data['total_spent_time_day']['datasets'])) {
|
||||
$this->showTasksChart = isset($this->data['total_spent_time_day']['datasets'][$this->project]);
|
||||
}
|
||||
if (isset($this->data['total_spent_time_day_and_users_separately']['datasets'])) {
|
||||
$this->showUsersChart = isset($this->data['total_spent_time_day_and_users_separately']['datasets'][$this->project]);
|
||||
foreach ($this->data['total_spent_time_day_and_users_separately']['datasets'] as $projectId => $userTasks) {
|
||||
if ($projectId !== $this->project) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$count += count($userTasks);
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
return \Str::limit("{$this->project}) $this->projectName", 8);
|
||||
}
|
||||
}
|
||||
182
app/Reports/ProjectReportExport.php
Normal file
182
app/Reports/ProjectReportExport.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace App\Reports;
|
||||
|
||||
use App\Contracts\AppReport;
|
||||
use App\Helpers\ReportHelper;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterval;
|
||||
use Carbon\CarbonPeriod;
|
||||
use Exception;
|
||||
use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\Exportable;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class ProjectReportExport extends AppReport implements FromCollection, WithMapping, ShouldAutoSize, WithHeadings, WithStyles
|
||||
{
|
||||
use Exportable;
|
||||
|
||||
const PICS_AMOUNT = 6;
|
||||
|
||||
private readonly CarbonPeriod $period;
|
||||
|
||||
public function __construct(
|
||||
private readonly ?array $users,
|
||||
private readonly ?array $projects,
|
||||
private readonly Carbon $startAt,
|
||||
private readonly Carbon $endAt,
|
||||
private readonly string $companyTimezone,
|
||||
)
|
||||
{
|
||||
$this->period = CarbonPeriod::create($this->startAt, $this->endAt);
|
||||
}
|
||||
|
||||
public function collection(): Collection
|
||||
{
|
||||
$that = $this;
|
||||
|
||||
return $this->queryReport()->map(static function ($interval) use ($that) {
|
||||
$date = optional(Carbon::make($interval->start_at));
|
||||
|
||||
$interval->hour = $date->hour;
|
||||
$interval->day = $date->format('Y-m-d');
|
||||
$interval->minute = round($date->minute, -1);
|
||||
$interval->duration = Carbon::make($interval->end_at)?->diffInSeconds(Carbon::make($interval->start_at));
|
||||
|
||||
$interval->durationByDay = ReportHelper::getIntervalDurationByDay($interval, $that->companyTimezone);
|
||||
|
||||
$interval->durationAtSelectedPeriod = ReportHelper::getIntervalDurationInPeriod(
|
||||
$that->period,
|
||||
$interval->durationByDay
|
||||
);
|
||||
|
||||
return $interval;
|
||||
})->groupBy('project_id')->map(
|
||||
static fn(Collection $collection, int $key) => [
|
||||
'id' => $key,
|
||||
'name' => $collection->first()->project_name,
|
||||
'time' => $collection->sum('durationAtSelectedPeriod'),
|
||||
'users' => $collection->groupBy('user_id')->map(
|
||||
static fn(Collection $collection, int $key) => [
|
||||
'id' => $key,
|
||||
'full_name' => $collection->first()->full_name,
|
||||
'email' => $collection->first()->user_email,
|
||||
'time' => $collection->sum('durationAtSelectedPeriod'),
|
||||
'tasks' => $collection->groupBy('task_id')->map(
|
||||
static fn(Collection $collection, int $key) => [
|
||||
'id' => $key,
|
||||
'task_name' => $collection->first()->task_name,
|
||||
'time' => $collection->sum('durationAtSelectedPeriod'),
|
||||
'intervals' => $collection->groupBy('day')->map(
|
||||
static fn(Collection $collection, string $key) => [
|
||||
'date' => $key,
|
||||
'time' => $collection->sum('durationAtSelectedPeriod'),
|
||||
'items' => $collection->groupBy('hour')->map(
|
||||
static fn(Collection $collection
|
||||
) => $collection
|
||||
->split(self::PICS_AMOUNT)
|
||||
->map(fn(Collection $group, $i
|
||||
) => $i < (self::PICS_AMOUNT - 1) ? $group->first() : $group->last())
|
||||
->values(),
|
||||
)->values(),
|
||||
],
|
||||
)->values(),
|
||||
],
|
||||
)->values(),
|
||||
],
|
||||
)->values(),
|
||||
],
|
||||
)->values();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $row
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function map($row): array
|
||||
{
|
||||
return array_merge(
|
||||
$row['users']
|
||||
->map(static fn($collection) => $collection['tasks'])->flatten(1)
|
||||
->map(static fn($collection) => array_merge(
|
||||
$collection['intervals']->map(
|
||||
static fn($collection) => $collection['items']
|
||||
)->flatten(2)->unique(static fn($item) => $item->task_id)->map(
|
||||
static fn($collection) => array_values($collection->only([
|
||||
'project_name',
|
||||
'full_name',
|
||||
'task_name'
|
||||
]))
|
||||
)->flatten(1)->all(),
|
||||
[
|
||||
CarbonInterval::seconds($collection['time'])->cascade()->forHumans(),
|
||||
round(CarbonInterval::seconds($collection['time'])->totalHours, 3)
|
||||
]
|
||||
))
|
||||
->all(),
|
||||
[
|
||||
[
|
||||
"Subtotal for {$row['name']}",
|
||||
'',
|
||||
'',
|
||||
CarbonInterval::seconds($row['time'])->cascade()->forHumans(),
|
||||
round(CarbonInterval::seconds($row['time'])->totalHours, 3),
|
||||
],
|
||||
[]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function queryReport(): Collection
|
||||
{
|
||||
return ReportHelper::getBaseQuery(
|
||||
$this->users,
|
||||
$this->startAt,
|
||||
$this->endAt,
|
||||
[
|
||||
'time_intervals.start_at',
|
||||
'time_intervals.activity_fill',
|
||||
'time_intervals.mouse_fill',
|
||||
'time_intervals.keyboard_fill',
|
||||
'time_intervals.end_at',
|
||||
'users.email as user_email',
|
||||
]
|
||||
)->whereIn('project_id', $this->projects)->get();
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'Project Name',
|
||||
'User Name',
|
||||
'Task Name',
|
||||
'Hours',
|
||||
'Hours (decimal)',
|
||||
];
|
||||
}
|
||||
|
||||
public function styles(Worksheet $sheet): array
|
||||
{
|
||||
return [
|
||||
1 => ['font' => ['bold' => true], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER]],
|
||||
];
|
||||
}
|
||||
|
||||
public function getReportId(): string
|
||||
{
|
||||
return 'project_report';
|
||||
}
|
||||
|
||||
public function getLocalizedReportName(): string
|
||||
{
|
||||
return __('Project_Report');
|
||||
}
|
||||
}
|
||||
235
app/Reports/TaskMultiSheetExport.php
Normal file
235
app/Reports/TaskMultiSheetExport.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace App\Reports;
|
||||
|
||||
use Maatwebsite\Excel\Concerns\FromArray;
|
||||
use Maatwebsite\Excel\Concerns\WithCharts;
|
||||
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use Carbon\Carbon;
|
||||
use Maatwebsite\Excel\Concerns\WithColumnWidths;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
|
||||
class TaskMultiSheetExport extends BaseExport implements FromArray, WithTitle, WithCharts, WithHeadings, WithColumnWidths
|
||||
{
|
||||
private $data;
|
||||
private $task;
|
||||
private $taskName;
|
||||
private $periodDates;
|
||||
private $countDate;
|
||||
private $reportData;
|
||||
|
||||
const COLUMN_FIRST = 'B';
|
||||
const OFFSET_CHART = [10, 30];
|
||||
const POSITIONS_CHART = [['A8', 'D38'], ['E8', 'H38']];
|
||||
const TEXT_USER = 'Worked by all users';
|
||||
const TEXT_USER_INDIVIDUALLY = 'Worked by all users individually';
|
||||
|
||||
public function __construct(array $collection, $taskId, $taskName, array $periodDates)
|
||||
{
|
||||
$this->data = $collection['reportCharts'];
|
||||
$this->reportData = $collection['reportData'];
|
||||
$this->task = $taskId;
|
||||
$this->taskName = $taskName;
|
||||
$this->periodDates = $periodDates;
|
||||
$this->countDate = count($this->periodDates);
|
||||
}
|
||||
|
||||
public function columnWidths(): array
|
||||
{
|
||||
$columnWidths = ['A' => 45];
|
||||
$currentColumn = 2;
|
||||
while ($currentColumn <= $this->countDate + 1) {
|
||||
$columnWidths[Coordinate::stringFromColumnIndex($currentColumn)] = 25;
|
||||
$currentColumn++;
|
||||
}
|
||||
|
||||
return $columnWidths;
|
||||
}
|
||||
public function array(): array
|
||||
{
|
||||
if (isset($this->data['total_spent_time_day']['datasets'])) {
|
||||
foreach ($this->data['total_spent_time_day']['datasets'] as $taskId => $task) {
|
||||
if ($taskId !== $this->task)
|
||||
continue;
|
||||
|
||||
$resultRow = [];
|
||||
$resultRow[] = $task['label'] ?? '';
|
||||
$this->taskName = $task['label'] ?? '';
|
||||
if (isset($task['data'])) {
|
||||
foreach ($task['data'] as $date => $time) {
|
||||
$resultRow[] = $this->formatDuration($time);
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
if (isset($this->data['total_spent_time_day_users_separately']['datasets'])) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = 'User name';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$result[] = $resultRow;
|
||||
|
||||
foreach ($this->data['total_spent_time_day_users_separately']['datasets'] as $taskId => $userTask) {
|
||||
if ($taskId !== $this->task)
|
||||
continue;
|
||||
|
||||
foreach ($userTask as $userId => $user) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = $user['label'] ?? '';
|
||||
if (isset($user['data'])) {
|
||||
foreach ($user['data'] as $date => $time) {
|
||||
$resultRow[] = $this->formatDuration($time);
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($this->reportData)) {
|
||||
|
||||
foreach ($this->reportData as $taskId => $userTasks) {
|
||||
if ($taskId !== $this->task)
|
||||
continue;
|
||||
$resultRow = [];
|
||||
if (isset($userTasks['users'])) {
|
||||
foreach ($userTasks['users'] as $taskId => $taskData) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = 'User name';
|
||||
$resultRow[] = 'Email user';
|
||||
$resultRow[] = 'Total time';
|
||||
$resultRow[] = 'Task name';
|
||||
$resultRow[] = 'Priority';
|
||||
$resultRow[] = 'Status';
|
||||
$resultRow[] = 'Estimate';
|
||||
$resultRow[] = 'Description';
|
||||
$result[] = $resultRow;
|
||||
|
||||
$resultRow = [];
|
||||
$resultRow[] = $taskData['full_name'] ?? '';
|
||||
$resultRow[] = $taskData['email'] ?? '';
|
||||
$resultRow[] = $taskData['total_spent_time_by_user'] ?? '';
|
||||
$resultRow[] = $userTasks['task_name'] ?? '';
|
||||
$resultRow[] = $userTasks['priority'] ?? '';
|
||||
$resultRow[] = $userTasks['status'] ?? '';
|
||||
$resultRow[] = $userTasks['estimate'] ?? '';
|
||||
$resultRow[] = $userTasks['description'] ?? '';
|
||||
if (isset($taskData['workers_day'])) {
|
||||
foreach ($taskData['workers_day'] as $date => $time) {
|
||||
$resultRow[] = 'Data ' . $date . ' time: ' . $this->formatDuration($time);
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
|
||||
$resultRow = [];
|
||||
$resultRow[] = 'Project name';
|
||||
$resultRow[] = 'created at';
|
||||
$resultRow[] = 'description';
|
||||
$resultRow[] = 'important';
|
||||
$result[] = $resultRow;
|
||||
|
||||
$resultRow = [];
|
||||
$resultRow[] = $userTasks['project']['name'] ?? '';
|
||||
$resultRow[] = $userTasks['project']['created_at'] ?? '';
|
||||
$resultRow[] = $userTasks['project']['description'] ?? '';
|
||||
$resultRow[] = $userTasks['project']['important'] ?? '';
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function charts()
|
||||
{
|
||||
$createDataSeries = function ($label, $categories, $values) {
|
||||
return new DataSeries(
|
||||
DataSeries::TYPE_LINECHART,
|
||||
DataSeries::GROUPING_STANDARD,
|
||||
[0],
|
||||
$label,
|
||||
$categories,
|
||||
$values
|
||||
);
|
||||
};
|
||||
|
||||
$createChart = function ($name, $title, $position, $offset, $startColumn, $endColumn, $rowCount, $columnLast) use ($createDataSeries) {
|
||||
$series = [];
|
||||
if (empty($rowCount)) {
|
||||
$label = [new DataSeriesValues('String', "'" . $this->title() . "'" . '!A2', null, 1)];
|
||||
$categories = [new DataSeriesValues('String', "'" . $this->title() . "'" . "!{$startColumn}1:{$endColumn}1", null, $columnLast)];
|
||||
$values = [new DataSeriesValues('Number', "'" . $this->title() . "'" . "!{$startColumn}2:{$endColumn}2", null, $columnLast)];
|
||||
$series[] = $createDataSeries($label, $categories, $values);
|
||||
} else {
|
||||
for ($i = $rowCount[0]; $i < $rowCount[1]; $i++) {
|
||||
$label = [new DataSeriesValues('String', "'" . $this->title() . "'" . '!A' . $i, null, $i)];
|
||||
$categories = [new DataSeriesValues('String', "'" . $this->title() . "'" . "!{$startColumn}1:{$endColumn}1", null, $columnLast)];
|
||||
$values = [new DataSeriesValues('Number', "'" . $this->title() . "'" . "!{$startColumn}" . $i . ":!{$endColumn}" . $i, null, $columnLast)];
|
||||
$series[] = $createDataSeries($label, $categories, $values);
|
||||
}
|
||||
}
|
||||
|
||||
$plot = new PlotArea(null, $series);
|
||||
$legend = new Legend();
|
||||
$chart = new Chart($name, new Title($title), $legend, $plot);
|
||||
$chart->setTopLeftPosition($position[0]);
|
||||
$chart->setTopLeftOffset($offset[0], $offset[0]);
|
||||
$chart->setBottomRightPosition($position[1]);
|
||||
$chart->setBottomRightOffset($offset[1], $offset[1]);
|
||||
return $chart;
|
||||
};
|
||||
|
||||
$columnNumber = $this->countDate;
|
||||
$charts = [];
|
||||
$columnLast = Coordinate::stringFromColumnIndex($columnNumber + 1);
|
||||
$charts[] = $createChart(static::TEXT_USER, static::TEXT_USER, static::POSITIONS_CHART[0], static::OFFSET_CHART, static::COLUMN_FIRST, $columnLast, [], $columnNumber);
|
||||
$charts[] = $createChart(static::TEXT_USER_INDIVIDUALLY, static::TEXT_USER_INDIVIDUALLY, static::POSITIONS_CHART[1], static::OFFSET_CHART, static::COLUMN_FIRST, $columnLast, [4, $this->rowCount() + 4], $columnNumber);
|
||||
return $charts;
|
||||
}
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'Task Name',
|
||||
...collect($this->periodDates)->map(fn($date) => Carbon::parse($date)->format('y-m-d'))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
return \Str::limit("{$this->task}) $this->taskName", 8);
|
||||
}
|
||||
|
||||
protected function rowCount()
|
||||
{
|
||||
$count = 0;
|
||||
if (isset($this->data['total_spent_time_day_users_separately']['datasets'])) {
|
||||
foreach ($this->data['total_spent_time_day_users_separately']['datasets'] as $taskId => $userTasks) {
|
||||
if ($taskId !== $this->task)
|
||||
continue;
|
||||
|
||||
$count += count($userTasks);
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
112
app/Reports/TimeUseReportExport.php
Normal file
112
app/Reports/TimeUseReportExport.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Reports;
|
||||
|
||||
use App\Contracts\AppReport;
|
||||
use App\Helpers\ReportHelper;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonPeriod;
|
||||
use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\Exportable;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class TimeUseReportExport extends AppReport implements FromCollection, WithMapping, ShouldAutoSize, WithHeadings, WithStyles
|
||||
{
|
||||
use Exportable;
|
||||
|
||||
private readonly CarbonPeriod $period;
|
||||
|
||||
public function __construct(
|
||||
private readonly ?array $users,
|
||||
private readonly Carbon $startAt,
|
||||
private readonly Carbon $endAt,
|
||||
private readonly string $companyTimezone
|
||||
)
|
||||
{
|
||||
$this->period = CarbonPeriod::create($this->startAt, $this->endAt);
|
||||
}
|
||||
|
||||
public function collection(): Collection
|
||||
{
|
||||
$that = $this;
|
||||
|
||||
return $this->queryReport()->map(static function ($interval) use ($that) {
|
||||
$interval->duration = Carbon::make($interval->end_at)?->diffInSeconds(Carbon::make($interval->start_at));
|
||||
|
||||
$interval->durationByDay = ReportHelper::getIntervalDurationByDay($interval, $that->companyTimezone);
|
||||
|
||||
$interval->durationAtSelectedPeriod = ReportHelper::getIntervalDurationInPeriod(
|
||||
$that->period,
|
||||
$interval->durationByDay
|
||||
);
|
||||
|
||||
return $interval;
|
||||
})->groupBy('user_id')->map(
|
||||
static fn($collection) => [
|
||||
'time' => $collection->sum('durationAtSelectedPeriod'),
|
||||
'user' => [
|
||||
'id' => $collection->first()->user_id,
|
||||
'email' => $collection->first()->user_email,
|
||||
'full_name' => $collection->first()->full_name,
|
||||
],
|
||||
'tasks' => $collection->groupBy('task_id')->map(
|
||||
static fn($collection) => [
|
||||
'time' => $collection->sum('durationAtSelectedPeriod'),
|
||||
'task_id' => $collection->first()->task_id,
|
||||
'task_name' => $collection->first()->task_name,
|
||||
'project_id' => $collection->first()->project_id,
|
||||
'project_name' => $collection->first()->project_name,
|
||||
],
|
||||
)->values(),
|
||||
],
|
||||
)->values();
|
||||
}
|
||||
|
||||
public function map($row): array
|
||||
{
|
||||
// TODO: Implement map() method.
|
||||
return [];
|
||||
}
|
||||
|
||||
private function queryReport(): Collection
|
||||
{
|
||||
return ReportHelper::getBaseQuery(
|
||||
$this->users,
|
||||
$this->startAt,
|
||||
$this->endAt,
|
||||
[
|
||||
'time_intervals.start_at',
|
||||
'time_intervals.activity_fill',
|
||||
'time_intervals.mouse_fill',
|
||||
'time_intervals.keyboard_fill',
|
||||
'time_intervals.end_at',
|
||||
'users.email as user_email',
|
||||
]
|
||||
)->get();
|
||||
}
|
||||
|
||||
public function getReportId(): string
|
||||
{
|
||||
return 'time_use_report';
|
||||
}
|
||||
|
||||
public function getLocalizedReportName(): string
|
||||
{
|
||||
return __('Time_Use_Report');
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function styles(Worksheet $sheet): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
159
app/Reports/UniversalReportExport.php
Normal file
159
app/Reports/UniversalReportExport.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace App\Reports;
|
||||
|
||||
use App\Contracts\AppReport;
|
||||
use App\Enums\UniversalReportBase;
|
||||
use App\Helpers\ReportHelper;
|
||||
use App\Models\UniversalReport;
|
||||
use App\Services\UniversalReportServiceProject;
|
||||
use App\Services\UniversalReportServiceTask;
|
||||
use App\Services\UniversalReportServiceUser;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonPeriod;
|
||||
use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\Exportable;
|
||||
use Maatwebsite\Excel\Concerns\WithDefaultStyles;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Style;
|
||||
|
||||
class UniversalReportExport extends AppReport implements FromCollection, ShouldAutoSize, WithDefaultStyles, WithMultipleSheets
|
||||
{
|
||||
use Exportable;
|
||||
|
||||
private array $periodDates;
|
||||
private UniversalReport $report;
|
||||
private string $colsDateFormat;
|
||||
private readonly CarbonPeriod $period;
|
||||
|
||||
public function __construct(
|
||||
private readonly int $id,
|
||||
private readonly Carbon $startAt,
|
||||
private readonly Carbon $endAt,
|
||||
private readonly string $companyTimezone,
|
||||
) {
|
||||
$this->report = UniversalReport::find($id);
|
||||
$this->period = CarbonPeriod::create(
|
||||
$this->startAt->clone()->setTimezone($this->companyTimezone),
|
||||
$this->endAt->clone()->setTimezone($this->companyTimezone)
|
||||
);
|
||||
$this->periodDates = $this->getPeriodDates($this->period);
|
||||
}
|
||||
|
||||
public function collection(): Collection
|
||||
{
|
||||
return match ($this->report->base) {
|
||||
UniversalReportBase::PROJECT => $this->collectionProject(),
|
||||
UniversalReportBase::USER => $this->collectionUser(),
|
||||
UniversalReportBase::TASK => $this->collectionTask()
|
||||
};
|
||||
}
|
||||
|
||||
public function sheets(): array
|
||||
{
|
||||
$sheets = [];
|
||||
switch ($this->report->base) {
|
||||
case UniversalReportBase::USER:
|
||||
$collection = $this->collectionUser()->all();
|
||||
$data = $collection['reportCharts'];
|
||||
if (isset($data['total_spent_time_day']['datasets'])) {
|
||||
foreach ($data['total_spent_time_day']['datasets'] as $userId => $user) {
|
||||
$sheets[] = new UserMultiSheetExport($collection, $userId, ($user['label'] ?? ''), $this->periodDates);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UniversalReportBase::TASK:
|
||||
$collection = $this->collectionTask()->all();
|
||||
$data = $collection['reportCharts'];
|
||||
if (isset($data['total_spent_time_day']['datasets'])) {
|
||||
foreach ($data['total_spent_time_day']['datasets'] as $taskId => $task) {
|
||||
$sheets[] = new TaskMultiSheetExport($collection, $taskId, ($task['label'] ?? ''), $this->periodDates);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UniversalReportBase::PROJECT:
|
||||
$collection = $this->collectionProject()->all();
|
||||
$charts = $collection['reportCharts'];
|
||||
$projectTasksIds = [];
|
||||
$projectUsersIds = [];
|
||||
if (isset($charts['total_spent_time_day']['datasets'])) {
|
||||
$projectTasksIds = array_keys($charts['total_spent_time_day']['datasets'] ?? []);
|
||||
}
|
||||
if (isset($charts['total_spent_time_day_and_users_separately']['datasets'])) {
|
||||
$projectUsersIds = array_keys($charts['total_spent_time_day_and_users_separately']['datasets'] ?? []);
|
||||
}
|
||||
if (isset($charts['total_spent_time_day']['datasets']) || isset($charts['total_spent_time_day_and_users_separately']['datasets'])) {
|
||||
$allIdsProjects = array_merge($projectTasksIds, $projectUsersIds);
|
||||
$allIdsProjects = array_unique($allIdsProjects);
|
||||
foreach ($allIdsProjects as $id) {
|
||||
$sheets[] = new ProjectMultiSheetExport($collection, $id, $this->periodDates);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (empty($sheets)) {
|
||||
$sheets[] = new DummySheetExport();
|
||||
}
|
||||
return $sheets;
|
||||
}
|
||||
|
||||
public function collectionUser(): Collection
|
||||
{
|
||||
$service = new UniversalReportServiceUser($this->startAt, $this->endAt, $this->report, $this->periodDates);
|
||||
return collect([
|
||||
'reportData' => $service->getUserReportData(),
|
||||
'reportName' => $this->report->name,
|
||||
'reportCharts' => $service->getUserReportCharts(),
|
||||
'periodDates' => $this->periodDates,
|
||||
]);
|
||||
}
|
||||
|
||||
public function collectionTask(): Collection
|
||||
{
|
||||
$service = new UniversalReportServiceTask($this->startAt, $this->endAt, $this->report, $this->periodDates);
|
||||
return collect([
|
||||
'reportData' => $service->getTaskReportData(),
|
||||
'reportName' => $this->report->name,
|
||||
'reportCharts' => $service->getTasksReportCharts(),
|
||||
'periodDates' => $this->periodDates,
|
||||
]);
|
||||
}
|
||||
|
||||
public function collectionProject(): Collection
|
||||
{
|
||||
$service = new UniversalReportServiceProject($this->startAt, $this->endAt, $this->report, $this->periodDates);
|
||||
return collect([
|
||||
'reportData' => $service->getProjectReportData(),
|
||||
'reportName' => $this->report->name,
|
||||
'reportCharts' => $service->getProjectReportCharts(),
|
||||
'periodDates' => $this->periodDates,
|
||||
]);
|
||||
}
|
||||
|
||||
private function getPeriodDates($period): array
|
||||
{
|
||||
$dates = [];
|
||||
foreach ($period as $date) {
|
||||
$dates[] = $date->format(ReportHelper::$dateFormat);
|
||||
}
|
||||
return $dates;
|
||||
}
|
||||
|
||||
public function getReportId(): string
|
||||
{
|
||||
return 'universal_report';
|
||||
}
|
||||
|
||||
public function getLocalizedReportName(): string
|
||||
{
|
||||
return __('Universal_Report');
|
||||
}
|
||||
|
||||
public function defaultStyles(Style $defaultStyle)
|
||||
{
|
||||
return ['alignment' => ['horizontal' => Alignment::HORIZONTAL_RIGHT]];
|
||||
}
|
||||
}
|
||||
268
app/Reports/UserMultiSheetExport.php
Normal file
268
app/Reports/UserMultiSheetExport.php
Normal file
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
namespace App\Reports;
|
||||
|
||||
use Maatwebsite\Excel\Concerns\FromArray;
|
||||
use Maatwebsite\Excel\Concerns\WithCharts;
|
||||
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Chart;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Legend;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
|
||||
use PhpOffice\PhpSpreadsheet\Chart\Title;
|
||||
use Carbon\Carbon;
|
||||
use Maatwebsite\Excel\Concerns\WithColumnWidths;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
|
||||
class UserMultiSheetExport extends BaseExport implements FromArray, WithTitle, WithCharts, WithHeadings, WithColumnWidths
|
||||
{
|
||||
private $data;
|
||||
private $reportData;
|
||||
private $user;
|
||||
private $userName;
|
||||
private $periodDates;
|
||||
private $countDate;
|
||||
|
||||
const COLUMN_FIRST = 'B';
|
||||
const OFFSET_CHART = [10, 30];
|
||||
const POSITIONS_CHART = [['A8', 'E38'], ['F8', 'R38'], ['S8', 'Z30']];
|
||||
const TEXT_USER = 'Worked by all users';
|
||||
const TEXT_TASK = 'Hours by tasks';
|
||||
const TEXT_PROJECT = 'Hours by projects';
|
||||
|
||||
public function __construct(array $collection, $userId, $userName, array $periodDates)
|
||||
{
|
||||
$this->data = $collection['reportCharts'];
|
||||
$this->reportData = $collection['reportData'];
|
||||
$this->user = $userId;
|
||||
$this->userName = $userName;
|
||||
$this->periodDates = $periodDates;
|
||||
$this->countDate = count($this->periodDates);
|
||||
}
|
||||
|
||||
public function columnWidths(): array
|
||||
{
|
||||
$columnWidths = ['A' => 45];
|
||||
$currentColumn = 2;
|
||||
while ($currentColumn <= $this->countDate + 1) {
|
||||
$columnWidths[Coordinate::stringFromColumnIndex($currentColumn)] = 25;
|
||||
$currentColumn++;
|
||||
}
|
||||
|
||||
return $columnWidths;
|
||||
}
|
||||
public function array(): array
|
||||
{
|
||||
if (isset($this->data['total_spent_time_day']['datasets'])) {
|
||||
foreach ($this->data['total_spent_time_day']['datasets'] as $userId => $user) {
|
||||
if ($userId !== $this->user)
|
||||
continue;
|
||||
|
||||
$resultRow = [];
|
||||
$resultRow[] = $user['label'] ?? '';
|
||||
$this->userName = $user['label'] ?? '';
|
||||
if (isset($user['data'])) {
|
||||
foreach ($user['data'] as $date => $time) {
|
||||
$resultRow[] = $this->formatDuration($time);
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->data['total_spent_time_day_and_tasks']['datasets'])) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = 'task name';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$result[] = $resultRow;
|
||||
|
||||
foreach ($this->data['total_spent_time_day_and_tasks']['datasets'] as $userId => $userTasks) {
|
||||
if ($userId !== $this->user)
|
||||
continue;
|
||||
|
||||
foreach ($userTasks as $taskId => $task) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = $task['label'] ?? '';
|
||||
if (isset($task['data'])) {
|
||||
foreach ($task['data'] as $date => $time) {
|
||||
$resultRow[] = $this->formatDuration($time);
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($this->data['total_spent_time_day_and_projects']['datasets'])) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = 'task project';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$resultRow[] = ' ';
|
||||
$result[] = $resultRow;
|
||||
|
||||
foreach ($this->data['total_spent_time_day_and_projects']['datasets'] as $userId => $userTasks) {
|
||||
if ($userId !== $this->user)
|
||||
continue;
|
||||
|
||||
foreach ($userTasks as $taskId => $task) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = $task['label'] ?? '';
|
||||
if (isset($task['data'])) {
|
||||
foreach ($task['data'] as $date => $time) {
|
||||
$resultRow[] = $this->formatDuration($time);
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->reportData)) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = 'User Name';
|
||||
$resultRow[] = 'User Email';
|
||||
$resultRow[] = 'Project Name';
|
||||
$resultRow[] = 'Project created at';
|
||||
$resultRow[] = 'Task name';
|
||||
$resultRow[] = 'Task priority';
|
||||
$resultRow[] = 'Task status';
|
||||
$resultRow[] = 'Task due date';
|
||||
$resultRow[] = 'Task description';
|
||||
$result[] = $resultRow;
|
||||
|
||||
foreach ($this->reportData as $userId => $user) {
|
||||
if ($userId !== $this->user)
|
||||
continue;
|
||||
|
||||
if (isset($user['projects'])) {
|
||||
foreach ($user['projects'] as $projectId => $project) {
|
||||
|
||||
if (isset($project['tasks'])) {
|
||||
foreach ($project['tasks'] as $taskId => $task) {
|
||||
$resultRow = [];
|
||||
$resultRow[] = $user['full_name'] ?? '';
|
||||
$resultRow[] = $user['email'] ?? '';
|
||||
$resultRow[] = $project['name'] ?? '';
|
||||
$resultRow[] = $project['created_at'] ?? '';
|
||||
$resultRow[] = $task['task_name'] ?? '';
|
||||
$resultRow[] = $task['priority'] ?? '';
|
||||
$resultRow[] = $task['status'] ?? '';
|
||||
$resultRow[] = $task['due_date'] ?? '';
|
||||
$resultRow[] = $task['description'] ?? '';
|
||||
$result[] = $resultRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function charts()
|
||||
{
|
||||
$createDataSeries = function ($label, $categories, $values) {
|
||||
return new DataSeries(
|
||||
DataSeries::TYPE_LINECHART,
|
||||
DataSeries::GROUPING_STANDARD,
|
||||
[0],
|
||||
$label,
|
||||
$categories,
|
||||
$values
|
||||
);
|
||||
};
|
||||
|
||||
$createChart = function ($name, $title, $position, $offset, $startColumn, $endColumn, $rowCount, $columnLast) use ($createDataSeries) {
|
||||
$series = [];
|
||||
if (empty($rowCount)) {
|
||||
$label = [new DataSeriesValues('String', "'" . $this->title() . "'" . '!A2', null, 1)];
|
||||
$categories = [new DataSeriesValues('String', "'" . $this->title() . "'" . "!{$startColumn}1:{$endColumn}1", null, $columnLast)];
|
||||
$values = [new DataSeriesValues('Number', "'" . $this->title() . "'" . "!{$startColumn}2:{$endColumn}2", null, $columnLast)];
|
||||
$series[] = $createDataSeries($label, $categories, $values);
|
||||
} else {
|
||||
for ($i = $rowCount[0]; $i < $rowCount[1]; $i++) {
|
||||
$label = [new DataSeriesValues('String', "'" . $this->title() . "'" . '!A' . $i, null, $i)];
|
||||
$categories = [new DataSeriesValues('String', "'" . $this->title() . "'" . "!{$startColumn}1:{$endColumn}1", null, $columnLast)];
|
||||
$values = [new DataSeriesValues('Number', "'" . $this->title() . "'" . "!{$startColumn}" . $i . ":!{$endColumn}" . $i, null, $columnLast)];
|
||||
$series[] = $createDataSeries($label, $categories, $values);
|
||||
}
|
||||
}
|
||||
|
||||
$plot = new PlotArea(null, $series);
|
||||
$legend = new Legend();
|
||||
$chart = new Chart($name, new Title($title), $legend, $plot);
|
||||
$chart->setTopLeftPosition($position[0]);
|
||||
$chart->setTopLeftOffset($offset[0], $offset[0]);
|
||||
$chart->setBottomRightPosition($position[1]);
|
||||
$chart->setBottomRightOffset($offset[1], $offset[1]);
|
||||
return $chart;
|
||||
};
|
||||
|
||||
$columnNumber = $this->countDate;
|
||||
$charts = [];
|
||||
$columnLast = Coordinate::stringFromColumnIndex($columnNumber + 1);
|
||||
$charts[] = $createChart(static::TEXT_USER, static::TEXT_USER, static::POSITIONS_CHART[0], static::OFFSET_CHART, static::COLUMN_FIRST, $columnLast, [], $columnNumber);
|
||||
$charts[] = $createChart(static::TEXT_TASK, static::TEXT_TASK, static::POSITIONS_CHART[1], static::OFFSET_CHART, static::COLUMN_FIRST, $columnLast, [4, $this->rowCount() + 4], $columnNumber);
|
||||
$charts[] = $createChart(static::TEXT_PROJECT, static::TEXT_PROJECT, static::POSITIONS_CHART[2], static::OFFSET_CHART, static::COLUMN_FIRST, $columnLast, [$this->rowCount() + 5, $this->rowCountProject() + $this->rowCount() + 5], $columnNumber);
|
||||
return $charts;
|
||||
}
|
||||
public function headings(): array
|
||||
{
|
||||
return [
|
||||
'User Name',
|
||||
...collect($this->periodDates)->map(fn($date) => Carbon::parse($date)->format('y-m-d'))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
return \Str::limit("$this->userName", 10);
|
||||
}
|
||||
|
||||
protected function rowCount()
|
||||
{
|
||||
$count = 0;
|
||||
if (isset($this->data['total_spent_time_day_and_tasks']['datasets'])) {
|
||||
foreach ($this->data['total_spent_time_day_and_tasks']['datasets'] as $userId => $userTasks) {
|
||||
if ($userId !== $this->user)
|
||||
continue;
|
||||
|
||||
$count += count($userTasks);
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
protected function rowCountProject()
|
||||
{
|
||||
$count = 0;
|
||||
if (isset($this->data['total_spent_time_day_and_projects']['datasets'])) {
|
||||
foreach ($this->data['total_spent_time_day_and_projects']['datasets'] as $userId => $userTasks) {
|
||||
if ($userId !== $this->user)
|
||||
continue;
|
||||
|
||||
$count += count($userTasks);
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user