Added billable rates; Added project members; Added visibility to projects

This commit is contained in:
Constantin Graf
2024-03-28 18:50:04 +01:00
parent bb42b0940a
commit ba0212ea01
71 changed files with 3236 additions and 174 deletions

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\Models\Membership;
use App\Models\Organization;
use App\Models\Project;
use App\Models\ProjectMember;
use App\Models\TimeEntry;
class BillableRateService
{
public function getBillableRateForTimeEntry(TimeEntry $timeEntry): ?int
{
if (! $timeEntry->billable) {
return null;
}
if ($timeEntry->project_id !== null) {
// Project member rate
/** @var ProjectMember|null $projectMember */
$projectMember = ProjectMember::query()
->where('user_id', '=', $timeEntry->user_id)
->where('project_id', '=', $timeEntry->project_id)
->first();
if ($projectMember !== null && $projectMember->billable_rate !== null) {
return $projectMember->billable_rate;
}
// Project rate
/** @var Project|null $project */
$project = Project::find($timeEntry->project_id);
if ($project !== null && $project->billable_rate !== null) {
return $project->billable_rate;
}
}
// Member rate
/** @var Membership|null $membership */
$membership = Membership::query()
->where('user_id', '=', $timeEntry->user_id)
->where('organization_id', '=', $timeEntry->organization_id)
->first();
if ($membership !== null && $membership->billable_rate !== null) {
return $membership->billable_rate;
}
// Organization rate
/** @var Organization|null $organization */
$organization = Organization::query()
->where('id', '=', $timeEntry->organization_id)
->first();
if ($organization !== null && $organization->billable_rate !== null) {
return $organization->billable_rate;
}
return null;
}
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Service;
use App\Enums\Weekday;
use App\Models\Organization;
use App\Models\TimeEntry;
use App\Models\User;
use Carbon\Carbon;
@@ -86,7 +87,7 @@ class DashboardService
*
* @return array<int, array{date: string, duration: int}>
*/
public function getDailyTrackedHours(User $user, int $days): array
public function getDailyTrackedHours(User $user, Organization $organization, int $days): array
{
$timezone = $this->timezoneService->getTimezoneFromUser($user);
$timezoneShift = $this->timezoneService->getShiftFromUtc($timezone);
@@ -103,7 +104,8 @@ class DashboardService
$query = TimeEntry::query()
->select(DB::raw('DATE('.$dateWithTimeZone.') as date, round(sum(extract(epoch from (coalesce("end", now()) - start)))) as aggregate'))
->where('user_id', '=', $user->id)
->where('user_id', '=', $user->getKey())
->where('organization_id', '=', $organization->getKey())
->groupBy(DB::raw('DATE('.$dateWithTimeZone.')'))
->orderBy('date');
@@ -128,7 +130,7 @@ class DashboardService
*
* @return array<int, array{date: string, duration: int}>
*/
public function getWeeklyHistory(User $user): array
public function getWeeklyHistory(User $user, Organization $organization): array
{
$timezone = $this->timezoneService->getTimezoneFromUser($user);
$timezoneShift = $this->timezoneService->getShiftFromUtc($timezone);
@@ -143,7 +145,8 @@ class DashboardService
$query = TimeEntry::query()
->select(DB::raw('DATE('.$dateWithTimeZone.') as date, round(sum(extract(epoch from (coalesce("end", now()) - start)))) as aggregate'))
->where('user_id', '=', $user->id)
->where('user_id', '=', $user->getKey())
->where('organization_id', '=', $organization->getKey())
->groupBy(DB::raw('DATE('.$dateWithTimeZone.')'))
->orderBy('date');
@@ -162,4 +165,92 @@ class DashboardService
return $result;
}
public function totalWeeklyTime(User $user, Organization $organization): int
{
$timezone = $this->timezoneService->getTimezoneFromUser($user);
$possibleDays = $this->daysOfThisWeek($timezone, $user->week_start);
$query = TimeEntry::query()
->select(DB::raw('round(sum(extract(epoch from (coalesce("end", now()) - start)))) as aggregate'))
->where('user_id', '=', $user->getKey())
->where('organization_id', '=', $organization->getKey());
$query = $this->constrainDateByPossibleDates($query, $possibleDays, $timezone);
/** @var Collection<int, object{aggregate: int}> $resultDb */
$resultDb = $query->get();
return (int) $resultDb->get(0)->aggregate;
}
public function totalWeeklyBillableTime(User $user, Organization $organization): int
{
$timezone = $this->timezoneService->getTimezoneFromUser($user);
$possibleDays = $this->daysOfThisWeek($timezone, $user->week_start);
$query = TimeEntry::query()
->select(DB::raw('round(sum(extract(epoch from (coalesce("end", now()) - start)))) as aggregate'))
->where('billable', '=', true)
->where('user_id', '=', $user->getKey())
->where('organization_id', '=', $organization->getKey());
$query = $this->constrainDateByPossibleDates($query, $possibleDays, $timezone);
/** @var Collection<int, object{aggregate: int}> $resultDb */
$resultDb = $query->get();
return (int) $resultDb->get(0)->aggregate;
}
/**
* @return array{value: int, currency: string}
*/
public function totalWeeklyBillableAmount(User $user, Organization $organization): array
{
$timezone = $this->timezoneService->getTimezoneFromUser($user);
$possibleDays = $this->daysOfThisWeek($timezone, $user->week_start);
$query = TimeEntry::query()
->select(DB::raw('
round(
sum(
extract(epoch from (coalesce("end", now()) - start)) * (billable_rate::float/60/60)
)
) as aggregate'))
->where('billable', '=', true)
->whereNotNull('billable_rate')
->where('user_id', '=', $user->id);
$query = $this->constrainDateByPossibleDates($query, $possibleDays, $timezone);
/** @var Collection<int, object{aggregate: int}> $resultDb */
$resultDb = $query->get();
return [
'value' => (int) $resultDb->get(0)->aggregate,
'currency' => $organization->currency,
];
}
/**
* @return array<int, array{value: int, name: string, color: string}>
*/
public function weeklyProjectOverview(User $user, Organization $organization): array
{
return [
[
'value' => 120,
'name' => 'Project 11',
'color' => '#26a69a',
],
[
'value' => 200,
'name' => 'Project 2',
'color' => '#d4e157',
],
[
'value' => 150,
'name' => 'Project 3',
'color' => '#ff7043',
],
];
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\Models\Organization;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class PermissionStore
{
/**
* @var array<string, array<string>>
*/
private array $permissionCache = [];
public function has(Organization $organization, string $permission): bool
{
/** @var User|null $user */
$user = Auth::user();
if ($user === null) {
return false;
}
if (! isset($this->permissionCache[$user->getKey().'|'.$organization->getKey()])) {
if ($user->ownsTeam($organization)) {
return true;
}
if (! $user->belongsToTeam($organization)) {
return false;
}
$permissions = $user->teamPermissions($organization);
$this->permissionCache[$user->getKey().'|'.$organization->getKey()] = $permissions;
} else {
$permissions = $this->permissionCache[$user->getKey().'|'.$organization->getKey()];
}
return in_array($permission, $permissions, true);
}
}

View File

@@ -5,9 +5,8 @@ declare(strict_types=1);
namespace App\Service;
use App\Models\User;
use Carbon\Carbon;
use Carbon\CarbonTimeZone;
use DateTime;
use DateTimeZone;
use Illuminate\Support\Facades\Log;
class TimezoneService
@@ -17,9 +16,7 @@ class TimezoneService
*/
public function getTimezones(): array
{
$tzlist = CarbonTimeZone::listIdentifiers(DateTimeZone::ALL);
return $tzlist;
return CarbonTimeZone::listIdentifiers();
}
public function getTimezoneFromUser(User $user): CarbonTimeZone
@@ -57,8 +54,6 @@ class TimezoneService
public function getShiftFromUtc(CarbonTimeZone $timeZone): int
{
$timezoneShift = $timeZone->getOffset(new DateTime('now', new DateTimeZone('UTC')));
return $timezoneShift;
return $timeZone->getOffset(Carbon::now());
}
}