mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-16 20:22:15 +01:00
Added billable rates; Added project members; Added visibility to projects
This commit is contained in:
59
app/Service/BillableRateService.php
Normal file
59
app/Service/BillableRateService.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
43
app/Service/PermissionStore.php
Normal file
43
app/Service/PermissionStore.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user