add billable rate restriction for free organizations

This commit is contained in:
Gregor Vostrak
2026-10-13 14:59:12 +02:00
parent 9e6013eaf1
commit ba2b37f264
55 changed files with 1207 additions and 103 deletions

View File

@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace App\Http\Controllers\Api\V1;
use App\Enums\Role;
use App\Models\Organization;
use App\Service\BillableRateService;
use App\Service\DashboardService;
use App\Service\PermissionStore;
use Illuminate\Auth\Access\AuthorizationException;
@@ -154,12 +154,12 @@ class ChartController extends Controller
*
* @response array{value: int, currency: string}
*/
public function totalWeeklyBillableAmount(Organization $organization, DashboardService $dashboardService): JsonResponse
public function totalWeeklyBillableAmount(Organization $organization, DashboardService $dashboardService, BillableRateService $billableRateService): JsonResponse
{
$this->checkPermission($organization, 'charts:view:own');
$user = $this->user();
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
$showBillableRate = $billableRateService->canSeeBillableRates($organization, $this->member($organization));
if (! $showBillableRate) {
throw new AuthorizationException('You do not have permission to view billable rates.');
}

View File

@@ -81,10 +81,9 @@ class MemberController extends Controller
{
$this->checkPermission($organization, 'members:update', $member);
if ($request->has('billable_rate') && $member->billable_rate !== $request->getBillableRate()) {
$oldBillableRate = $member->billable_rate;
if ($request->has('billable_rate')) {
$member->billable_rate = $request->getBillableRate();
$billableRateService->updateTimeEntriesBillableRateForMember($member);
}
if ($request->has('role') && $member->role !== $request->getRole()->value) {
$newRole = $request->getRole();
@@ -93,6 +92,10 @@ class MemberController extends Controller
}
$member->save();
if ($oldBillableRate !== $member->billable_rate) {
$billableRateService->updateTimeEntriesBillableRateForMember($member);
}
return new MemberResource($member);
}

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\Http\Controllers\Api\V1;
use App\Enums\Role;
use App\Events\AfterCreateOrganization;
use App\Http\Requests\V1\Organization\OrganizationDestroyRequest;
use App\Http\Requests\V1\Organization\OrganizationStoreRequest;
@@ -28,11 +27,11 @@ class OrganizationController extends Controller
*
* @throws AuthorizationException
*/
public function show(Organization $organization): OrganizationResource
public function show(Organization $organization, BillableRateService $billableRateService): OrganizationResource
{
$this->checkPermission($organization, 'organizations:view');
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
$showBillableRate = $billableRateService->canSeeBillableRates($organization, $this->member($organization));
return new OrganizationResource($organization, $showBillableRate);
}
@@ -81,14 +80,13 @@ class OrganizationController extends Controller
if ($request->getBreaksEnabled() !== null) {
$organization->breaks_enabled = $request->getBreaksEnabled();
}
$hasBillableRate = $request->has('billable_rate');
if ($hasBillableRate) {
$oldBillableRate = $organization->billable_rate;
$oldBillableRate = $organization->billable_rate;
if ($request->has('billable_rate')) {
$organization->billable_rate = $request->getBillableRate();
}
$organization->save();
if ($hasBillableRate && $oldBillableRate !== $request->getBillableRate()) {
if ($oldBillableRate !== $organization->billable_rate) {
$billableRateService->updateTimeEntriesBillableRateForOrganization($organization);
}

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\Http\Controllers\Api\V1;
use App\Enums\Role;
use App\Exceptions\Api\EntityStillInUseApiException;
use App\Http\Requests\V1\Project\ProjectIndexRequest;
use App\Http\Requests\V1\Project\ProjectStoreRequest;
@@ -39,7 +38,7 @@ class ProjectController extends Controller
*
* @operationId getProjects
*/
public function index(Organization $organization, ProjectIndexRequest $request): ProjectCollection
public function index(Organization $organization, ProjectIndexRequest $request, BillableRateService $billableRateService): ProjectCollection
{
$this->checkPermission($organization, 'projects:view');
$canViewAllProjects = $this->hasPermission($organization, 'projects:view:all');
@@ -63,7 +62,7 @@ class ProjectController extends Controller
->orderBy('id')
->paginate(config('app.pagination_per_page_default'));
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
$showBillableRate = $billableRateService->canSeeBillableRates($organization, $this->member($organization));
return new ProjectCollection($projects, $showBillableRate);
}
@@ -137,14 +136,16 @@ class ProjectController extends Controller
}
$oldBillableRate = $project->billable_rate;
$clientIdChanged = false;
$project->billable_rate = $request->getBillableRate();
if ($request->has('billable_rate')) {
$project->billable_rate = $request->getBillableRate();
}
if ($project->client_id !== $request->input('client_id')) {
$project->client_id = $request->input('client_id');
$clientIdChanged = true;
}
$project->save();
if ($oldBillableRate !== $request->getBillableRate()) {
if ($oldBillableRate !== $project->billable_rate) {
$billableRateService->updateTimeEntriesBillableRateForProject($project);
}
if ($clientIdChanged) {

View File

@@ -79,7 +79,7 @@ class ProjectMemberController extends Controller
$projectMember->project()->associate($project);
$projectMember->save();
if ($request->getBillableRate() !== null) {
if ($projectMember->billable_rate !== null) {
$billableRateService->updateTimeEntriesBillableRateForProjectMember($projectMember);
}
@@ -100,7 +100,7 @@ class ProjectMemberController extends Controller
$projectMember->billable_rate = $request->getBillableRate();
$projectMember->save();
if ($oldBillableRate !== $request->getBillableRate()) {
if ($oldBillableRate !== $projectMember->billable_rate) {
$billableRateService->updateTimeEntriesBillableRateForProjectMember($projectMember);
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace App\Http\Controllers\Api\V1;
use App\Enums\ExportFormat;
use App\Enums\Role;
use App\Enums\TimeEntryType;
use App\Exceptions\Api\FeatureIsNotAvailableInFreePlanApiException;
use App\Exceptions\Api\OverlappingTimeEntryApiException;
@@ -30,6 +29,7 @@ use App\Models\Organization;
use App\Models\Project;
use App\Models\Task;
use App\Models\TimeEntry;
use App\Service\BillableRateService;
use App\Service\LocalizationService;
use App\Service\ReportExport\TimeEntriesDetailedCsvExport;
use App\Service\ReportExport\TimeEntriesDetailedExport;
@@ -229,7 +229,7 @@ class TimeEntryController extends Controller
*
* @operationId exportTimeEntries
*/
public function indexExport(Organization $organization, TimeEntryIndexExportRequest $request, TimeEntryAggregationService $timeEntryAggregationService): JsonResponse
public function indexExport(Organization $organization, TimeEntryIndexExportRequest $request, TimeEntryAggregationService $timeEntryAggregationService, BillableRateService $billableRateService): JsonResponse
{
$member = $this->member($organization);
/** @var Member|null $memberFilter */
@@ -247,7 +247,7 @@ class TimeEntryController extends Controller
}
$user = $this->user();
$timezone = $user->timezone;
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
$showBillableRate = $billableRateService->canSeeBillableRates($organization, $this->member($organization));
$roundingType = $canAccessPremiumFeatures ? $request->getRoundingType() : null;
$roundingMinutes = $canAccessPremiumFeatures ? $request->getRoundingMinutes() : null;
@@ -386,7 +386,7 @@ class TimeEntryController extends Controller
*
* @throws AuthorizationException
*/
public function aggregate(Organization $organization, TimeEntryAggregateRequest $request, TimeEntryAggregationService $timeEntryAggregationService): array
public function aggregate(Organization $organization, TimeEntryAggregateRequest $request, TimeEntryAggregationService $timeEntryAggregationService, BillableRateService $billableRateService): array
{
$member = $this->member($organization);
/** @var Member|null $memberFilter */
@@ -398,7 +398,7 @@ class TimeEntryController extends Controller
}
$canAccessPremiumFeatures = $this->canAccessPremiumFeatures($organization);
$user = $this->user();
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
$showBillableRate = $billableRateService->canSeeBillableRates($organization, $this->member($organization));
$group1Type = $request->getGroup();
$group2Type = $request->getSubGroup();
@@ -436,7 +436,7 @@ class TimeEntryController extends Controller
* @throws NoOutputFileInResponse
* @throws FeatureIsNotAvailableInFreePlanApiException
*/
public function aggregateExport(Organization $organization, TimeEntryAggregateExportRequest $request, TimeEntryAggregationService $timeEntryAggregationService): JsonResponse
public function aggregateExport(Organization $organization, TimeEntryAggregateExportRequest $request, TimeEntryAggregationService $timeEntryAggregationService, BillableRateService $billableRateService): JsonResponse
{
$member = $this->member($organization);
/** @var Member|null $memberFilter */
@@ -453,7 +453,7 @@ class TimeEntryController extends Controller
}
$debug = $request->getDebug();
$user = $this->user();
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
$showBillableRate = $billableRateService->canSeeBillableRates($organization, $this->member($organization));
$group = $request->getGroup();
$subGroup = $request->getSubGroup();