mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-15 19:52:15 +01:00
Added billable rates; Added project members; Added visibility to projects
This commit is contained in:
244
tests/Unit/Service/BillableRateServiceTest.php
Normal file
244
tests/Unit/Service/BillableRateServiceTest.php
Normal file
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectMember;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use App\Service\BillableRateService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class BillableRateServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private BillableRateService $billableRateService;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->billableRateService = app(BillableRateService::class);
|
||||
}
|
||||
|
||||
public function test_billable_rate_is_null_if_time_entry_is_not_billable(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create([
|
||||
'billable_rate' => 1001,
|
||||
]);
|
||||
$user = User::factory()->attachToOrganization($organization, [
|
||||
'billable_rate' => 2002,
|
||||
])->create();
|
||||
$project = Project::factory()->forOrganization($organization)->create([
|
||||
'billable_rate' => 3003,
|
||||
]);
|
||||
$projectMember = ProjectMember::factory()->forUser($user)->forProject($project)->create([
|
||||
'billable_rate' => 4004,
|
||||
]);
|
||||
$timeEntry = TimeEntry::factory()->forProject($project)->forUser($user)->forOrganization($organization)->create([
|
||||
'billable' => false,
|
||||
]);
|
||||
|
||||
// Act
|
||||
$billableRate = $this->billableRateService->getBillableRateForTimeEntry($timeEntry);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(null, $billableRate);
|
||||
}
|
||||
|
||||
public function test_billable_rate_uses_project_member_rate_as_first_priority(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create([
|
||||
'billable_rate' => 1001,
|
||||
]);
|
||||
$user = User::factory()->attachToOrganization($organization, [
|
||||
'billable_rate' => 2002,
|
||||
])->create();
|
||||
$project = Project::factory()->forOrganization($organization)->create([
|
||||
'billable_rate' => 3003,
|
||||
]);
|
||||
$projectMember = ProjectMember::factory()->forUser($user)->forProject($project)->create([
|
||||
'billable_rate' => 4004,
|
||||
]);
|
||||
$timeEntry = TimeEntry::factory()->forProject($project)->forUser($user)->forOrganization($organization)->create([
|
||||
'billable' => true,
|
||||
]);
|
||||
|
||||
// Act
|
||||
$billableRate = $this->billableRateService->getBillableRateForTimeEntry($timeEntry);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(4004, $billableRate);
|
||||
}
|
||||
|
||||
public function test_billable_rate_uses_project_rate_as_second_priority_using_null_values_before(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create([
|
||||
'billable_rate' => 1001,
|
||||
]);
|
||||
$user = User::factory()->attachToOrganization($organization, [
|
||||
'billable_rate' => 2002,
|
||||
])->create();
|
||||
$project = Project::factory()->forOrganization($organization)->create([
|
||||
'billable_rate' => 3003,
|
||||
]);
|
||||
$projectMember = ProjectMember::factory()->forUser($user)->forProject($project)->create([
|
||||
'billable_rate' => null,
|
||||
]);
|
||||
$timeEntry = TimeEntry::factory()->forProject($project)->forUser($user)->forOrganization($organization)->create([
|
||||
'billable' => true,
|
||||
]);
|
||||
|
||||
// Act
|
||||
$billableRate = $this->billableRateService->getBillableRateForTimeEntry($timeEntry);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(3003, $billableRate);
|
||||
}
|
||||
|
||||
public function test_billable_rate_uses_project_rate_as_second_priority_using_non_existing_entities_before(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create([
|
||||
'billable_rate' => 1001,
|
||||
]);
|
||||
$user = User::factory()->attachToOrganization($organization, [
|
||||
'billable_rate' => 2002,
|
||||
])->create();
|
||||
$project = Project::factory()->forOrganization($organization)->create([
|
||||
'billable_rate' => 3003,
|
||||
]);
|
||||
$timeEntry = TimeEntry::factory()->forProject($project)->forUser($user)->forOrganization($organization)->create([
|
||||
'billable' => true,
|
||||
]);
|
||||
|
||||
// Act
|
||||
$billableRate = $this->billableRateService->getBillableRateForTimeEntry($timeEntry);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(3003, $billableRate);
|
||||
}
|
||||
|
||||
public function test_billable_rate_uses_organization_member_rate_as_third_priority_using_null_values_before(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create([
|
||||
'billable_rate' => 1001,
|
||||
]);
|
||||
$user = User::factory()->attachToOrganization($organization, [
|
||||
'billable_rate' => 2002,
|
||||
])->create();
|
||||
$project = Project::factory()->forOrganization($organization)->create([
|
||||
'billable_rate' => null,
|
||||
]);
|
||||
$projectMember = ProjectMember::factory()->forUser($user)->forProject($project)->create([
|
||||
'billable_rate' => null,
|
||||
]);
|
||||
$timeEntry = TimeEntry::factory()->forProject($project)->forUser($user)->forOrganization($organization)->create([
|
||||
'billable' => true,
|
||||
]);
|
||||
|
||||
// Act
|
||||
$billableRate = $this->billableRateService->getBillableRateForTimeEntry($timeEntry);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(2002, $billableRate);
|
||||
}
|
||||
|
||||
public function test_billable_rate_uses_organization_member_rate_as_third_priority_using_non_existing_entities_before(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create([
|
||||
'billable_rate' => 1001,
|
||||
]);
|
||||
$user = User::factory()->attachToOrganization($organization, [
|
||||
'billable_rate' => 2002,
|
||||
])->create();
|
||||
$timeEntry = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
'billable' => true,
|
||||
]);
|
||||
|
||||
// Act
|
||||
$billableRate = $this->billableRateService->getBillableRateForTimeEntry($timeEntry);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(2002, $billableRate);
|
||||
}
|
||||
|
||||
public function test_billable_rate_uses_organization_rate_as_fourth_priority_using_null_values_before(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create([
|
||||
'billable_rate' => 1001,
|
||||
]);
|
||||
$user = User::factory()->attachToOrganization($organization, [
|
||||
'billable_rate' => null,
|
||||
])->create();
|
||||
$project = Project::factory()->forOrganization($organization)->create([
|
||||
'billable_rate' => null,
|
||||
]);
|
||||
$projectMember = ProjectMember::factory()->forUser($user)->forProject($project)->create([
|
||||
'billable_rate' => null,
|
||||
]);
|
||||
$timeEntry = TimeEntry::factory()->forProject($project)->forUser($user)->forOrganization($organization)->create([
|
||||
'billable' => true,
|
||||
]);
|
||||
|
||||
// Act
|
||||
$billableRate = $this->billableRateService->getBillableRateForTimeEntry($timeEntry);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(1001, $billableRate);
|
||||
}
|
||||
|
||||
public function test_billable_rate_uses_organization_rate_as_fourth_priority_using_non_existing_entities_before(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create([
|
||||
'billable_rate' => 1001,
|
||||
]);
|
||||
$user = User::factory()->create();
|
||||
$timeEntry = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
'billable' => true,
|
||||
]);
|
||||
|
||||
// Act
|
||||
$billableRate = $this->billableRateService->getBillableRateForTimeEntry($timeEntry);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(1001, $billableRate);
|
||||
}
|
||||
|
||||
public function test_billable_rate_is_null_if_billable_rate_on_all_levels_are_null(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create([
|
||||
'billable_rate' => null,
|
||||
]);
|
||||
$user = User::factory()->attachToOrganization($organization, [
|
||||
'billable_rate' => null,
|
||||
])->create();
|
||||
$project = Project::factory()->forOrganization($organization)->create([
|
||||
'billable_rate' => null,
|
||||
]);
|
||||
$projectMember = ProjectMember::factory()->forUser($user)->forProject($project)->create([
|
||||
'billable_rate' => null,
|
||||
]);
|
||||
$timeEntry = TimeEntry::factory()->forProject($project)->forUser($user)->forOrganization($organization)->create([
|
||||
'billable' => true,
|
||||
]);
|
||||
|
||||
// Act
|
||||
$billableRate = $this->billableRateService->getBillableRateForTimeEntry($timeEntry);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(null, $billableRate);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Tests\Unit\Service;
|
||||
|
||||
use App\Enums\Weekday;
|
||||
use App\Models\Organization;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use App\Service\DashboardService;
|
||||
@@ -28,22 +29,24 @@ class DashboardServiceTest extends TestCase
|
||||
{
|
||||
// Arrange
|
||||
$this->travelTo(Carbon::create(2024, 1, 1, 12, 0, 0, 'Europe/Vienna'));
|
||||
$organization = Organization::factory()->create();
|
||||
$user = User::factory()->create([
|
||||
'timezone' => 'Europe/Vienna',
|
||||
]);
|
||||
$timeEntry1 = TimeEntry::factory()->forUser($user)->create([
|
||||
$user->organizations()->attach($organization);
|
||||
$timeEntry1 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time shifts in timezone Europe/Vienna to the next day
|
||||
'start' => Carbon::create(2023, 12, 30, 23, 0, 0, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 40, 'UTC'),
|
||||
]);
|
||||
$timeEntry2 = TimeEntry::factory()->forUser($user)->create([
|
||||
$timeEntry2 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time NOT shifts in timezone Europe/Vienna to the next day
|
||||
'start' => Carbon::create(2023, 12, 30, 22, 59, 59, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 39, 'UTC'),
|
||||
]);
|
||||
|
||||
// Act
|
||||
$result = $this->dashboardService->getDailyTrackedHours($user, 5);
|
||||
$result = $this->dashboardService->getDailyTrackedHours($user, $organization, 5);
|
||||
|
||||
// Assert
|
||||
$this->assertSame([
|
||||
@@ -75,25 +78,27 @@ class DashboardServiceTest extends TestCase
|
||||
// Arrange
|
||||
// Note: Is a Monday
|
||||
$this->travelTo(Carbon::create(2024, 1, 1, 12, 0, 0, 'Europe/Vienna'));
|
||||
$organization = Organization::factory()->create();
|
||||
$user = User::factory()->create([
|
||||
'timezone' => 'Europe/Vienna',
|
||||
'week_start' => Weekday::Sunday,
|
||||
]);
|
||||
$user->organizations()->attach($organization);
|
||||
// Note: This is a Sunday
|
||||
$timeEntry1 = TimeEntry::factory()->forUser($user)->create([
|
||||
$timeEntry1 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time shifts in timezone Europe/Vienna to the next day
|
||||
'start' => Carbon::create(2023, 12, 30, 23, 0, 0, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 40, 'UTC'),
|
||||
]);
|
||||
// Note: This is a Saturday
|
||||
$timeEntry2 = TimeEntry::factory()->forUser($user)->create([
|
||||
$timeEntry2 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time NOT shifts in timezone Europe/Vienna to the next day
|
||||
'start' => Carbon::create(2023, 12, 30, 22, 59, 59, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 39, 'UTC'),
|
||||
]);
|
||||
|
||||
// Act
|
||||
$result = $this->dashboardService->getWeeklyHistory($user);
|
||||
$result = $this->dashboardService->getWeeklyHistory($user, $organization);
|
||||
|
||||
// Assert
|
||||
$this->assertSame([
|
||||
@@ -127,4 +132,123 @@ class DashboardServiceTest extends TestCase
|
||||
],
|
||||
], $result);
|
||||
}
|
||||
|
||||
public function test_total_weekly_time_returns_correct_value(): void
|
||||
{
|
||||
// Arrange
|
||||
// Note: Is a Monday
|
||||
$this->travelTo(Carbon::create(2024, 1, 1, 12, 0, 0, 'Europe/Vienna'));
|
||||
$organization = Organization::factory()->create();
|
||||
$user = User::factory()->create([
|
||||
'timezone' => 'Europe/Vienna',
|
||||
'week_start' => Weekday::Sunday,
|
||||
]);
|
||||
$user->organizations()->attach($organization);
|
||||
// Note: This is a Sunday
|
||||
$timeEntry1 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time shifts in timezone Europe/Vienna to the next day
|
||||
'start' => Carbon::create(2023, 12, 30, 23, 0, 0, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 40, 'UTC'),
|
||||
]);
|
||||
// Note: This is a Saturday
|
||||
$timeEntry2 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time NOT shifts in timezone Europe/Vienna to the next day
|
||||
'start' => Carbon::create(2023, 12, 30, 22, 59, 59, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 39, 'UTC'),
|
||||
]);
|
||||
|
||||
// Act
|
||||
$result = $this->dashboardService->totalWeeklyTime($user, $organization);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(40, $result);
|
||||
}
|
||||
|
||||
public function test_total_weekly_billable_time_returns_correct_value(): void
|
||||
{
|
||||
// Arrange
|
||||
// Note: Is a Monday
|
||||
$this->travelTo(Carbon::create(2024, 1, 1, 12, 0, 0, 'Europe/Vienna'));
|
||||
$organization = Organization::factory()->create();
|
||||
$user = User::factory()->create([
|
||||
'timezone' => 'Europe/Vienna',
|
||||
'week_start' => Weekday::Sunday,
|
||||
]);
|
||||
$user->organizations()->attach($organization);
|
||||
// Note: This is a Sunday
|
||||
$timeEntry1 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time shifts in timezone Europe/Vienna to the next day
|
||||
'billable' => true,
|
||||
'start' => Carbon::create(2023, 12, 30, 23, 0, 0, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 40, 'UTC'),
|
||||
]);
|
||||
// Note: This is a Sunday (non-billable)
|
||||
$timeEntry1 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time shifts in timezone Europe/Vienna to the next day
|
||||
'billable' => false,
|
||||
'start' => Carbon::create(2023, 12, 30, 23, 0, 40, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 59, 'UTC'),
|
||||
]);
|
||||
// Note: This is a Saturday
|
||||
$timeEntry2 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time NOT shifts in timezone Europe/Vienna to the next day
|
||||
'billable' => true,
|
||||
'start' => Carbon::create(2023, 12, 30, 22, 59, 59, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 39, 'UTC'),
|
||||
]);
|
||||
|
||||
// Act
|
||||
$result = $this->dashboardService->totalWeeklyBillableTime($user, $organization);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(40, $result);
|
||||
}
|
||||
|
||||
public function test_total_weekly_billable_amount_returns_correct_value(): void
|
||||
{
|
||||
// Arrange
|
||||
// Note: Is a Monday
|
||||
$this->travelTo(Carbon::create(2024, 1, 1, 12, 0, 0, 'Europe/Vienna'));
|
||||
$currency = 'USD';
|
||||
$organization = Organization::factory()->create([
|
||||
'currency' => $currency,
|
||||
]);
|
||||
$user = User::factory()->create([
|
||||
'timezone' => 'Europe/Vienna',
|
||||
'week_start' => Weekday::Sunday,
|
||||
]);
|
||||
$user->organizations()->attach($organization);
|
||||
// Note: This is a Sunday
|
||||
$timeEntry1 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time shifts in timezone Europe/Vienna to the next day
|
||||
'billable' => true,
|
||||
'billable_rate' => 50 * 100,
|
||||
'start' => Carbon::create(2023, 12, 30, 23, 0, 0, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 31, 0, 0, 0, 'UTC'),
|
||||
]);
|
||||
// Note: This is a Sunday (non-billable)
|
||||
$timeEntry2 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time shifts in timezone Europe/Vienna to the next day
|
||||
'billable' => false,
|
||||
'start' => Carbon::create(2023, 12, 30, 23, 0, 40, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 59, 'UTC'),
|
||||
]);
|
||||
// Note: This is a Saturday
|
||||
$timeEntry3 = TimeEntry::factory()->forUser($user)->forOrganization($organization)->create([
|
||||
// Note: The start time NOT shifts in timezone Europe/Vienna to the next day
|
||||
'billable' => true,
|
||||
'billable_rate' => 100 * 100,
|
||||
'start' => Carbon::create(2023, 12, 30, 22, 59, 59, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 39, 'UTC'),
|
||||
]);
|
||||
|
||||
// Act
|
||||
$result = $this->dashboardService->totalWeeklyBillableAmount($user, $organization);
|
||||
|
||||
// Assert
|
||||
$this->assertSame([
|
||||
'value' => 5000,
|
||||
'currency' => $currency,
|
||||
], $result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user