From 09384d35170415ff18cc2efe81e497c505a79d91 Mon Sep 17 00:00:00 2001 From: Gregor Vostrak Date: Sun, 9 Mar 2025 14:12:42 +0100 Subject: [PATCH] hide total billable amounts from employees when employees_can_see_billable_rates is disabled --- .../Api/V1/Public/ReportController.php | 2 + .../Api/V1/TimeEntryController.php | 22 +++-- .../Controllers/Web/DashboardController.php | 5 +- .../Report/DetailedWithDataReportResource.php | 6 +- .../ReportExport/TimeEntriesReportExport.php | 12 +-- app/Service/TimeEntryAggregationService.php | 32 +++--- .../Common/Reporting/ReportingRow.vue | 4 +- .../Common/Reporting/ReportingTabNavbar.vue | 5 + .../Components/Dashboard/ThisWeekOverview.vue | 5 +- .../js/Components/NavigationSidebarItem.vue | 3 +- resources/js/Layouts/AppLayout.vue | 5 +- resources/js/Pages/Reporting.vue | 3 +- resources/js/Pages/ReportingDetailed.vue | 5 +- .../packages/api/src/openapi.json.client.ts | 85 ++-------------- resources/js/utils/permissions.ts | 7 ++ .../Endpoint/Web/DashboardEndpointTest.php | 2 +- .../TimeEntryAggregationServiceTest.php | 98 ++++++++++++++++++- 17 files changed, 176 insertions(+), 125 deletions(-) diff --git a/app/Http/Controllers/Api/V1/Public/ReportController.php b/app/Http/Controllers/Api/V1/Public/ReportController.php index 5c2c3ef2..739fa008 100644 --- a/app/Http/Controllers/Api/V1/Public/ReportController.php +++ b/app/Http/Controllers/Api/V1/Public/ReportController.php @@ -73,6 +73,7 @@ class ReportController extends Controller false, $report->properties->start, $report->properties->end, + true ); $historyData = $timeEntryAggregationService->getAggregatedTimeEntriesWithDescriptions( $timeEntriesQuery->clone(), @@ -83,6 +84,7 @@ class ReportController extends Controller true, $report->properties->start, $report->properties->end, + true ); return new DetailedWithDataReportResource($report, $data, $historyData); diff --git a/app/Http/Controllers/Api/V1/TimeEntryController.php b/app/Http/Controllers/Api/V1/TimeEntryController.php index 6f8afb58..cd3039cb 100644 --- a/app/Http/Controllers/Api/V1/TimeEntryController.php +++ b/app/Http/Controllers/Api/V1/TimeEntryController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Http\Controllers\Api\V1; use App\Enums\ExportFormat; +use App\Enums\Role; use App\Exceptions\Api\FeatureIsNotAvailableInFreePlanApiException; use App\Exceptions\Api\PdfRendererIsNotConfiguredException; use App\Exceptions\Api\TimeEntryCanNotBeRestartedApiException; @@ -180,6 +181,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; $timeEntriesQuery = $this->getTimeEntriesQuery($organization, $request, $member); $timeEntriesQuery->with([ @@ -211,7 +213,8 @@ class TimeEntryController extends Controller $user->week_start, false, null, - null + null, + $showBillableRate ); $html = Blade::render($viewFile, [ 'timeEntries' => $timeEntriesQuery->get(), @@ -285,18 +288,18 @@ class TimeEntryController extends Controller * grouped_data: null|array * }>, * seconds: int, - * cost: int + * cost: int|null * } * } * @@ -312,6 +315,7 @@ class TimeEntryController extends Controller $this->checkPermission($organization, 'time-entries:view:all'); } $user = $this->user(); + $showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates; $group1Type = $request->getGroup(); $group2Type = $request->getSubGroup(); @@ -325,7 +329,8 @@ class TimeEntryController extends Controller $user->week_start, $request->getFillGapsInTimeGroups(), $request->getStart(), - $request->getEnd() + $request->getEnd(), + $showBillableRate ); return [ @@ -359,6 +364,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; $group = $request->getGroup(); $subGroup = $request->getSubGroup(); @@ -372,7 +378,8 @@ class TimeEntryController extends Controller $user->week_start, false, $request->getStart(), - $request->getEnd() + $request->getEnd(), + $showBillableRate ); $dataHistoryChart = $timeEntryAggregationService->getAggregatedTimeEntries( $timeEntriesAggregateQuery->clone(), @@ -382,7 +389,8 @@ class TimeEntryController extends Controller $user->week_start, true, $request->getStart(), - $request->getEnd() + $request->getEnd(), + $showBillableRate ); $currency = $organization->currency; $timezone = app(TimezoneService::class)->getTimezoneFromUser($this->user()); diff --git a/app/Http/Controllers/Web/DashboardController.php b/app/Http/Controllers/Web/DashboardController.php index 4c807ac6..01751c04 100644 --- a/app/Http/Controllers/Web/DashboardController.php +++ b/app/Http/Controllers/Web/DashboardController.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Http\Controllers\Web; +use App\Enums\Role; use App\Service\DashboardService; use App\Service\PermissionStore; use Illuminate\Auth\Access\AuthorizationException; @@ -33,6 +34,8 @@ class DashboardController extends Controller $latestTeamActivity = $dashboardService->latestTeamActivity($organization); } + $showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates; + return Inertia::render('Dashboard', [ 'weeklyProjectOverview' => $weeklyProjectOverview, 'latestTasks' => $latestTasks, @@ -41,7 +44,7 @@ class DashboardController extends Controller 'dailyTrackedHours' => $dailyTrackedHours, 'totalWeeklyTime' => $totalWeeklyTime, 'totalWeeklyBillableTime' => $totalWeeklyBillableTime, - 'totalWeeklyBillableAmount' => $totalWeeklyBillableAmount, + 'totalWeeklyBillableAmount' => $showBillableRate ? $totalWeeklyBillableAmount : null, 'weeklyHistory' => $weeklyHistory, ]); } diff --git a/app/Http/Resources/V1/Report/DetailedWithDataReportResource.php b/app/Http/Resources/V1/Report/DetailedWithDataReportResource.php index 1b6449f3..1ccec056 100644 --- a/app/Http/Resources/V1/Report/DetailedWithDataReportResource.php +++ b/app/Http/Resources/V1/Report/DetailedWithDataReportResource.php @@ -18,20 +18,20 @@ use Illuminate\Http\Request; * description: string|null, * color: string|null, * seconds: int, - * cost: int, + * cost: int|null, * grouped_type: string|null, * grouped_data: null|array * }>, * seconds: int, - * cost: int + * cost: int|null * } */ class DetailedWithDataReportResource extends BaseResource diff --git a/app/Service/ReportExport/TimeEntriesReportExport.php b/app/Service/ReportExport/TimeEntriesReportExport.php index c73a29be..f22e164a 100644 --- a/app/Service/ReportExport/TimeEntriesReportExport.php +++ b/app/Service/ReportExport/TimeEntriesReportExport.php @@ -22,18 +22,18 @@ class TimeEntriesReportExport implements FromView, ShouldAutoSize, WithCustomCsv * grouped_data: null|array * }>, * seconds: int, - * cost: int + * cost: int|null * } */ private array $data; @@ -52,18 +52,18 @@ class TimeEntriesReportExport implements FromView, ShouldAutoSize, WithCustomCsv * grouped_data: null|array * }>, * seconds: int, - * cost: int + * cost: int|null * } $data */ public function __construct(array $data, ExportFormat $exportFormat, string $currency, TimeEntryAggregationType $group, TimeEntryAggregationType $subGroup) diff --git a/app/Service/TimeEntryAggregationService.php b/app/Service/TimeEntryAggregationService.php index 62d0613f..55aa1f82 100644 --- a/app/Service/TimeEntryAggregationService.php +++ b/app/Service/TimeEntryAggregationService.php @@ -27,21 +27,21 @@ class TimeEntryAggregationService * grouped_data: null|array * }>, * seconds: int, - * cost: int + * cost: int|null * } */ - public function getAggregatedTimeEntries(Builder $timeEntriesQuery, ?TimeEntryAggregationType $group1Type, ?TimeEntryAggregationType $group2Type, string $timezone, Weekday $startOfWeek, bool $fillGapsInTimeGroups, ?Carbon $start, ?Carbon $end): array + public function getAggregatedTimeEntries(Builder $timeEntriesQuery, ?TimeEntryAggregationType $group1Type, ?TimeEntryAggregationType $group2Type, string $timezone, Weekday $startOfWeek, bool $fillGapsInTimeGroups, ?Carbon $start, ?Carbon $end, bool $showBillableRate): array { $fillGapsInTimeGroupsIsPossible = $fillGapsInTimeGroups && $start !== null && $end !== null; $group1Select = null; @@ -96,7 +96,7 @@ class TimeEntryAggregationService $group2Response[] = [ 'key' => $group2 === '' ? null : (string) $group2, 'seconds' => (int) $aggregate->get(0)->aggregate, - 'cost' => (int) $aggregate->get(0)->cost, + 'cost' => $showBillableRate ? (int) $aggregate->get(0)->cost : null, 'grouped_type' => null, 'grouped_data' => null, ]; @@ -113,7 +113,7 @@ class TimeEntryAggregationService $group1Response[] = [ 'key' => $group1 === '' ? null : (string) $group1, 'seconds' => $group2ResponseSum, - 'cost' => $group2ResponseCost, + 'cost' => $showBillableRate ? $group2ResponseCost : null, 'grouped_type' => $group2Type?->value, 'grouped_data' => $group2Response, ]; @@ -133,7 +133,7 @@ class TimeEntryAggregationService return [ 'seconds' => $group1ResponseSum, - 'cost' => $group1ResponseCost, + 'cost' => $showBillableRate ? $group1ResponseCost : null, 'grouped_type' => $group1Type?->value, 'grouped_data' => $group1Response, ]; @@ -148,25 +148,25 @@ class TimeEntryAggregationService * description: string|null, * color: string|null, * seconds: int, - * cost: int, + * cost: int|null, * grouped_type: string|null, * grouped_data: null|array * }>, * seconds: int, - * cost: int + * cost: int|null * } */ - public function getAggregatedTimeEntriesWithDescriptions(Builder $timeEntriesQuery, ?TimeEntryAggregationType $group1Type, ?TimeEntryAggregationType $group2Type, string $timezone, Weekday $startOfWeek, bool $fillGapsInTimeGroups, ?Carbon $start, ?Carbon $end): array + public function getAggregatedTimeEntriesWithDescriptions(Builder $timeEntriesQuery, ?TimeEntryAggregationType $group1Type, ?TimeEntryAggregationType $group2Type, string $timezone, Weekday $startOfWeek, bool $fillGapsInTimeGroups, ?Carbon $start, ?Carbon $end, bool $showBillableRate): array { - $aggregatedTimeEntries = $this->getAggregatedTimeEntries($timeEntriesQuery, $group1Type, $group2Type, $timezone, $startOfWeek, $fillGapsInTimeGroups, $start, $end); + $aggregatedTimeEntries = $this->getAggregatedTimeEntries($timeEntriesQuery, $group1Type, $group2Type, $timezone, $startOfWeek, $fillGapsInTimeGroups, $start, $end, $showBillableRate); $keysGroup1 = []; $keysGroup2 = []; @@ -289,12 +289,12 @@ class TimeEntryAggregationService * @param array @@ -302,12 +302,12 @@ class TimeEntryAggregationService * @return array diff --git a/resources/js/Components/Common/Reporting/ReportingRow.vue b/resources/js/Components/Common/Reporting/ReportingRow.vue index 3ac640e2..63ec536a 100644 --- a/resources/js/Components/Common/Reporting/ReportingRow.vue +++ b/resources/js/Components/Common/Reporting/ReportingRow.vue @@ -12,7 +12,7 @@ type AggregatedGroupedData = GroupedData & { type GroupedData = { seconds: number; - cost: number; + cost: number | null; description: string | null | undefined; }; @@ -48,7 +48,7 @@ const expanded = ref(false); {{ formatHumanReadableDuration(entry.seconds) }}
- {{ formatCents(entry.cost, getOrganizationCurrencyString()) }} + {{entry.cost ? formatCents(entry.cost, getOrganizationCurrencyString()) : '--' }}
(); + +const showSharedReports = computed(() => canViewReport());