Skip to content
This repository has been archived by the owner on Feb 8, 2025. It is now read-only.

Commit

Permalink
feat: manager approvals of timesheets (#464)
Browse files Browse the repository at this point in the history
* feat: manager approvals of timesheets

* wip

* Optimised images with calibre/image-actions

* final touch before CI

* wip

* cypress tests

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
djaiss and github-actions[bot] authored Jan 3, 2021
1 parent e622d0e commit 70526e6
Show file tree
Hide file tree
Showing 19 changed files with 769 additions and 139 deletions.
19 changes: 14 additions & 5 deletions app/Console/Commands/Tests/SetupDummyAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -1863,6 +1863,15 @@ private function createTimeTrackingEntries(): void
$this->populateTimeTrackingEntries($this->michael, 2);
$this->populateTimeTrackingEntries($this->michael, 1);
$this->populateTimeTrackingEntries($this->michael, 0);

// create multiple time tracking entries for direct reports of Michael
$this->populateTimeTrackingEntries($this->jim, 3);
$this->populateTimeTrackingEntries($this->jim, 2);
$this->populateTimeTrackingEntries($this->dwight, 3);
$this->populateTimeTrackingEntries($this->dwight, 2);
$this->populateTimeTrackingEntries($this->erin, 5);
$this->populateTimeTrackingEntries($this->erin, 4);
$this->populateTimeTrackingEntries($this->erin, 3);
}

private function populateTimeTrackingEntries(Employee $employee, int $weeksAgo): void
Expand All @@ -1871,7 +1880,7 @@ private function populateTimeTrackingEntries(Employee $employee, int $weeksAgo):
// first we need to create timesheets
$timesheet = (new CreateOrGetTimesheet)->execute([
'company_id' => $this->company->id,
'author_id' => $this->michael->id,
'author_id' => $employee->id,
'employee_id' => $employee->id,
'date' => Carbon::now()->subWeeks($weeksAgo)->startOfWeek()->format('Y-m-d'),
]);
Expand All @@ -1896,12 +1905,12 @@ private function populateTimeTrackingEntries(Employee $employee, int $weeksAgo):
}
}

// submit only the two oldest timesheets
if ($weeksAgo == 3 || $weeksAgo == 2) {
// submit only older timesheets
if ($weeksAgo > 1) {
(new SubmitTimesheet)->execute([
'company_id' => $this->company->id,
'author_id' => $this->michael->id,
'employee_id' => $this->michael->id,
'author_id' => $employee->id,
'employee_id' => $employee->id,
'timesheet_id' => $timesheet->id,
]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public function index()
->with('directReport.expenses')
->with('directReport.expenses.employee')
->with('directReport.expenses.category')
->with('directReport.timesheets')
->get();

if ($directReports->count() == 0) {
Expand All @@ -62,13 +63,15 @@ public function index()
$pendingExpenses = DashboardManagerViewHelper::pendingExpenses($employee, $directReports);
$oneOnOnes = DashboardManagerViewHelper::oneOnOnes($employee, $directReports);
$contractRenewals = DashboardManagerViewHelper::contractRenewals($employee, $directReports);
$timesheetApprovals = DashboardManagerViewHelper::timesheetApprovals($employee, $directReports);

return Inertia::render('Dashboard/Manager/Index', [
'employee' => $employeeInformation,
'notifications' => NotificationHelper::getNotifications($employee),
'pendingExpenses' => $pendingExpenses,
'oneOnOnes' => $oneOnOnes,
'contractRenewals' => $contractRenewals,
'timesheetApprovals' => $timesheetApprovals,
'defaultCompanyCurrency' => $company->currency,
]);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

namespace App\Http\Controllers\Company\Dashboard;

use Inertia\Response;
use Illuminate\Http\Request;
use App\Helpers\InstanceHelper;
use App\Models\Company\Company;
use App\Models\Company\Employee;
use App\Models\Company\Timesheet;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use App\Models\Company\DirectReport;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Services\Company\Employee\Timesheet\RejectTimesheet;
use App\Services\Company\Employee\Timesheet\ApproveTimesheet;

class DashboardTimesheetManagerController extends Controller
{
/**
* Approve the timesheet.
*
* @param Request $request
* @param int $companyId
* @param int $timesheetId
* @return JsonResponse
*/
public function approve(Request $request, int $companyId, int $timesheetId): JsonResponse
{
$company = InstanceHelper::getLoggedCompany();
$employee = InstanceHelper::getLoggedEmployee();

$timesheet = $this->canAccess($company, $timesheetId, $employee);

$data = [
'company_id' => $company->id,
'author_id' => $employee->id,
'employee_id' => $timesheet->employee->id,
'timesheet_id' => $timesheetId,
];

$timesheet = (new ApproveTimesheet)->execute($data);

return response()->json([
'data' => $timesheet->id,
], 201);
}

/**
* Reject the timesheet.
*
* @param Request $request
* @param int $companyId
* @param int $timesheetId
* @return JsonResponse
*/
public function reject(Request $request, int $companyId, int $timesheetId): JsonResponse
{
$company = InstanceHelper::getLoggedCompany();
$employee = InstanceHelper::getLoggedEmployee();

$timesheet = $this->canAccess($company, $timesheetId, $employee);

$data = [
'company_id' => $company->id,
'author_id' => $employee->id,
'employee_id' => $timesheet->employee->id,
'timesheet_id' => $timesheetId,
];

$timesheet = (new RejectTimesheet)->execute($data);

return response()->json([
'data' => $timesheet->id,
], 201);
}

/**
* Check that the current employee has access to this method.
* @param Company $company
* @param int $timesheetId
* @param Employee $employee
* @return mixed
*/
private function canAccess(Company $company, int $timesheetId, Employee $employee)
{
try {
$timesheet = Timesheet::where('company_id', $company->id)
->findOrFail($timesheetId);
} catch (ModelNotFoundException $e) {
return redirect('home');
}

if ($timesheet->status !== Timesheet::READY_TO_SUBMIT) {
return redirect('home');
}

// is the user a manager?
$directReports = DirectReport::where('company_id', $company->id)
->where('manager_id', $employee->id)
->with('directReport')
->with('directReport.timesheets')
->get();

if ($directReports->count() == 0) {
return redirect('home');
}

// can the manager see this timesheet?
if (! $employee->isManagerOf($timesheet->employee->id)) {
return redirect('home');
}

return $timesheet;
}
}
59 changes: 59 additions & 0 deletions app/Http/ViewHelpers/Dashboard/DashboardManagerViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

use Carbon\Carbon;
use App\Helpers\DateHelper;
use App\Helpers\TimeHelper;
use App\Helpers\MoneyHelper;
use App\Models\Company\Expense;
use App\Models\Company\Employee;
use App\Models\Company\Timesheet;
use App\Models\Company\OneOnOneEntry;
use App\Models\Company\EmployeeStatus;
use Illuminate\Database\Eloquent\Collection;
Expand Down Expand Up @@ -211,4 +213,61 @@ public static function contractRenewals(Employee $manager, Collection $directRep

return $collection;
}

/**
* Get the information about timesheets that need approval.
*
* @param Employee $manager
* @param Collection $directReports
* @return SupportCollection|null
*/
public static function timesheetApprovals(Employee $manager, Collection $directReports): ?SupportCollection
{
$employeesCollection = collect([]);
$company = $manager->company;

foreach ($directReports as $directReport) {
$employee = $directReport->directReport;

$pendingTimesheets = $employee->timesheets()
->where('status', Timesheet::READY_TO_SUBMIT)
->with('timeTrackingEntries')
->orderBy('started_at', 'desc')
->get();

$timesheetCollection = collect([]);
foreach ($pendingTimesheets as $timesheet) {
$totalWorkedInMinutes = $timesheet->timeTrackingEntries
->sum('duration');

$arrayOfTime = TimeHelper::convertToHoursAndMinutes($totalWorkedInMinutes);

$timesheetCollection->push([
'id' => $timesheet->id,
'started_at' => DateHelper::formatDate($timesheet->started_at),
'ended_at' => DateHelper::formatDate($timesheet->ended_at),
'duration' => trans('dashboard.manager_timesheet_approval_duration', [
'hours' => $arrayOfTime['hours'],
'minutes' => $arrayOfTime['minutes'],
]),
]);
}

if ($pendingTimesheets->count() !== 0) {
$employeesCollection->push([
'id' => $employee->id,
'name' => $employee->name,
'avatar' => $employee->avatar,
'position' => (! $employee->position) ? null : $employee->position->title,
'url' => route('employees.show', [
'company' => $company,
'employee' => $employee,
]),
'timesheets' => $timesheetCollection,
]);
}
}

return $employeesCollection;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ private function createTimesheet(): Timesheet

// is there an existing timesheet for this date?
$timesheet = Timesheet::where('company_id', $this->data['company_id'])
->where('employee_id', $this->data['employee_id'])
->where('started_at', $startOfWeek)
->where('ended_at', $endOfWeek)
->first();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use App\Jobs\LogEmployeeAudit;
use App\Models\Company\Employee;
use App\Models\Company\Timesheet;
use Illuminate\Support\Facades\Log;

class SubmitTimesheet extends BaseService
{
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions resources/js/Pages/Dashboard/Manager/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
<dashboard-menu :employee="employee" />
</div>

<timesheet-approvals
:direct-reports="timesheetApprovals"
/>

<one-on-one-with-direct-report
:one-on-ones="oneOnOnes"
/>
Expand All @@ -31,6 +35,7 @@
import Expense from '@/Pages/Dashboard/Manager/Partials/Expense';
import OneOnOneWithDirectReport from '@/Pages/Dashboard/Manager/Partials/OneOnOneWithDirectReport';
import ContractRenewal from '@/Pages/Dashboard/Manager/Partials/ContractRenewal';
import TimesheetApprovals from '@/Pages/Dashboard/Manager/Partials/TimesheetApprovals';
import Layout from '@/Shared/Layout';
import DashboardMenu from '@/Pages/Dashboard/Partials/DashboardMenu';
Expand All @@ -41,6 +46,7 @@ export default {
Layout,
DashboardMenu,
ContractRenewal,
TimesheetApprovals,
},
props: {
Expand All @@ -64,6 +70,10 @@ export default {
type: Array,
default: null,
},
timesheetApprovals: {
type: Array,
default: null,
},
defaultCurrency: {
type: Object,
default: null,
Expand Down
Loading

0 comments on commit 70526e6

Please sign in to comment.