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

Commit

Permalink
feat: record employee position history in the company (#822)
Browse files Browse the repository at this point in the history
  • Loading branch information
djaiss authored Apr 29, 2021
1 parent ba8e510 commit 3857bf7
Show file tree
Hide file tree
Showing 23 changed files with 498 additions and 38 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

</div>

# OfficeLife

<div align="center">

![Logo](docs/img/logo.png)
Expand Down
30 changes: 30 additions & 0 deletions app/Console/Commands/Tests/SetupDummyAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use App\Models\Company\RateYourManagerSurvey;
use App\Services\Company\Project\StartProject;
use App\Services\Company\Team\Ship\CreateShip;
use App\Models\Company\EmployeePositionHistory;
use App\Services\Company\Project\CreateProject;
use Symfony\Component\Console\Helper\ProgressBar;
use App\Services\Company\Adminland\Team\CreateTeam;
Expand Down Expand Up @@ -208,6 +209,7 @@ public function handle(): void
$this->createTimeTrackingEntries();
$this->setContractRenewalDates();
$this->setECoffeeProcess();
$this->addPreviousPositionsHistory();
$this->addSecondaryBlankAccount();
$this->stop();
}
Expand Down Expand Up @@ -2041,6 +2043,34 @@ private function setECoffeeProcess(): void
});
}

private function addPreviousPositionsHistory(): void
{
foreach ($this->employees as $employee) {
$position = Position::inRandomOrder()->first();

$started = Carbon::now()->subMonths(rand(24, 60));
$ended = $started->copy()->addMonths(rand(12, 24));

EmployeePositionHistory::create([
'employee_id' => $employee->id,
'position_id' => $position->id,
'started_at' => $started,
'ended_at' => $ended,
]);

$position = Position::inRandomOrder()->first();
$started = $ended->copy();
$ended = $started->copy()->addMonths(rand(6, 12));

EmployeePositionHistory::create([
'employee_id' => $employee->id,
'position_id' => $position->id,
'started_at' => $started,
'ended_at' => $ended,
]);
}
}

private function addSecondaryBlankAccount(): void
{
$this->info('☐ Create a blank account');
Expand Down
11 changes: 11 additions & 0 deletions app/Helpers/DateHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ public static function formatMonthAndDay(Carbon $date): string
return $date->isoFormat(trans('format.long_month_day'));
}

/**
* Return the short month and the year in a format like "Jul 2020".
*
* @param Carbon $date
* @return string
*/
public static function formatMonthAndYear(Carbon $date): string
{
return $date->isoFormat(trans('format.short_month_day'));
}

/**
* Return the day and the month in a format like "Jul 29".
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ public function show(Request $request, int $companyId, int $employeeId)
// all eCoffee session of this employee
$ecoffees = EmployeeShowViewHelper::eCoffees($employee, $company);

// all current and past positions
$currentPastPositions = EmployeeShowViewHelper::employeeCurrentAndPastPositions($employee, $company);

// information about the employee that the logged employee consults, that depends on what the logged Employee has the right to see
$employee = EmployeeShowViewHelper::informationAboutEmployee($employee, $permissions, $loggedEmployee);

Expand All @@ -82,6 +85,7 @@ public function show(Request $request, int $companyId, int $employeeId)
'teams' => $employeeTeams,
'skills' => $skills,
'ecoffees' => $ecoffees,
'positions' => $currentPastPositions,
]);
}

Expand Down
26 changes: 26 additions & 0 deletions app/Http/ViewHelpers/Employee/EmployeeShowViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -919,4 +919,30 @@ public static function hiredAfterEmployee(Employee $employee, Company $company):

return $percent;
}

/**
* Get the list of all positions the employee ever had in the company.
*
* @param Employee $employee
* @param Company $company
* @return Collection
*/
public static function employeeCurrentAndPastPositions(Employee $employee, Company $company): Collection
{
$positions = $employee->positionHistoryEntries()
->orderBy('started_at', 'desc')
->get();

$positionCollection = collect();
foreach ($positions as $entry) {
$positionCollection->push([
'id' => $entry->id,
'position' => $entry->position->title,
'started_at' => DateHelper::formatMonthAndYear($entry->started_at),
'ended_at' => $entry->ended_at ? DateHelper::formatMonthAndYear($entry->ended_at) : null,
]);
}

return $positionCollection;
}
}
10 changes: 10 additions & 0 deletions app/Models/Company/Employee.php
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,16 @@ public function picture()
return $this->hasOne(File::class, 'id', 'avatar_file_id');
}

/**
* Get the employee position history associated with the employee.
*
* @return HasMany
*/
public function positionHistoryEntries()
{
return $this->hasMany(EmployeePositionHistory::class);
}

/**
* Scope a query to only include unlocked users.
*
Expand Down
56 changes: 56 additions & 0 deletions app/Models/Company/EmployeePositionHistory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace App\Models\Company;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class EmployeePositionHistory extends Model
{
use HasFactory;

protected $table = 'employee_position_history';

/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'employee_id',
'position_id',
'started_at',
'ended_at',
];

/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'started_at',
'ended_at',
];

/**
* Get the employee record associated with the employee position.
*
* @return BelongsTo
*/
public function employee()
{
return $this->belongsTo(Employee::class);
}

/**
* Get the position record associated with the employee position.
*
* @return BelongsTo
*/
public function position()
{
return $this->belongsTo(Position::class);
}
}
10 changes: 10 additions & 0 deletions app/Models/Company/Position.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,14 @@ public function employees()
{
return $this->hasMany(Employee::class);
}

/**
* Get the employee position history records associated with the position.
*
* @return HasMany
*/
public function positionHistoryEntries()
{
return $this->hasMany(EmployeePositionHistory::class);
}
}
84 changes: 65 additions & 19 deletions app/Services/Company/Employee/Position/AssignPositionToEmployee.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@
use App\Jobs\LogEmployeeAudit;
use App\Models\Company\Employee;
use App\Models\Company\Position;
use App\Models\Company\EmployeePositionHistory;

class AssignPositionToEmployee extends BaseService
{
private Employee $employee;
private array $data;
private Position $position;
private int $previousPositionId;

/**
* Get the validation rules that apply to the service.
*
Expand All @@ -30,52 +36,92 @@ public function rules(): array
* Set an employee's position.
*
* @param array $data
*
* @return Employee
*/
public function execute(array $data): Employee
{
$this->validateRules($data);
$this->data = $data;
$this->validate();
$this->updateEmployee();
$this->addEmployeePositionHistoryEntry();
$this->log();

$this->author($data['author_id'])
->inCompany($data['company_id'])
return $this->employee;
}

private function validate(): void
{
$this->validateRules($this->data);

$this->author($this->data['author_id'])
->inCompany($this->data['company_id'])
->asAtLeastHR()
->canExecuteService();

$employee = $this->validateEmployeeBelongsToCompany($data);
$this->employee = $this->validateEmployeeBelongsToCompany($this->data);

$position = Position::where('company_id', $data['company_id'])
->findOrFail($data['position_id']);
$this->position = Position::where('company_id', $this->data['company_id'])
->findOrFail($this->data['position_id']);

$employee->position_id = $position->id;
$employee->save();
$this->previousPositionId = 0;
if ($this->employee->position_id) {
$this->previousPositionId = $this->employee->position_id;
}
}

private function updateEmployee(): void
{
$this->employee->position_id = $this->position->id;
$this->employee->save();
}

private function addEmployeePositionHistoryEntry(): void
{
// is there a previous employee position entry?
// the previous entry should be the one that has no ended_at date
$previousEntry = EmployeePositionHistory::where('employee_id', $this->employee->id)
->where('position_id', $this->previousPositionId)
->whereNull('ended_at')
->first();

if ($previousEntry) {
$previousEntry->ended_at = Carbon::now();
$previousEntry->save();
}

EmployeePositionHistory::create([
'position_id' => $this->position->id,
'employee_id' => $this->employee->id,
'started_at' => Carbon::now(),
]);
}

private function log(): void
{
LogAccountAudit::dispatch([
'company_id' => $data['company_id'],
'company_id' => $this->data['company_id'],
'action' => 'position_assigned',
'author_id' => $this->author->id,
'author_name' => $this->author->name,
'audited_at' => Carbon::now(),
'objects' => json_encode([
'employee_id' => $employee->id,
'employee_name' => $employee->name,
'position_id' => $position->id,
'position_title' => $position->title,
'employee_id' => $this->employee->id,
'employee_name' => $this->employee->name,
'position_id' => $this->position->id,
'position_title' => $this->position->title,
]),
])->onQueue('low');

LogEmployeeAudit::dispatch([
'employee_id' => $data['employee_id'],
'employee_id' => $this->data['employee_id'],
'action' => 'position_assigned',
'author_id' => $this->author->id,
'author_name' => $this->author->name,
'audited_at' => Carbon::now(),
'objects' => json_encode([
'position_id' => $position->id,
'position_title' => $position->title,
'position_id' => $this->position->id,
'position_title' => $this->position->title,
]),
])->onQueue('low');

return $employee;
}
}
34 changes: 34 additions & 0 deletions database/factories/Company/EmployeePositionHistoryFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Database\Factories\Company;

use Carbon\Carbon;
use App\Models\Company\Employee;
use App\Models\Company\Position;
use App\Models\Company\EmployeePositionHistory;
use Illuminate\Database\Eloquent\Factories\Factory;

class EmployeePositionHistoryFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = EmployeePositionHistory::class;

/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'employee_id' => Employee::factory(),
'position_id' => Position::factory(),
'started_at' => Carbon::now()->subMonths(2),
'ended_at' => Carbon::now(),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class CreateProjectMemberActivityTable extends Migration
*/
public function up()
{
// necessary for SQLlite
Schema::enableForeignKeyConstraints();

Schema::create('project_member_activities', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('project_id');
Expand Down
Loading

0 comments on commit 3857bf7

Please sign in to comment.