first commit
This commit is contained in:
203
app/Services/AttachmentService.php
Normal file
203
app/Services/AttachmentService.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Contracts\AttachmentAble;
|
||||
use App\Enums\AttachmentStatus;
|
||||
use App\Jobs\VerifyAttachmentHash;
|
||||
use App\Models\Attachment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskComment;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Storage;
|
||||
|
||||
class AttachmentService implements \App\Contracts\AttachmentService
|
||||
{
|
||||
public const DISK = 'attachments';
|
||||
private readonly FilesystemAdapter $storage;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->storage = Storage::disk($this::DISK);
|
||||
}
|
||||
public function storeFile(UploadedFile $file, Attachment $attachment): string|false
|
||||
{
|
||||
return $file->storeAs(
|
||||
$this->getPath($attachment),
|
||||
['disk' => $this::DISK]
|
||||
);
|
||||
}
|
||||
|
||||
public function handleProjectChange(Task $parent): void
|
||||
{
|
||||
$newProjectId = $parent->getProjectId();
|
||||
$parent->attachmentsRelation()->lazyById()
|
||||
->each(fn(Attachment $attachment) => $this->moveAttachment($attachment, $newProjectId));
|
||||
|
||||
$parent->comments()->lazyById()
|
||||
->each(fn(TaskComment $comment) => $comment->attachmentsRelation()->lazyById()
|
||||
->each(fn(Attachment $attachment) => $this->moveAttachment($attachment, $newProjectId)));
|
||||
}
|
||||
|
||||
public function moveAttachment(Attachment $attachment, $newProjectId): void
|
||||
{
|
||||
if ($attachment->status === AttachmentStatus::NOT_ATTACHED) {
|
||||
return;
|
||||
}
|
||||
$oldPath = $this->getPath($attachment);
|
||||
$oldProjectId = $attachment->project_id;
|
||||
$attachment->project_id = $newProjectId;
|
||||
$attachment->saveQuietly();
|
||||
$newPath = $this->getPath($attachment);
|
||||
|
||||
$fileExists = $this->storage->exists($oldPath);
|
||||
if (!$fileExists || !$this->storage->move($oldPath, $newPath)) {
|
||||
$attachment->project_id = $oldProjectId;
|
||||
$attachment->saveQuietly();
|
||||
\Log::error("Unable to move attachment`s file", [
|
||||
'attachment_id' => $attachment->id,
|
||||
'attachmentable_id' => $attachment->attachmentable_id,
|
||||
'attachmentable_type' => $attachment->attachmentable_type,
|
||||
'attachment_project_id' => $attachment->project_id,
|
||||
'old_attachment_project_id' => $oldProjectId,
|
||||
'old_path' => $oldPath,
|
||||
'new_path' => $newPath
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteAttachment(Attachment $attachment): void
|
||||
{
|
||||
if (($fileExists = $this->fileExists($attachment)) && $this->deleteFile($attachment)) {
|
||||
$attachment->deleteQuietly();
|
||||
} elseif (!$fileExists) {
|
||||
$attachment->deleteQuietly();
|
||||
} else {
|
||||
\Log::error("Unable to delete attachment`s file", [
|
||||
'attachment_id' => $attachment->id,
|
||||
'attachmentable_id' => $attachment->attachmentable_id,
|
||||
'attachmentable_type' => $attachment->attachmentable_type,
|
||||
'attachment_project_id' => $attachment->project_id,
|
||||
'path' => $this->getPath($attachment)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteAttachments(array $attachmentsIds): void
|
||||
{
|
||||
Attachment::whereIn('id', $attachmentsIds)
|
||||
->each(fn (Attachment $attachment) => $this->deleteAttachment($attachment));
|
||||
}
|
||||
|
||||
public function handleParentDeletion(AttachmentAble $parent): void
|
||||
{
|
||||
$parent->attachmentsRelation()->lazyById()
|
||||
->each(fn (Attachment $attachment) => $this->deleteAttachment($attachment));
|
||||
}
|
||||
|
||||
public function handleProjectDeletion(Project $project): void
|
||||
{
|
||||
Attachment::whereProjectId($project->id)->delete();
|
||||
|
||||
if ($this->storage->directoryExists($this->getProjectPath($project)) && !$this->storage->deleteDirectory($this->getProjectPath($project))) {
|
||||
\Log::error("Unable to delete project's attachments directory", [
|
||||
'project_id' => $project->id,
|
||||
'path' => $this->getProjectPath($project)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function fileExists(Attachment $attachment): bool
|
||||
{
|
||||
return $this->storage->exists($this->getPath($attachment));
|
||||
}
|
||||
|
||||
private function deleteFile(Attachment $attachment): bool
|
||||
{
|
||||
return $this->storage->delete($this->getPath($attachment));
|
||||
}
|
||||
|
||||
public function attach(AttachmentAble $parent, array $idsToAttach): void
|
||||
{
|
||||
$projectId = $parent->getProjectId();
|
||||
Attachment::whereIn('id', $idsToAttach)
|
||||
->each(function (Attachment $attachment) use ($parent, $projectId) {
|
||||
if ($attachment->status !== AttachmentStatus::NOT_ATTACHED) {
|
||||
return;
|
||||
}
|
||||
$tmpPath = $this->getPath($attachment);
|
||||
|
||||
if ($this->storage->exists($tmpPath)) {
|
||||
$attachment->attachmentAbleRelation()->associate($parent);
|
||||
$attachment->status = AttachmentStatus::PROCESSING;
|
||||
$attachment->project_id = $projectId;
|
||||
$attachment->saveQuietly();
|
||||
|
||||
$newPath = $this->getPath($attachment);
|
||||
$this->storage->move($tmpPath, $newPath);
|
||||
|
||||
VerifyAttachmentHash::dispatch($attachment)->afterCommit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function getProjectPath(Project $project): string
|
||||
{
|
||||
return "projects/{$project->id}";
|
||||
}
|
||||
|
||||
public function getPath(Attachment $attachment): string
|
||||
{
|
||||
return match ($attachment->status) {
|
||||
AttachmentStatus::NOT_ATTACHED => "users/{$attachment->user_id}/{$attachment->id}",
|
||||
AttachmentStatus::PROCESSING,
|
||||
AttachmentStatus::GOOD,
|
||||
AttachmentStatus::BAD => "projects/{$attachment->project_id}/{$attachment->id}",
|
||||
};
|
||||
}
|
||||
|
||||
public function getFullPath(Attachment $attachment): string
|
||||
{
|
||||
return $this->storage->path($this->getPath($attachment));
|
||||
}
|
||||
|
||||
public function readStream(Attachment $attachment)
|
||||
{
|
||||
return $this->storage->readStream($this->getPath($attachment));
|
||||
}
|
||||
|
||||
public function getHashAlgo(): string
|
||||
{
|
||||
return $this->storage->getConfig()['checksum_algo'] ?? 'sha512/256';
|
||||
}
|
||||
|
||||
public function getHashSum(Attachment|string $attachment): false|string
|
||||
{
|
||||
return $this->storage->checksum($this->callIfInstance($attachment, 'getPath'), ['checksum_algo' => $this->getHashAlgo()]);
|
||||
}
|
||||
|
||||
public function getMimeType(Attachment|string $attachment): false|string
|
||||
{
|
||||
return $this->storage->mimeType($this->callIfInstance($attachment, 'getPath'));
|
||||
}
|
||||
|
||||
private function callIfInstance(Attachment|string $attachment, $action)
|
||||
{
|
||||
return $attachment instanceof Attachment ? $this->{$action}($attachment) : $attachment;
|
||||
}
|
||||
|
||||
public function verifyHash(Attachment $attachment): void
|
||||
{
|
||||
if ($attachment->status === AttachmentStatus::PROCESSING && $hash = $this->getHashSum($attachment)) {
|
||||
$attachment->hash = $hash;
|
||||
$attachment->status = AttachmentStatus::GOOD;
|
||||
} elseif ($attachment->status === AttachmentStatus::GOOD && $attachment->hash !== $this->getHashSum($attachment)
|
||||
) {
|
||||
$attachment->status = AttachmentStatus::BAD;
|
||||
}
|
||||
|
||||
$attachment->save();
|
||||
}
|
||||
}
|
||||
47
app/Services/InvitationService.php
Normal file
47
app/Services/InvitationService.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Invitation;
|
||||
use Exception;
|
||||
use Str;
|
||||
|
||||
class InvitationService
|
||||
{
|
||||
/**
|
||||
* The invitation expires in one months.
|
||||
*/
|
||||
protected const EXPIRATION_TIME_IN_DAYS = 30;
|
||||
|
||||
/**
|
||||
* Create new invitation.
|
||||
*
|
||||
* @param array $user
|
||||
* @return Invitation|null
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function create(array $user): ?Invitation
|
||||
{
|
||||
return Invitation::create([
|
||||
'email' => $user['email'],
|
||||
'key' => Str::uuid(),
|
||||
'expires_at' => now()->addDays(self::EXPIRATION_TIME_IN_DAYS),
|
||||
'role_id' => $user['role_id']
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update invitation.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Invitation|null
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function update(int $id): ?Invitation
|
||||
{
|
||||
return tap(Invitation::find($id))->update([
|
||||
'key' => Str::uuid(),
|
||||
'expires_at' => now()->addDays(self::EXPIRATION_TIME_IN_DAYS)
|
||||
]);
|
||||
}
|
||||
}
|
||||
127
app/Services/ModuleActivatorService.php
Normal file
127
app/Services/ModuleActivatorService.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Cache;
|
||||
use Exception;
|
||||
use JsonException;
|
||||
use Nwidart\Modules\Contracts\ActivatorInterface;
|
||||
use Nwidart\Modules\Module;
|
||||
use App\Models\Module as ModuleModel;
|
||||
|
||||
class ModuleActivatorService implements ActivatorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public function enable(Module $module): void
|
||||
{
|
||||
$this->setActiveByName($module->getName(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public function disable(Module $module): void
|
||||
{
|
||||
$this->setActiveByName($module->getName(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public function hasStatus(Module $module, bool $status): bool
|
||||
{
|
||||
$moduleStatuses = Cache::store('octane')
|
||||
->rememberForever(config('modules.activators.amazing.cache_key'), function () {
|
||||
$configFile = config('modules.activators.amazing.file_name');
|
||||
|
||||
$databaseModules = [];
|
||||
|
||||
try {
|
||||
foreach (ModuleModel::all()->toArray() as $module) {
|
||||
$databaseModules[$module['name']] = $module['enabled'];
|
||||
}
|
||||
} catch (Exception) {
|
||||
// We can't communicate with db - then do nothing
|
||||
// This can happen on first install when we are trying to migrate over clear database
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
$this->getFileConfig($configFile),
|
||||
$this->getFileConfig("$configFile." . config('app.env')),
|
||||
$this->getFileConfig("$configFile.local"),
|
||||
$databaseModules,
|
||||
);
|
||||
});
|
||||
|
||||
if (isset($moduleStatuses[$module->getName()])) {
|
||||
return $moduleStatuses[$module->getName()] ?? false === $status;
|
||||
}
|
||||
|
||||
return (bool)$module->json()?->active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setActive(Module $module, bool $active): void
|
||||
{
|
||||
$this->setActiveByName($module->getName(), $active);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setActiveByName(string $name, bool $active): void
|
||||
{
|
||||
ModuleModel::firstOrCreate(['name' => $name])->update(['enabled' => $active]);
|
||||
$this->flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public function delete(Module $module): void
|
||||
{
|
||||
ModuleModel::firstOrFail($module->getName())->delete();
|
||||
$this->flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
ModuleModel::truncate();
|
||||
$this->flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return array
|
||||
* @throws JsonException
|
||||
*/
|
||||
private function getFileConfig($name): array
|
||||
{
|
||||
$filePath = base_path("$name.json");
|
||||
return file_exists($filePath) ? json_decode(file_get_contents($filePath), true, 512, JSON_THROW_ON_ERROR) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function flushCache(): void
|
||||
{
|
||||
Cache::store('octane')->forget(config('modules.activators.amazing.cache_key'));
|
||||
}
|
||||
}
|
||||
22
app/Services/ProductionScreenshotService.php
Normal file
22
app/Services/ProductionScreenshotService.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Contracts\ScreenshotService as ScreenshotServiceContract;
|
||||
use App\Models\TimeInterval;
|
||||
|
||||
class ProductionScreenshotService extends ScreenshotServiceContract
|
||||
{
|
||||
public function getScreenshotPath(TimeInterval|int $interval): string
|
||||
{
|
||||
return self::PARENT_FOLDER . hash('sha256', optional($interval)->id ?: $interval) . '.' . self::FILE_FORMAT;
|
||||
}
|
||||
|
||||
public function getThumbPath(TimeInterval|int $interval): string
|
||||
{
|
||||
return self::PARENT_FOLDER . self::THUMBS_FOLDER . hash(
|
||||
'sha256',
|
||||
optional($interval)->id ?: $interval
|
||||
) . '.' . self::FILE_FORMAT;
|
||||
}
|
||||
}
|
||||
22
app/Services/ProjectMemberService.php
Normal file
22
app/Services/ProjectMemberService.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Project;
|
||||
|
||||
class ProjectMemberService
|
||||
{
|
||||
public static function getMembers(int $projectId): array
|
||||
{
|
||||
return Project::find($projectId, 'id')
|
||||
->where('id', $projectId)
|
||||
->with('users')
|
||||
->first()
|
||||
->only(['id', 'users']);
|
||||
}
|
||||
|
||||
public static function syncMembers(int $projectId, array $users): array
|
||||
{
|
||||
return Project::findOrFail($projectId)->users()->sync($users);
|
||||
}
|
||||
}
|
||||
168
app/Services/SettingsProviderService.php
Normal file
168
app/Services/SettingsProviderService.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Contracts\SettingsProvider;
|
||||
use App\Models\Setting;
|
||||
use Cache;
|
||||
use Exception;
|
||||
use PDOException;
|
||||
|
||||
class SettingsProviderService implements SettingsProvider
|
||||
{
|
||||
protected string $scope = 'app';
|
||||
|
||||
public function __construct(private readonly Setting $model, private readonly bool $saveScope = true)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets scope for the next request to settings module
|
||||
*
|
||||
* @param string $moduleName
|
||||
*
|
||||
* @return SettingsProviderService
|
||||
*/
|
||||
public function scope(string $moduleName): SettingsProviderService
|
||||
{
|
||||
$this->scope = $moduleName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inerhitDoc
|
||||
*/
|
||||
final public function all(): array
|
||||
{
|
||||
$scope = $this->scope;
|
||||
|
||||
if (!$this->saveScope) {
|
||||
$this->scope = '';
|
||||
}
|
||||
|
||||
$result = $this->model::whereModuleName($scope)
|
||||
->get()
|
||||
->map(static fn(Setting $item) => [$item->key => $item->value])
|
||||
->collapse()
|
||||
->toArray();
|
||||
|
||||
try {
|
||||
Cache::store('octane')->forever("settings:$scope", $result);
|
||||
} catch (Exception) {
|
||||
// DO NOTHING
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inerhitDoc
|
||||
*/
|
||||
final public function get(string $key = null, mixed $default = null): mixed
|
||||
{
|
||||
$scope = $this->scope;
|
||||
|
||||
if (!$this->saveScope) {
|
||||
$this->scope = '';
|
||||
}
|
||||
|
||||
try {
|
||||
$cached = Cache::store('octane')->get("settings:$scope");
|
||||
|
||||
if (!isset($cached[$key])) {
|
||||
$cached[$key] = optional(
|
||||
$this->model::where([
|
||||
'module_name' => $scope,
|
||||
'key' => $key,
|
||||
])->first()
|
||||
)->value ?? $default;
|
||||
|
||||
Cache::store('octane')->put("settings:$scope", $cached);
|
||||
}
|
||||
|
||||
return $cached[$key];
|
||||
} catch (PDOException) {
|
||||
return $default;
|
||||
} catch (Exception) {
|
||||
return optional(
|
||||
$this->model::where([
|
||||
'module_name' => $scope,
|
||||
'key' => $key,
|
||||
])->first()
|
||||
)->value ?? $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inerhitDoc
|
||||
*/
|
||||
final public function set(mixed $key, mixed $value = null, bool $onlyIfNotExists = false): void
|
||||
{
|
||||
$scope = $this->scope;
|
||||
|
||||
if (!$this->saveScope) {
|
||||
$this->scope = '';
|
||||
}
|
||||
|
||||
if (is_array($key)) {
|
||||
foreach ($key as $_key => $_value) {
|
||||
if ($onlyIfNotExists &&
|
||||
$this->model::where([
|
||||
'module_name' => $scope,
|
||||
'key' => $_key,
|
||||
])->exists()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->model::updateOrCreate([
|
||||
'module_name' => $scope,
|
||||
'key' => $_key,
|
||||
], [
|
||||
'value' => $_value,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if ($onlyIfNotExists &&
|
||||
$this->model::where([
|
||||
'module_name' => $scope,
|
||||
'key' => $key,
|
||||
])->exists()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->model::updateOrCreate([
|
||||
'module_name' => $scope,
|
||||
'key' => $key,
|
||||
], [
|
||||
'value' => $value,
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
Cache::store('octane')->forget("settings:$scope");
|
||||
} catch (Exception) {
|
||||
// DO NOTHING
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inerhitDoc
|
||||
*/
|
||||
final public function flush(): void
|
||||
{
|
||||
$scope = $this->scope;
|
||||
|
||||
if (!$this->saveScope) {
|
||||
$this->scope = '';
|
||||
}
|
||||
|
||||
try {
|
||||
Cache::store('octane')->forget("settings:$scope");
|
||||
} catch (Exception) {
|
||||
// DO NOTHING
|
||||
}
|
||||
}
|
||||
}
|
||||
280
app/Services/UniversalReportServiceProject.php
Normal file
280
app/Services/UniversalReportServiceProject.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\Task;
|
||||
use App\Models\TimeInterval;
|
||||
use App\Models\UniversalReport;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class UniversalReportServiceProject
|
||||
{
|
||||
private Carbon $startAt;
|
||||
private Carbon $endAt;
|
||||
private UniversalReport $report;
|
||||
private array $periodDates;
|
||||
|
||||
public function __construct(Carbon $startAt, Carbon $endAt, UniversalReport $report, array $periodDates = [])
|
||||
{
|
||||
$this->startAt = $startAt;
|
||||
$this->endAt = $endAt;
|
||||
$this->report = $report;
|
||||
$this->periodDates = $periodDates;
|
||||
}
|
||||
|
||||
public function getProjectReportData()
|
||||
{
|
||||
$projectFields = ['id'];
|
||||
foreach ($this->report->fields['base'] as $field) {
|
||||
$projectFields[] = 'projects.' . $field;
|
||||
}
|
||||
$taskRelations = [];
|
||||
$taskFields = ['id', 'tasks.project_id'];
|
||||
foreach ($this->report->fields['tasks'] as $field) {
|
||||
if ($field !== 'priority' && $field !== 'status') {
|
||||
$taskFields[] = 'tasks.' . $field;
|
||||
} else {
|
||||
$taskRelations[] = 'tasks.' . $field;
|
||||
$taskFields[] = 'tasks.' . $field . '_id';
|
||||
}
|
||||
}
|
||||
$userFields = ['id'];
|
||||
foreach ($this->report->fields['users'] as $field) {
|
||||
$userFields[] = 'users.' . $field;
|
||||
}
|
||||
$projectsQuery = Project::with(['tasks' => function ($query) use ($taskFields) {
|
||||
$query->select($taskFields);
|
||||
}, 'users' => function ($query) use ($userFields) {
|
||||
$query->select($userFields);
|
||||
}])
|
||||
->select(array_merge($projectFields))->whereIn('id', $this->report->data_objects);
|
||||
if (!empty($taskRelations)) $projectsQuery = $projectsQuery->with($taskRelations);
|
||||
$projects = $projectsQuery->get();
|
||||
$tasksId = [];
|
||||
foreach ($projects as $project) {
|
||||
$tasksId[] = $project->tasks->pluck('id');
|
||||
}
|
||||
$taskQuery = Task::whereIn('project_id', $projects->pluck('id'));
|
||||
$projectIdsIndexedByTaskIds = $taskQuery->pluck('project_id', 'id');
|
||||
$tasksId = collect($tasksId)->flatten()->toArray();
|
||||
$endAt = clone $this->endAt;
|
||||
$endAt = $endAt->endOfDay();
|
||||
$totalSpentTimeByUserAndDay = TimeInterval::whereIn('task_id', $tasksId)
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('user_id', 'task_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_by_user_and_day')
|
||||
->groupBy('user_id', 'date_at', 'task_id')->get();
|
||||
$totalSpentTimeByDay = TimeInterval::whereIn('task_id', $tasksId)
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('task_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_by_day')
|
||||
->groupBy('date_at', 'task_id')->get();
|
||||
$intervalProjectId = null;
|
||||
$workedTimeByDayUser = [];
|
||||
$totalSpentTimeUser = [];
|
||||
foreach ($totalSpentTimeByUserAndDay as $timeInterval) {
|
||||
$intervalDate = $timeInterval['date_at'];
|
||||
if (isset($projectIdsIndexedByTaskIds[$timeInterval->task_id])) {
|
||||
$intervalProjectId = $projectIdsIndexedByTaskIds[$timeInterval->task_id];
|
||||
}
|
||||
$intervalUserId = $timeInterval->user_id;
|
||||
$startDateTime = Carbon::parse($this->startAt);
|
||||
$endDateTime = Carbon::parse($this->endAt);
|
||||
|
||||
if (!isset($workedTimeByDayUser[$intervalProjectId][$intervalUserId])) {
|
||||
$workedTimeByDayUser[$intervalProjectId][$intervalUserId] = [];
|
||||
}
|
||||
if (!isset($workedTimeByDayUser[$intervalProjectId][$intervalUserId][$intervalDate])) {
|
||||
$workedTimeByDayUser[$intervalProjectId][$intervalUserId][$intervalDate] = 0;
|
||||
}
|
||||
if (!isset($totalSpentTimeUser[$intervalProjectId][$intervalUserId])) {
|
||||
$totalSpentTimeUser[$intervalProjectId][$intervalUserId] = 0;
|
||||
}
|
||||
$workedTimeByDayUser[$intervalProjectId][$intervalUserId][$intervalDate] += $timeInterval->total_spent_time_by_user_and_day;
|
||||
$totalSpentTimeUser[$intervalProjectId][$intervalUserId] += $timeInterval->total_spent_time_by_user_and_day;
|
||||
while ($startDateTime <= $endDateTime) {
|
||||
$currentDate = $startDateTime->format('Y-m-d');
|
||||
|
||||
if ($currentDate !== $intervalDate) {
|
||||
$workedTimeByDayUser[$intervalProjectId][$intervalUserId][$currentDate] = 0;
|
||||
}
|
||||
$startDateTime->modify('+1 day');
|
||||
}
|
||||
}
|
||||
$workedTimeByDay = [];
|
||||
foreach ($totalSpentTimeByDay as $timeInterval) {
|
||||
$intervalDate = $timeInterval['date_at'];
|
||||
$intervalProjectId = $projectIdsIndexedByTaskIds[$timeInterval->task_id];
|
||||
$startDateTime = \Carbon\Carbon::parse($this->startAt);
|
||||
$endDateTime = \Carbon\Carbon::parse($this->endAt);
|
||||
while ($startDateTime <= $endDateTime) {
|
||||
$currentDate = $startDateTime->format('Y-m-d');
|
||||
|
||||
if ($currentDate !== $intervalDate) {
|
||||
$workedTimeByDay[$intervalProjectId][$currentDate] = 0;
|
||||
}
|
||||
$startDateTime->modify('+1 day');
|
||||
}
|
||||
if (!isset($workedTimeByDay[$intervalProjectId])) {
|
||||
$workedTimeByDay[$intervalProjectId] = [];
|
||||
}
|
||||
if (!isset($workedTimeByDay[$intervalProjectId][$intervalDate])) {
|
||||
$workedTimeByDay[$intervalProjectId][$intervalDate] = 0;
|
||||
}
|
||||
$workedTimeByDay[$intervalProjectId][$intervalDate] += $timeInterval->total_spent_time_by_day;
|
||||
}
|
||||
foreach ($projects as $project) {
|
||||
if (isset($workedTimeByDay[$project->id]))
|
||||
$project->worked_time_day = $workedTimeByDay[$project->id];
|
||||
foreach ($project->users as $user) {
|
||||
if (isset($workedTimeByDayUser[$project->id][$user->id]))
|
||||
$user->workers_day = $workedTimeByDayUser[$project->id][$user->id];
|
||||
if (isset($totalSpentTimeUser[$project->id][$user->id]))
|
||||
$user->total_spent_time_by_user = $totalSpentTimeUser[$project->id][$user->id];
|
||||
else
|
||||
$user->total_spent_time_by_user = 0;
|
||||
}
|
||||
}
|
||||
$projects = $projects->keyBy('id')->toArray();
|
||||
foreach ($projects as &$project) {
|
||||
if (isset($project['created_at'])) {
|
||||
$date = Carbon::parse($project['created_at']);
|
||||
$project['created_at'] = $date->format('Y-m-d H:i:s');
|
||||
}
|
||||
foreach ($project['tasks'] as &$task) {
|
||||
if (isset($task['priority'])) $task['priority'] = $task['priority']['name'];
|
||||
if (isset($task['status'])) $task['status'] = $task['status']['name'];
|
||||
}
|
||||
}
|
||||
return $projects;
|
||||
}
|
||||
|
||||
public function getProjectReportCharts()
|
||||
{
|
||||
$result = [];
|
||||
|
||||
if (count($this->report->charts) === 0) {
|
||||
return $result;
|
||||
}
|
||||
$projects = Project::query()
|
||||
->with(['tasks', 'users'])
|
||||
->whereIn('id', $this->report->data_objects)->get();
|
||||
$usersId = [];
|
||||
$tasksId = [];
|
||||
foreach ($projects as $project) {
|
||||
$projectsName = $project->pluck('name', 'id');
|
||||
$usersId[] = $project->users->pluck('id');
|
||||
$tasksId[] = $project->tasks->pluck('id');
|
||||
foreach ($project->users as $user) {
|
||||
$userNames = $user->pluck('full_name', 'id');
|
||||
}
|
||||
}
|
||||
$endAt = clone $this->endAt;
|
||||
$endAt = $endAt->endOfDay();
|
||||
$usersId = collect($usersId)->flatten()->toArray();
|
||||
$tasksId = collect($tasksId)->flatten()->toArray();
|
||||
if (in_array('total_spent_time_day', $this->report->charts)) {
|
||||
$total_spent_time_day = [
|
||||
'datasets' => []
|
||||
];
|
||||
$taskQuery = Task::whereIn('project_id', $projects->pluck('id'));
|
||||
$projectIdsIndexedByTaskIds = $taskQuery->pluck('project_id', 'id');
|
||||
TimeInterval::whereIn('task_id', $tasksId)
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('task_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_day')
|
||||
->groupBy('date_at', 'task_id')
|
||||
->get()
|
||||
->each(function ($timeInterval) use (&$total_spent_time_day, $userNames, $projectIdsIndexedByTaskIds, $projectsName) {
|
||||
$time = 0;
|
||||
$projectId = (int)$timeInterval->task->project_id;
|
||||
|
||||
foreach ($projectIdsIndexedByTaskIds as $taskId => $id) {
|
||||
if ($projectId === $id) {
|
||||
$time += $timeInterval->total_spent_time_day;
|
||||
}
|
||||
}
|
||||
if (!isset($total_spent_time_day['datasets'][$projectId])) {
|
||||
$color = sprintf('#%02X%02X%02X', rand(0, 255), rand(0, 255), rand(0, 255));
|
||||
$total_spent_time_day['datasets'][$projectId] = [
|
||||
'label' => $projectsName[$projectId] ?? ' ',
|
||||
'borderColor' => $color,
|
||||
'backgroundColor' => $color,
|
||||
'data' => [$timeInterval->date_at => $time],
|
||||
];
|
||||
}
|
||||
$total_spent_time_day['datasets'][$projectId]['data'][$timeInterval->date_at] = $time;
|
||||
});
|
||||
|
||||
foreach ($total_spent_time_day['datasets'] as $key => $item) {
|
||||
$this->fillNullDatesAsZeroTime($total_spent_time_day['datasets'], 'data');
|
||||
}
|
||||
$result['total_spent_time_day'] = $total_spent_time_day;
|
||||
}
|
||||
if (in_array('total_spent_time_day_and_users_separately', $this->report->charts)) {
|
||||
$total_spent_time_day_and_users_separately = [
|
||||
'datasets' => [],
|
||||
];
|
||||
|
||||
TimeInterval::whereIn('user_id', $usersId)
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('user_id', 'task_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_day_and_users_separately')
|
||||
->groupBy('user_id', 'date_at', 'task_id')
|
||||
->get()
|
||||
->each(function ($timeInterval) use (&$total_spent_time_day_and_users_separately, $userNames, $projectsName) {
|
||||
$time = $timeInterval->total_spent_time_day_and_users_separately;
|
||||
$projectId = (int)$timeInterval->task->project_id;
|
||||
if (!isset($total_spent_time_day_and_users_separately['datasets'][$projectId])) {
|
||||
$total_spent_time_day_and_users_separately['datasets'][$projectId] = [];
|
||||
}
|
||||
$userId = $timeInterval->user_id;
|
||||
if (!isset($total_spent_time_day_and_users_separately['datasets'][$projectId][$userId])) {
|
||||
$color = sprintf('#%02X%02X%02X', rand(0, 255), rand(0, 255), rand(0, 255));
|
||||
$total_spent_time_day_and_users_separately['datasets'][$projectId][$userId] = [
|
||||
'label' => $userNames[$timeInterval->user_id] ?? ' ',
|
||||
'projectLabel' => $projectsName[$projectId] ?? ' ',
|
||||
'borderColor' => $color,
|
||||
'backgroundColor' => $color,
|
||||
'data' => [$timeInterval->date_at => $time],
|
||||
];
|
||||
}
|
||||
$total_spent_time_day_and_users_separately['datasets'][$projectId][$userId]['data'][$timeInterval->date_at] = $time;
|
||||
});
|
||||
|
||||
foreach ($total_spent_time_day_and_users_separately['datasets'] as $key => $item) {
|
||||
$this->fillNullDatesAsZeroTime($total_spent_time_day_and_users_separately['datasets'][$key], 'data');
|
||||
}
|
||||
$result['total_spent_time_day_and_users_separately'] = $total_spent_time_day_and_users_separately;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function fillNullDatesAsZeroTime(array &$datesToFill, $key = null)
|
||||
{
|
||||
foreach ($this->periodDates as $date) {
|
||||
if (is_null($key)) {
|
||||
unset($datesToFill['']);
|
||||
array_key_exists($date, $datesToFill) ? '' : $datesToFill[$date] = 0.0;
|
||||
} else {
|
||||
foreach ($datesToFill as $k => $item) {
|
||||
unset($datesToFill[$k][$key]['']);
|
||||
|
||||
if (!array_key_exists($date, $item[$key])) {
|
||||
$datesToFill[$k][$key][$date] = 0.0;
|
||||
}
|
||||
ksort($datesToFill[$k][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
229
app/Services/UniversalReportServiceTask.php
Normal file
229
app/Services/UniversalReportServiceTask.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Task;
|
||||
use App\Models\TimeInterval;
|
||||
use App\Models\UniversalReport;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class UniversalReportServiceTask
|
||||
{
|
||||
private Carbon $startAt;
|
||||
private Carbon $endAt;
|
||||
private UniversalReport $report;
|
||||
private array $periodDates;
|
||||
|
||||
public function __construct(Carbon $startAt, Carbon $endAt, UniversalReport $report, array $periodDates = [])
|
||||
{
|
||||
$this->startAt = $startAt;
|
||||
$this->endAt = $endAt;
|
||||
$this->report = $report;
|
||||
$this->periodDates = $periodDates;
|
||||
}
|
||||
public function getTaskReportData()
|
||||
{
|
||||
$projectFields = ['id'];
|
||||
foreach ($this->report->fields['projects'] as $field) {
|
||||
$projectFields[] = 'projects.' . $field;
|
||||
}
|
||||
$taskRelations = [];
|
||||
$taskFields = ['id', 'tasks.project_id'];
|
||||
foreach ($this->report->fields['base'] as $field) {
|
||||
if ($field !== 'priority' && $field !== 'status') {
|
||||
$taskFields[] = 'tasks.' . $field;
|
||||
} else {
|
||||
$taskRelations[] = $field;
|
||||
$taskFields[] = 'tasks.' . $field . '_id';
|
||||
}
|
||||
}
|
||||
$userFields = ['id'];
|
||||
foreach ($this->report->fields['users'] as $field) {
|
||||
$userFields[] = 'users.' . $field;
|
||||
}
|
||||
$tasksQuery = Task::query()
|
||||
->with(['project' => function ($query) use ($projectFields) {
|
||||
$query->select($projectFields);
|
||||
}, 'users' => function ($query) use ($userFields) {
|
||||
$query->select($userFields);
|
||||
}])
|
||||
->select(array_merge($taskFields))->whereIn('id', $this->report->data_objects);
|
||||
if (!empty($taskRelations)) $tasksQuery = $tasksQuery->with($taskRelations);
|
||||
$tasks = $tasksQuery->get();
|
||||
$endAt = clone $this->endAt;
|
||||
$endAt = $endAt->endOfDay();
|
||||
$totalSpentTimeByUser = TimeInterval::whereIn('task_id', $tasks->pluck('id'))
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('user_id', 'task_id')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_by_user')
|
||||
->groupBy('user_id', 'task_id')
|
||||
->get();
|
||||
$totalSpentTimeByUserAndDay = TimeInterval::whereIn('task_id', $tasks->pluck('id'))
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('user_id', 'task_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_by_user_and_day')
|
||||
->groupBy('user_id', 'date_at', 'task_id')->get();
|
||||
$totalSpentTime = TimeInterval::whereIn('task_id', $tasks->pluck('id'))
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('task_id')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time')
|
||||
->groupBy('task_id')->pluck('total_spent_time', 'task_id');
|
||||
$totalSpentTimeByDay = TimeInterval::whereIn('task_id', $tasks->pluck('id'))
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('task_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_by_day')
|
||||
->groupBy('task_id', 'date_at')->get();
|
||||
foreach ($tasks as $task) {
|
||||
$worked_time_day = [];
|
||||
$startDateTime = Carbon::parse($this->startAt);
|
||||
$endDateTime = Carbon::parse($this->endAt);
|
||||
$task->total_spent_time = $totalSpentTime[$task->id] ?? 0;
|
||||
while ($startDateTime <= $endDateTime) {
|
||||
$currentDate = $startDateTime->format('Y-m-d');
|
||||
foreach ($totalSpentTimeByDay as $item) {
|
||||
if (($item['date_at'] === $currentDate) && (int)$item['task_id'] === $task->id) {
|
||||
$worked_time_day[$currentDate] = $item['total_spent_time_by_day'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isset($worked_time_day[$currentDate])) {
|
||||
$worked_time_day[$currentDate] = 0.0;
|
||||
}
|
||||
$startDateTime->modify('+1 day');
|
||||
}
|
||||
$task->worked_time_day = $worked_time_day;
|
||||
foreach ($task->users as $user) {
|
||||
$worked_time_day = [];
|
||||
$startDateTime = Carbon::parse($this->startAt);
|
||||
$endDateTime = Carbon::parse($this->endAt);
|
||||
$user->total_spent_time_by_user = $totalSpentTimeByUser->where('user_id', $user->id)->where('task_id', $task->id)->first()->total_spent_time_by_user ?? 0;
|
||||
while ($startDateTime <= $endDateTime) {
|
||||
$currentDate = $startDateTime->format('Y-m-d');
|
||||
foreach ($totalSpentTimeByUserAndDay as $item) {
|
||||
if (($item['date_at'] === $currentDate) && ($user->id === (int)$item['user_id'] && (int)$item['task_id'] === $task->id)) {
|
||||
$worked_time_day[$currentDate] = $item['total_spent_time_by_user_and_day'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isset($worked_time_day[$currentDate])) {
|
||||
$worked_time_day[$currentDate] = 0.0;
|
||||
}
|
||||
$startDateTime->modify('+1 day');
|
||||
}
|
||||
$user->workers_day = $worked_time_day;
|
||||
}
|
||||
}
|
||||
$tasks = $tasks->keyBy('id')->toArray();
|
||||
foreach ($tasks as &$task) {
|
||||
if (isset($task['priority'])) $task['priority'] = $task['priority']['name'];
|
||||
if (isset($task['status'])) $task['status'] = $task['status']['name'];
|
||||
}
|
||||
return $tasks;
|
||||
}
|
||||
|
||||
public function getTasksReportCharts()
|
||||
{
|
||||
$result = [];
|
||||
if (count($this->report->charts) === 0) {
|
||||
return $result;
|
||||
}
|
||||
$endAt = clone $this->endAt;
|
||||
$endAt = $endAt->endOfDay();
|
||||
$tasks = Task::whereIn('id', $this->report->data_objects)->get();
|
||||
if (in_array('total_spent_time_day', $this->report->charts)) {
|
||||
$total_spent_time_day = [
|
||||
'datasets' => []
|
||||
];
|
||||
$taskNames = $tasks->pluck('task_name', 'id');
|
||||
TimeInterval::whereIn('task_id', $this->report->data_objects)
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('task_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_day')
|
||||
->groupBy('task_id', 'date_at')
|
||||
->get()
|
||||
->each(function ($timeInterval) use (&$total_spent_time_day, $taskNames) {
|
||||
$time = sprintf("%02d.%02d", floor($timeInterval->total_spent_time_day / 3600), floor($timeInterval->total_spent_time_day / 60) % 60);
|
||||
if (!array_key_exists($timeInterval->task_id, $total_spent_time_day['datasets'])) {
|
||||
$color = sprintf('#%02X%02X%02X', rand(0, 255), rand(0, 255), rand(0, 255));
|
||||
$total_spent_time_day['datasets'][$timeInterval->task_id] = [
|
||||
'label' => $taskNames[$timeInterval->task_id] ?? ' ',
|
||||
'borderColor' => $color,
|
||||
'backgroundColor' => $color,
|
||||
'data' => [$timeInterval->date_at => $time],
|
||||
];
|
||||
}
|
||||
$total_spent_time_day['datasets'][$timeInterval->task_id]['data'][$timeInterval->date_at] = $time;
|
||||
});
|
||||
$this->fillNullDatesAsZeroTime($total_spent_time_day['datasets'], 'data');
|
||||
$result['total_spent_time_day'] = $total_spent_time_day;
|
||||
}
|
||||
|
||||
if (in_array('total_spent_time_day_users_separately', $this->report->charts)) {
|
||||
$total_spent_time_day_users_separately = [
|
||||
'datasets' => [],
|
||||
];
|
||||
$userTasks = User::whereHas('tasks', function ($query) {
|
||||
$query->whereIn('task_id', $this->report->data_objects);
|
||||
})->get();
|
||||
$userNames = $userTasks->pluck('full_name', 'id');
|
||||
TimeInterval::whereIn('task_id', $this->report->data_objects)
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('task_id', 'user_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_day_users_separately')
|
||||
->groupBy('task_id', 'date_at', 'user_id')
|
||||
->get()
|
||||
->each(function ($timeInterval) use (&$total_spent_time_day_users_separately, $userNames) {
|
||||
$time = $timeInterval->total_spent_time_day_users_separately;
|
||||
$taskId = $timeInterval->task_id;
|
||||
$userId = $timeInterval->user_id;
|
||||
if (!isset($total_spent_time_day_users_separately['datasets'][$taskId])) {
|
||||
$total_spent_time_day_users_separately['datasets'][$taskId] = [];
|
||||
}
|
||||
if (!isset($total_spent_time_day_users_separately['datasets'][$taskId][$userId])) {
|
||||
$color = sprintf('#%02X%02X%02X', rand(0, 255), rand(0, 255), rand(0, 255));
|
||||
$total_spent_time_day_users_separately['datasets'][$taskId][$userId] = [
|
||||
'label' => $userNames[$userId] ?? ' ',
|
||||
'borderColor' => $color,
|
||||
'backgroundColor' => $color,
|
||||
'data' => [$timeInterval->date_at => $time],
|
||||
];
|
||||
}
|
||||
$total_spent_time_day_users_separately['datasets'][$timeInterval->task_id][$timeInterval->user_id]['data'][$timeInterval->date_at] = $time;
|
||||
});
|
||||
foreach ($total_spent_time_day_users_separately['datasets'] as $key => $item) {
|
||||
$this->fillNullDatesAsZeroTime($total_spent_time_day_users_separately['datasets'][$key], 'data');
|
||||
}
|
||||
|
||||
$result['total_spent_time_day_users_separately'] = $total_spent_time_day_users_separately;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
public function fillNullDatesAsZeroTime(array &$datesToFill, $key = null)
|
||||
{
|
||||
foreach ($this->periodDates as $date) {
|
||||
if (is_null($key)) {
|
||||
unset($datesToFill['']);
|
||||
array_key_exists($date, $datesToFill) ? '' : $datesToFill[$date] = 0.0;
|
||||
} else {
|
||||
foreach ($datesToFill as $k => $item) {
|
||||
unset($datesToFill[$k][$key]['']);
|
||||
if (!array_key_exists($date, $item[$key])) {
|
||||
$datesToFill[$k][$key][$date] = 0.0;
|
||||
}
|
||||
ksort($datesToFill[$k][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
263
app/Services/UniversalReportServiceUser.php
Normal file
263
app/Services/UniversalReportServiceUser.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\Task;
|
||||
use App\Models\TimeInterval;
|
||||
use App\Models\UniversalReport;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class UniversalReportServiceUser
|
||||
{
|
||||
private Carbon $startAt;
|
||||
private Carbon $endAt;
|
||||
private UniversalReport $report;
|
||||
private array $periodDates;
|
||||
|
||||
public function __construct(Carbon $startAt, Carbon $endAt, UniversalReport $report, array $periodDates = [])
|
||||
{
|
||||
$this->startAt = $startAt;
|
||||
$this->endAt = $endAt;
|
||||
$this->report = $report;
|
||||
$this->periodDates = $periodDates;
|
||||
}
|
||||
|
||||
public function getUserReportData()
|
||||
{
|
||||
$projectFields = ['id'];
|
||||
foreach ($this->report->fields['projects'] as $field) {
|
||||
$projectFields[] = 'projects.' . $field;
|
||||
}
|
||||
$taskRelations = [];
|
||||
$taskFields = ['tasks.id', 'tasks.project_id'];
|
||||
foreach ($this->report->fields['tasks'] as $field) {
|
||||
if ($field !== 'priority' && $field !== 'status') {
|
||||
$taskFields[] = 'tasks.' . $field;
|
||||
} else {
|
||||
$taskRelations[] = 'tasks.' . $field;
|
||||
$taskFields[] = 'tasks.' . $field . '_id';
|
||||
}
|
||||
}
|
||||
$usersQuery = User::query()->with(['projects' => function ($query) use ($projectFields) {
|
||||
$query->select($projectFields);
|
||||
}])->with(['tasks' => function ($query) use ($taskFields) {
|
||||
$query->select($taskFields);
|
||||
}])->select(array_merge($this->report->fields['base'], ['id']))->whereIn('id', $this->report->data_objects);
|
||||
if (!empty($taskRelations)) $usersQuery = $usersQuery->with($taskRelations);
|
||||
$users = $usersQuery->get();
|
||||
foreach ($users as $user) {
|
||||
foreach ($user->projects as $project) {
|
||||
$project->tasks = $user->tasks->where('project_id', $project->id)->toArray();
|
||||
}
|
||||
}
|
||||
$endAt = clone $this->endAt;
|
||||
$endAt = $endAt->endOfDay();
|
||||
$totalSpentTime = TimeInterval::whereIn('user_id', $users->pluck('id'))
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('user_id')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time')
|
||||
->groupBy('user_id')
|
||||
->pluck('total_spent_time', 'user_id');
|
||||
|
||||
$totalSpentTimeDay = TimeInterval::whereIn('user_id', $users->pluck('id'))
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('user_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw(' SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_by_day')
|
||||
->groupBy('user_id', 'date_at')->get()->toArray();
|
||||
foreach ($users as $user) {
|
||||
$worked_time_day = [];
|
||||
$startDateTime = Carbon::parse($this->startAt);
|
||||
$endDateTime = Carbon::parse($this->endAt);
|
||||
$user->total_spent_time = $totalSpentTime[$user->id] ?? 0;
|
||||
while ($startDateTime <= $endDateTime) {
|
||||
$currentDate = $startDateTime->format('Y-m-d');
|
||||
foreach ($totalSpentTimeDay as $item) {
|
||||
if (($item['date_at'] === $currentDate) && ($user->id === (int)$item['user_id'])) {
|
||||
$worked_time_day[$currentDate] = $item['total_spent_time_by_day'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isset($worked_time_day[$currentDate])) {
|
||||
$worked_time_day[$currentDate] = 0;
|
||||
}
|
||||
|
||||
$startDateTime->modify('+1 day');
|
||||
}
|
||||
|
||||
$user->worked_time_day = $worked_time_day;
|
||||
foreach ($user->projects as $project) {
|
||||
if (!empty($project['tasks'])) {
|
||||
$tasks = $project['tasks'];
|
||||
foreach ($tasks as $key => $task) {
|
||||
if (isset($tasks[$key]['priority'])) {
|
||||
$tasks[$key]['priority'] = $tasks[$key]['priority']['name'];
|
||||
}
|
||||
if (isset($tasks[$key]['status'])) {
|
||||
$tasks[$key]['status'] = $tasks[$key]['status']['name'];
|
||||
}
|
||||
}
|
||||
$project['tasks'] = $tasks;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $users->keyBy('id');
|
||||
}
|
||||
public function getUserReportCharts()
|
||||
{
|
||||
$result = [];
|
||||
if (count($this->report->charts) === 0) {
|
||||
return $result;
|
||||
}
|
||||
$endAt = clone $this->endAt;
|
||||
$endAt = $endAt->endOfDay();
|
||||
if (in_array('total_spent_time_day', $this->report->charts)) {
|
||||
$total_spent_time_by_day = [
|
||||
'datasets' => [],
|
||||
];
|
||||
$users = User::query()->whereIn('id', $this->report->data_objects)->get();
|
||||
$userNames = $users->pluck('full_name', 'id');
|
||||
TimeInterval::whereIn('user_id', $users->pluck('id'))
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->select('user_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw(' SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_by_day')
|
||||
->groupBy('user_id', 'date_at')
|
||||
->get()
|
||||
->each(function ($timeInterval) use (&$total_spent_time_by_day, $userNames) {
|
||||
$time = sprintf("%02d.%02d", floor($timeInterval->total_spent_time_by_day / 3600), floor($timeInterval->total_spent_time_by_day / 60) % 60);
|
||||
if (!array_key_exists($timeInterval->user_id, $total_spent_time_by_day['datasets'])) {
|
||||
$color = sprintf('#%02X%02X%02X', rand(0, 255), rand(0, 255), rand(0, 255));
|
||||
$total_spent_time_by_day['datasets'][$timeInterval->user_id] = [
|
||||
'label' => $userNames[$timeInterval->user_id] ?? ' ',
|
||||
'borderColor' => $color,
|
||||
'backgroundColor' => $color,
|
||||
'data' => [$timeInterval->date_at => $time],
|
||||
];
|
||||
}
|
||||
$total_spent_time_by_day['datasets'][$timeInterval->user_id]['data'][$timeInterval->date_at] = $time;
|
||||
});
|
||||
$this->fillNullDatesAsZeroTime($total_spent_time_by_day['datasets'], 'data');
|
||||
|
||||
$result['total_spent_time_day'] = $total_spent_time_by_day;
|
||||
}
|
||||
if (in_array('total_spent_time_day_and_tasks', $this->report->charts)) {
|
||||
$total_spent_time_by_day_and_tasks = [
|
||||
'datasets' => [],
|
||||
];
|
||||
$userTasks = Task::whereHas('users', function ($query) {
|
||||
$query->whereIn('id', $this->report->data_objects);
|
||||
})->get();
|
||||
$taskNames = $userTasks->pluck('task_name', 'id');
|
||||
TimeInterval::whereIn('user_id', $this->report->data_objects)
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->whereIn('task_id', $userTasks->pluck('id'))
|
||||
->select('task_id', 'user_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_by_day_and_tasks')
|
||||
->groupBy('task_id', 'date_at', 'user_id')
|
||||
->get()
|
||||
->each(function ($timeInterval) use (&$total_spent_time_by_day_and_tasks, $taskNames) {
|
||||
$time = sprintf("%02d.%02d", floor($timeInterval->total_spent_time_by_day_and_tasks / 3600), floor($timeInterval->total_spent_time_by_day_and_tasks / 60) % 60);
|
||||
if (!array_key_exists($timeInterval->user_id, $total_spent_time_by_day_and_tasks['datasets'])) {
|
||||
$total_spent_time_by_day_and_tasks['datasets'][$timeInterval->user_id] = [];
|
||||
}
|
||||
|
||||
if (!array_key_exists($timeInterval->task_id, $total_spent_time_by_day_and_tasks['datasets'][$timeInterval->user_id])) {
|
||||
$color = sprintf('#%02X%02X%02X', rand(0, 255), rand(0, 255), rand(0, 255));
|
||||
$total_spent_time_by_day_and_tasks['datasets'][$timeInterval->user_id][$timeInterval->task_id] = [
|
||||
'label' => $taskNames[$timeInterval->task_id] ?? ' ',
|
||||
'borderColor' => $color,
|
||||
'backgroundColor' => $color,
|
||||
'data' => [$timeInterval->date_at => $time],
|
||||
];
|
||||
} else {
|
||||
$total_spent_time_by_day_and_tasks['datasets'][$timeInterval->user_id][$timeInterval->task_id]['data'][$timeInterval->date_at] = $time;
|
||||
}
|
||||
});
|
||||
|
||||
foreach ($total_spent_time_by_day_and_tasks['datasets'] as $key => $item) {
|
||||
$this->fillNullDatesAsZeroTime($total_spent_time_by_day_and_tasks['datasets'][$key], 'data');
|
||||
}
|
||||
|
||||
$result['total_spent_time_day_and_tasks'] = $total_spent_time_by_day_and_tasks;
|
||||
}
|
||||
if (in_array('total_spent_time_day_and_projects', $this->report->charts)) {
|
||||
$total_spent_time_by_day_and_projects = [
|
||||
'datasets' => [],
|
||||
];
|
||||
$userProjects = Project::whereHas('users', function ($query) {
|
||||
$query->whereIn('id', $this->report->data_objects);
|
||||
})->get();
|
||||
$projectNames = $userProjects->pluck('name', 'id');
|
||||
$userTasks = Task::whereHas('users', function ($query) {
|
||||
$query->whereIn('id', $this->report->data_objects);
|
||||
})->pluck('project_id', 'id');
|
||||
|
||||
$timeIntervals = TimeInterval::whereIn('user_id', $this->report->data_objects)
|
||||
->where('start_at', '>=', $this->startAt->format('Y-m-d H:i:s'))
|
||||
->where('end_at', '<=', $endAt->format('Y-m-d H:i:s'))
|
||||
->whereIn('task_id', $userTasks->keys())
|
||||
->select('task_id', 'user_id')
|
||||
->selectRaw('DATE(start_at) as date_at')
|
||||
->selectRaw('SUM(TIMESTAMPDIFF(SECOND, start_at, end_at)) as total_spent_time_by_day_and_projects')
|
||||
->groupBy('task_id', 'date_at', 'user_id')
|
||||
->get();
|
||||
$time = [];
|
||||
foreach ($timeIntervals as $timeInterval) {
|
||||
$projectId = $userTasks[$timeInterval->task_id];
|
||||
if (!isset($time[$timeInterval->date_at . '_' . $projectId . '_' . $timeInterval->user_id])) {
|
||||
$time[$timeInterval->date_at . '_' . $projectId . '_' . $timeInterval->user_id] = 0;
|
||||
}
|
||||
$time[$timeInterval->date_at . '_' . $projectId . '_' . $timeInterval->user_id] += $timeInterval->total_spent_time_by_day_and_projects;
|
||||
}
|
||||
$timeIntervals->each(function ($timeInterval) use (&$total_spent_time_by_day_and_projects, $userTasks, $projectNames, $time) {
|
||||
if (!array_key_exists($timeInterval->user_id, $total_spent_time_by_day_and_projects['datasets'])) {
|
||||
$total_spent_time_by_day_and_projects['datasets'][$timeInterval->user_id] = [];
|
||||
}
|
||||
$projectId = $userTasks[$timeInterval->task_id];
|
||||
if (!array_key_exists($projectId, $total_spent_time_by_day_and_projects['datasets'][$timeInterval->user_id])) {
|
||||
$color = sprintf('#%02X%02X%02X', rand(0, 255), rand(0, 255), rand(0, 255));
|
||||
$total_spent_time_by_day_and_projects['datasets'][$timeInterval->user_id][$projectId] = [
|
||||
'label' => $projectNames[$projectId] ?? '',
|
||||
'borderColor' => $color,
|
||||
'backgroundColor' => $color,
|
||||
'data' => [$timeInterval->date_at => $time[$timeInterval->date_at . '_' . $projectId . '_' . $timeInterval->user_id] / 3600],
|
||||
];
|
||||
}
|
||||
$total_spent_time_by_day_and_projects['datasets'][$timeInterval->user_id][$projectId]['data'][$timeInterval->date_at] = $time[$timeInterval->date_at . '_' . $projectId . '_' . $timeInterval->user_id] / 3600;
|
||||
});
|
||||
|
||||
foreach ($total_spent_time_by_day_and_projects['datasets'] as $key => $item) {
|
||||
$this->fillNullDatesAsZeroTime($total_spent_time_by_day_and_projects['datasets'][$key], 'data');
|
||||
}
|
||||
$result['total_spent_time_day_and_projects'] = $total_spent_time_by_day_and_projects;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function fillNullDatesAsZeroTime(array &$datesToFill, $key = null)
|
||||
{
|
||||
foreach ($this->periodDates as $date) {
|
||||
if (is_null($key)) {
|
||||
unset($datesToFill['']);
|
||||
array_key_exists($date, $datesToFill) ? '' : $datesToFill[$date] = 0.0;
|
||||
} else {
|
||||
foreach ($datesToFill as $k => $item) {
|
||||
unset($datesToFill[$k][$key]['']);
|
||||
|
||||
if (!array_key_exists($date, $item[$key])) {
|
||||
$datesToFill[$k][$key][$date] = 0.0;
|
||||
}
|
||||
ksort($datesToFill[$k][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user