mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-17 04:32:15 +01:00
Add organization setting employees_can_see_billable_rates
This commit is contained in:
committed by
Constantin Graf
parent
9d279d4980
commit
51cd919db6
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api\V1;
|
namespace App\Http\Controllers\Api\V1;
|
||||||
|
|
||||||
|
use App\Enums\Role;
|
||||||
use App\Http\Requests\V1\Organization\OrganizationUpdateRequest;
|
use App\Http\Requests\V1\Organization\OrganizationUpdateRequest;
|
||||||
use App\Http\Resources\V1\Organization\OrganizationResource;
|
use App\Http\Resources\V1\Organization\OrganizationResource;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
@@ -23,7 +24,9 @@ class OrganizationController extends Controller
|
|||||||
{
|
{
|
||||||
$this->checkPermission($organization, 'organizations:view');
|
$this->checkPermission($organization, 'organizations:view');
|
||||||
|
|
||||||
return new OrganizationResource($organization);
|
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
|
||||||
|
|
||||||
|
return new OrganizationResource($organization, $showBillableRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,6 +42,9 @@ class OrganizationController extends Controller
|
|||||||
|
|
||||||
$organization->name = $request->input('name');
|
$organization->name = $request->input('name');
|
||||||
$oldBillableRate = $organization->billable_rate;
|
$oldBillableRate = $organization->billable_rate;
|
||||||
|
if ($request->has('employees_can_see_billable_rates')) {
|
||||||
|
$organization->employees_can_see_billable_rates = $request->validated('employees_can_see_billable_rates');
|
||||||
|
}
|
||||||
$organization->billable_rate = $request->getBillableRate();
|
$organization->billable_rate = $request->getBillableRate();
|
||||||
$organization->save();
|
$organization->save();
|
||||||
|
|
||||||
@@ -46,6 +52,6 @@ class OrganizationController extends Controller
|
|||||||
$billableRateService->updateTimeEntriesBillableRateForOrganization($organization);
|
$billableRateService->updateTimeEntriesBillableRateForOrganization($organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OrganizationResource($organization);
|
return new OrganizationResource($organization, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api\V1;
|
namespace App\Http\Controllers\Api\V1;
|
||||||
|
|
||||||
|
use App\Enums\Role;
|
||||||
use App\Exceptions\Api\EntityStillInUseApiException;
|
use App\Exceptions\Api\EntityStillInUseApiException;
|
||||||
use App\Http\Requests\V1\Project\ProjectIndexRequest;
|
use App\Http\Requests\V1\Project\ProjectIndexRequest;
|
||||||
use App\Http\Requests\V1\Project\ProjectStoreRequest;
|
use App\Http\Requests\V1\Project\ProjectStoreRequest;
|
||||||
@@ -60,7 +61,9 @@ class ProjectController extends Controller
|
|||||||
|
|
||||||
$projects = $projectsQuery->paginate(config('app.pagination_per_page_default'));
|
$projects = $projectsQuery->paginate(config('app.pagination_per_page_default'));
|
||||||
|
|
||||||
return new ProjectCollection($projects);
|
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
|
||||||
|
|
||||||
|
return new ProjectCollection($projects, $showBillableRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,9 +77,12 @@ class ProjectController extends Controller
|
|||||||
{
|
{
|
||||||
$this->checkPermission($organization, 'projects:view', $project);
|
$this->checkPermission($organization, 'projects:view', $project);
|
||||||
|
|
||||||
|
// Note: There is currently no need to check if a user is a member of the project,
|
||||||
|
// since this is only relevant for users with the role "employee" and they can not access this endpoint.
|
||||||
|
|
||||||
$project->load('organization');
|
$project->load('organization');
|
||||||
|
|
||||||
return new ProjectResource($project);
|
return new ProjectResource($project, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,7 +107,7 @@ class ProjectController extends Controller
|
|||||||
$project->organization()->associate($organization);
|
$project->organization()->associate($organization);
|
||||||
$project->save();
|
$project->save();
|
||||||
|
|
||||||
return new ProjectResource($project);
|
return new ProjectResource($project, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,7 +138,7 @@ class ProjectController extends Controller
|
|||||||
$billableRateService->updateTimeEntriesBillableRateForProject($project);
|
$billableRateService->updateTimeEntriesBillableRateForProject($project);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ProjectResource($project);
|
return new ProjectResource($project, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ class OrganizationUpdateRequest extends FormRequest
|
|||||||
'integer',
|
'integer',
|
||||||
'min:0',
|
'min:0',
|
||||||
],
|
],
|
||||||
|
'employees_can_see_billable_rates' => [
|
||||||
|
'boolean',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,20 @@ use Illuminate\Http\Request;
|
|||||||
*/
|
*/
|
||||||
class OrganizationResource extends BaseResource
|
class OrganizationResource extends BaseResource
|
||||||
{
|
{
|
||||||
|
private bool $showBillableRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new resource instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Organization $resource, bool $showBillableRate)
|
||||||
|
{
|
||||||
|
parent::__construct($resource);
|
||||||
|
|
||||||
|
$this->showBillableRate = $showBillableRate;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
*
|
*
|
||||||
@@ -28,7 +42,9 @@ class OrganizationResource extends BaseResource
|
|||||||
/** @var bool $color Personal organizations automatically created after registration */
|
/** @var bool $color Personal organizations automatically created after registration */
|
||||||
'is_personal' => $this->resource->personal_team,
|
'is_personal' => $this->resource->personal_team,
|
||||||
/** @var int|null $billable_rate Billable rate in cents per hour */
|
/** @var int|null $billable_rate Billable rate in cents per hour */
|
||||||
'billable_rate' => $this->resource->billable_rate,
|
'billable_rate' => $this->showBillableRate ? $this->resource->billable_rate : null,
|
||||||
|
/** @var bool $employees_can_see_billable_rates Can members of the organization with role "employee" see the billable rates */
|
||||||
|
'employees_can_see_billable_rates' => $this->resource->employees_can_see_billable_rates,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,39 @@ declare(strict_types=1);
|
|||||||
namespace App\Http\Resources\V1\Project;
|
namespace App\Http\Resources\V1\Project;
|
||||||
|
|
||||||
use App\Http\Resources\PaginatedResourceCollection;
|
use App\Http\Resources\PaginatedResourceCollection;
|
||||||
|
use App\Models\Project;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
|
|
||||||
class ProjectCollection extends ResourceCollection implements PaginatedResourceCollection
|
class ProjectCollection extends ResourceCollection implements PaginatedResourceCollection
|
||||||
{
|
{
|
||||||
|
private bool $showBillableRates;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The resource that this resource collects.
|
* @param LengthAwarePaginator<Project> $resource
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
*/
|
||||||
public $collects = ProjectResource::class;
|
public function __construct($resource, bool $showBillableRates)
|
||||||
|
{
|
||||||
|
parent::__construct($resource);
|
||||||
|
$this->showBillableRates = $showBillableRates;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function collects(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the resource collection into an array.
|
||||||
|
*
|
||||||
|
* @return array<array<string, string|bool|int|null>>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return $this->collection->map(function (Project $project) use ($request): array {
|
||||||
|
return (new ProjectResource($project, $this->showBillableRates))
|
||||||
|
->toArray($request);
|
||||||
|
})->all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ use Illuminate\Http\Request;
|
|||||||
*/
|
*/
|
||||||
class ProjectResource extends BaseResource
|
class ProjectResource extends BaseResource
|
||||||
{
|
{
|
||||||
|
private bool $showBillableRate;
|
||||||
|
|
||||||
|
public function __construct(Project $resource, bool $showBillableRate)
|
||||||
|
{
|
||||||
|
parent::__construct($resource);
|
||||||
|
|
||||||
|
$this->showBillableRate = $showBillableRate;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform the resource into an array.
|
* Transform the resource into an array.
|
||||||
*
|
*
|
||||||
@@ -32,7 +41,7 @@ class ProjectResource extends BaseResource
|
|||||||
/** @var bool $is_archived Whether the client is archived */
|
/** @var bool $is_archived Whether the client is archived */
|
||||||
'is_archived' => $this->resource->is_archived,
|
'is_archived' => $this->resource->is_archived,
|
||||||
/** @var int|null $billable_rate Billable rate in cents per hour */
|
/** @var int|null $billable_rate Billable rate in cents per hour */
|
||||||
'billable_rate' => $this->resource->billable_rate,
|
'billable_rate' => $this->showBillableRate ? $this->resource->billable_rate : null,
|
||||||
/** @var bool $is_billable Project time entries billable default */
|
/** @var bool $is_billable Project time entries billable default */
|
||||||
'is_billable' => $this->resource->is_billable,
|
'is_billable' => $this->resource->is_billable,
|
||||||
/** @var int|null $estimated_time Estimated time in seconds */
|
/** @var int|null $estimated_time Estimated time in seconds */
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
|
|||||||
* @property string $currency
|
* @property string $currency
|
||||||
* @property int|null $billable_rate
|
* @property int|null $billable_rate
|
||||||
* @property string $user_id
|
* @property string $user_id
|
||||||
|
* @property bool $employees_can_see_billable_rates
|
||||||
* @property User $owner
|
* @property User $owner
|
||||||
* @property Carbon|null $created_at
|
* @property Carbon|null $created_at
|
||||||
* @property Carbon|null $updated_at
|
* @property Carbon|null $updated_at
|
||||||
@@ -58,6 +59,7 @@ class Organization extends JetstreamTeam implements AuditableContract
|
|||||||
'name' => 'string',
|
'name' => 'string',
|
||||||
'personal_team' => 'boolean',
|
'personal_team' => 'boolean',
|
||||||
'currency' => 'string',
|
'currency' => 'string',
|
||||||
|
'employees_can_see_billable_rates' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class OrganizationFactory extends Factory
|
|||||||
'billable_rate' => null,
|
'billable_rate' => null,
|
||||||
'user_id' => User::factory(),
|
'user_id' => User::factory(),
|
||||||
'personal_team' => true,
|
'personal_team' => true,
|
||||||
|
'employees_can_see_billable_rates' => false,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use App\Models\Project;
|
|||||||
use App\Models\ProjectMember;
|
use App\Models\ProjectMember;
|
||||||
use App\Service\ColorService;
|
use App\Service\ColorService;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends Factory<Project>
|
* @extends Factory<Project>
|
||||||
@@ -46,12 +47,21 @@ class ProjectFactory extends Factory
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function billable(): self
|
public function billable(?int $billableRate = null): self
|
||||||
{
|
{
|
||||||
return $this->state(function (array $attributes): array {
|
return $this->state(function (array $attributes) use ($billableRate): array {
|
||||||
return [
|
return [
|
||||||
'is_billable' => true,
|
'is_billable' => true,
|
||||||
'billable_rate' => $this->faker->numberBetween(50, 1000) * 100,
|
'billable_rate' => $billableRate === null ? $this->faker->numberBetween(50, 1000) * 100 : $billableRate,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createdAt(Carbon $createdAt): self
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) use ($createdAt): array {
|
||||||
|
return [
|
||||||
|
'created_at' => $createdAt,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('organizations', function (Blueprint $table): void {
|
||||||
|
$table->boolean('employees_can_see_billable_rates')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('organizations', function (Blueprint $table): void {
|
||||||
|
$table->dropColumn('employees_can_see_billable_rates');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -53,6 +53,32 @@ abstract class TestCaseWithDatabase extends TestCase
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createUserWithRole(Role $role): object
|
||||||
|
{
|
||||||
|
$owner = User::factory()->create();
|
||||||
|
$organization = Organization::factory()->withOwner($owner)->create();
|
||||||
|
$ownerMember = Member::factory()->forUser($owner)->forOrganization($organization)->role(Role::Owner)->create();
|
||||||
|
$owner->currentOrganization()->associate($organization);
|
||||||
|
$owner->save();
|
||||||
|
|
||||||
|
if ($role === Role::Owner) {
|
||||||
|
$user = $owner;
|
||||||
|
$member = $ownerMember;
|
||||||
|
} else {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$member = Member::factory()->forUser($user)->forOrganization($organization)->role($role)->create();
|
||||||
|
$user->currentOrganization()->associate($organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (object) [
|
||||||
|
'user' => $user,
|
||||||
|
'organization' => $organization,
|
||||||
|
'member' => $member,
|
||||||
|
'owner' => $owner,
|
||||||
|
'ownerMember' => $ownerMember,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
protected function enableQueryLog(): void
|
protected function enableQueryLog(): void
|
||||||
{
|
{
|
||||||
DB::flushQueryLog();
|
DB::flushQueryLog();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Tests\Unit\Endpoint\Api\V1;
|
namespace Tests\Unit\Endpoint\Api\V1;
|
||||||
|
|
||||||
|
use App\Enums\Role;
|
||||||
use App\Http\Controllers\Api\V1\OrganizationController;
|
use App\Http\Controllers\Api\V1\OrganizationController;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
use App\Service\BillableRateService;
|
use App\Service\BillableRateService;
|
||||||
@@ -58,6 +59,40 @@ class OrganizationEndpointTest extends ApiEndpointTestAbstract
|
|||||||
$response->assertJsonPath('data.id', $data->organization->getKey());
|
$response->assertJsonPath('data.id', $data->organization->getKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_show_endpoint_shows_billable_rate_for_members_with_role_employee_if_organization_allows_it(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithRole(Role::Employee);
|
||||||
|
$data->organization->employees_can_see_billable_rates = true;
|
||||||
|
$data->organization->billable_rate = 100;
|
||||||
|
$data->organization->save();
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->getJson(route('api.v1.organizations.show', [$data->organization->getKey()]));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('data.billable_rate', 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_show_endpoint_does_not_show_billable_rate_for_members_with_role_employee_if_organization_does_not_allow_it(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithRole(Role::Employee);
|
||||||
|
$data->organization->employees_can_see_billable_rates = false;
|
||||||
|
$data->organization->billable_rate = 100;
|
||||||
|
$data->organization->save();
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->getJson(route('api.v1.organizations.show', [$data->organization->getKey()]));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('data.billable_rate', null);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_update_endpoint_fails_if_user_has_no_permission_to_update_organizations(): void
|
public function test_update_endpoint_fails_if_user_has_no_permission_to_update_organizations(): void
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
@@ -123,6 +158,32 @@ class OrganizationEndpointTest extends ApiEndpointTestAbstract
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_update_endpoint_can_update_the_setting_employees_can_see_billable_rates(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithPermission([
|
||||||
|
'organizations:update',
|
||||||
|
]);
|
||||||
|
$this->assertBillableRateServiceIsUnused();
|
||||||
|
$data->organization->employees_can_see_billable_rates = false;
|
||||||
|
$data->organization->save();
|
||||||
|
$organizationFake = Organization::factory()->make();
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->putJson(route('api.v1.organizations.update', [$data->organization->getKey()]), [
|
||||||
|
'name' => $organizationFake->name,
|
||||||
|
'employees_can_see_billable_rates' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$this->assertDatabaseHas(Organization::class, [
|
||||||
|
'name' => $organizationFake->name,
|
||||||
|
'employees_can_see_billable_rates' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_update_endpoint_can_update_billable_rate_of_organization_and_update_time_entries(): void
|
public function test_update_endpoint_can_update_billable_rate_of_organization_and_update_time_entries(): void
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Tests\Unit\Endpoint\Api\V1;
|
namespace Tests\Unit\Endpoint\Api\V1;
|
||||||
|
|
||||||
|
use App\Enums\Role;
|
||||||
use App\Http\Controllers\Api\V1\ProjectController;
|
use App\Http\Controllers\Api\V1\ProjectController;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
@@ -159,6 +160,64 @@ class ProjectEndpointTest extends ApiEndpointTestAbstract
|
|||||||
$response->assertJsonCount(4, 'data');
|
$response->assertJsonCount(4, 'data');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_index_endpoint_sets_billable_rate_to_null_if_member_is_employee_and_organization_does_not_allow_employees_to_see_billable_rates(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithRole(Role::Employee);
|
||||||
|
$organization = $data->organization;
|
||||||
|
$organization->employees_can_see_billable_rates = false;
|
||||||
|
$organization->save();
|
||||||
|
$privateProjects = Project::factory()->forOrganization($data->organization)->isPrivate()->billable(111)->createMany(2);
|
||||||
|
$publicProjects = Project::factory()->forOrganization($data->organization)->isPublic()->billable(112)->createMany(2);
|
||||||
|
$privateProjectsWithMembership = Project::factory()->forOrganization($data->organization)->addMember($data->member)->billable(113)->isPrivate()->createMany(2);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->getJson(route('api.v1.projects.index', [$organization->getKey()]));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonCount(4, 'data');
|
||||||
|
$response->assertJson(fn (AssertableJson $json) => $json
|
||||||
|
->has('data')
|
||||||
|
->has('links')
|
||||||
|
->has('meta')
|
||||||
|
->where('data.0.billable_rate', null)
|
||||||
|
->where('data.1.billable_rate', null)
|
||||||
|
->where('data.2.billable_rate', null)
|
||||||
|
->where('data.3.billable_rate', null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_index_endpoint_does_not_set_billable_rate_to_null_if_member_is_employee_and_organization_allows_employees_to_see_billable_rates(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$data = $this->createUserWithRole(Role::Employee);
|
||||||
|
$organization = $data->organization;
|
||||||
|
$organization->employees_can_see_billable_rates = true;
|
||||||
|
$organization->save();
|
||||||
|
$privateProjects = Project::factory()->forOrganization($data->organization)->isPrivate()->billable(111)->createdAt(now()->subMinutes(4))->createMany(2);
|
||||||
|
$publicProjects = Project::factory()->forOrganization($data->organization)->isPublic()->billable(112)->createdAt(now()->subMinutes(3))->createMany(2);
|
||||||
|
$privateProjectsWithMembership = Project::factory()->forOrganization($data->organization)->addMember($data->member)->billable(113)->isPrivate()->createdAt(now()->subMinutes(2))->createMany(2);
|
||||||
|
Passport::actingAs($data->user);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$response = $this->getJson(route('api.v1.projects.index', [$organization->getKey()]));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonCount(4, 'data');
|
||||||
|
$response->assertJson(fn (AssertableJson $json) => $json
|
||||||
|
->has('data')
|
||||||
|
->has('links')
|
||||||
|
->has('meta')
|
||||||
|
->where('data.0.billable_rate', 112)
|
||||||
|
->where('data.1.billable_rate', 112)
|
||||||
|
->where('data.2.billable_rate', 113)
|
||||||
|
->where('data.3.billable_rate', 113)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_show_endpoint_fails_if_user_is_not_part_of_project_organization(): void
|
public function test_show_endpoint_fails_if_user_is_not_part_of_project_organization(): void
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
Reference in New Issue
Block a user