From 175cb4acab518c7c205d775e8f864010911a83d6 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sun, 14 Jun 2026 15:29:08 +0700 Subject: [PATCH] refactor: make employee employment fields nullable and update related salary calculations and display logic --- app/Enums/Role.php | 8 +++- .../Requests/Admin/Hr/EmployeeRequest.php | 8 ++-- app/Models/Employee.php | 6 +-- app/Models/Payroll.php | 6 +-- app/Services/Finance/PayrollService.php | 2 +- app/Services/Hr/EmployeeService.php | 42 ++++++++++++++----- database/seeders/PayrollSeeder.php | 4 +- .../js/components/admin/hr/EmployeeForm.vue | 16 +++++-- .../js/pages/admin/hr/employees/Edit.vue | 2 +- 9 files changed, 65 insertions(+), 29 deletions(-) diff --git a/app/Enums/Role.php b/app/Enums/Role.php index 2d1e25c..aa9af89 100644 --- a/app/Enums/Role.php +++ b/app/Enums/Role.php @@ -35,7 +35,13 @@ public function label(): string public function permissions(): array { return match ($this) { - self::DEVELOPER, self::OWNER => Permission::cases(), + self::DEVELOPER, self::OWNER => array_values(array_filter( + Permission::cases(), + fn (Permission $permission) => ! in_array($permission, [ + Permission::ATTENDANCES_CREATE, + Permission::ATTENDANCES_DELETE, + ], true) + )), self::DIREKTUR => [ Permission::DASHBOARD_VIEW, diff --git a/app/Http/Requests/Admin/Hr/EmployeeRequest.php b/app/Http/Requests/Admin/Hr/EmployeeRequest.php index 85461e7..2e29b04 100644 --- a/app/Http/Requests/Admin/Hr/EmployeeRequest.php +++ b/app/Http/Requests/Admin/Hr/EmployeeRequest.php @@ -25,6 +25,8 @@ public function authorize(): bool */ public function rules(): array { + $anyEmployee = ! empty($this->join_date) || ! empty($this->employment_status) || ! empty($this->base_salary); + return [ 'email' => ['required', 'email', 'max:100', Rule::unique('users', 'email')->ignore($this->route('user')?->id)], 'username' => ['required', 'string', 'max:20', 'alpha_dash', Rule::unique('users', 'username')->ignore($this->route('user')?->id)], @@ -33,9 +35,9 @@ public function rules(): array 'gender' => ['nullable', Rule::enum(Gender::class)], 'birth_date' => ['nullable', 'date', 'before:today'], 'address' => ['nullable', 'string'], - 'join_date' => ['required', 'date'], - 'employment_status' => ['required', Rule::enum(EmploymentStatus::class)], - 'base_salary' => ['required', 'integer', 'min:0'], + 'join_date' => [$anyEmployee ? 'required' : 'nullable', 'date'], + 'employment_status' => [$anyEmployee ? 'required' : 'nullable', Rule::enum(EmploymentStatus::class)], + 'base_salary' => [$anyEmployee ? 'required' : 'nullable', 'integer', 'min:0'], 'role' => ['required', Rule::in(Role::assignableValues())], ]; } diff --git a/app/Models/Employee.php b/app/Models/Employee.php index f037481..283c81f 100644 --- a/app/Models/Employee.php +++ b/app/Models/Employee.php @@ -86,21 +86,21 @@ public function user(): BelongsTo public function baseSalaryFormatted(): Attribute { return Attribute::make( - get: fn () => 'Rp '.number_format($this->base_salary, 0, ',', '.'), + get: fn () => $this->base_salary !== null ? 'Rp '.number_format($this->base_salary, 0, ',', '.') : '-', ); } public function employmentStatusLabel(): Attribute { return Attribute::make( - get: fn () => $this->employment_status?->label(), + get: fn () => $this->employment_status?->label() ?? '-', ); } public function joinDateFormatted(): Attribute { return Attribute::make( - get: fn () => Carbon::parse($this->join_date)->translatedFormat('l, d F Y'), + get: fn () => $this->join_date ? Carbon::parse($this->join_date)->translatedFormat('l, d F Y') : '-', ); } diff --git a/app/Models/Payroll.php b/app/Models/Payroll.php index cf96ce3..9aabf20 100644 --- a/app/Models/Payroll.php +++ b/app/Models/Payroll.php @@ -70,7 +70,7 @@ public function payrollPeriod(): BelongsTo public function baseSalaryFormatted(): Attribute { return Attribute::make( - get: fn () => 'Rp '.number_format($this->base_salary, 0, ',', '.'), + get: fn () => $this->base_salary !== null ? 'Rp '.number_format($this->base_salary, 0, ',', '.') : '-', ); } @@ -133,7 +133,7 @@ public function calculateKasbonDeduction(): int ->where('status', EmployeeAdvanceStatus::APPROVED) ->sum('amount'); - return min($outstanding, $this->base_salary + (int) $this->adjustments() + return min($outstanding, (int) $this->base_salary + (int) $this->adjustments() ->where('type', PayrollAdjustmentType::BONUS) ->sum('amount')); } @@ -152,6 +152,6 @@ public function recalculateAmounts(): void $this->bonus_amount = $bonusAmount; $this->deduction_amount = $kasbonDeduction + $manualDeduction; - $this->total_amount = max(0, $this->base_salary + $bonusAmount - $this->deduction_amount); + $this->total_amount = max(0, (int) $this->base_salary + $bonusAmount - $this->deduction_amount); } } diff --git a/app/Services/Finance/PayrollService.php b/app/Services/Finance/PayrollService.php index 4c4cee0..1656ee9 100644 --- a/app/Services/Finance/PayrollService.php +++ b/app/Services/Finance/PayrollService.php @@ -178,7 +178,7 @@ public function generatePayrollsForPeriod(PayrollPeriod $period): void $payroll = new Payroll([ 'payroll_period_id' => $period->id, 'employee_id' => $employee->id, - 'base_salary' => $employee->base_salary, + 'base_salary' => $employee->base_salary ?? 0, 'bonus_amount' => 0, 'deduction_amount' => 0, 'total_amount' => 0, diff --git a/app/Services/Hr/EmployeeService.php b/app/Services/Hr/EmployeeService.php index ab3a5e1..7457cab 100644 --- a/app/Services/Hr/EmployeeService.php +++ b/app/Services/Hr/EmployeeService.php @@ -24,7 +24,6 @@ public function paginateForIndex( ): LengthAwarePaginator { $query = User::query() ->with(['profile', 'employee', 'roles']) - ->whereHas('employee') ->when($tableQuery['search'] !== '', function ($query) use ($tableQuery): void { $search = $tableQuery['search']; $query->where(function ($query) use ($search): void { @@ -81,12 +80,16 @@ public function create(array $validated): void 'address' => $validated['address'], ]); - Employee::create([ - 'user_id' => $user->id, - 'join_date' => $validated['join_date'], - 'employment_status' => $validated['employment_status'], - 'base_salary' => $validated['base_salary'], - ]); + $hasEmployee = !empty($validated['join_date']) && !empty($validated['employment_status']) && !empty($validated['base_salary']); + + if ($hasEmployee) { + Employee::create([ + 'user_id' => $user->id, + 'join_date' => $validated['join_date'], + 'employment_status' => $validated['employment_status'], + 'base_salary' => $validated['base_salary'], + ]); + } $user->syncRoles([$validated['role']]); }); @@ -112,10 +115,27 @@ public function update(User $user, array $validated): void $profile->address = $validated['address']; $profile->save(); - $employee->join_date = $validated['join_date']; - $employee->employment_status = $validated['employment_status']; - $employee->base_salary = $validated['base_salary']; - $employee->save(); + $hasEmployee = !empty($validated['join_date']) && !empty($validated['employment_status']) && !empty($validated['base_salary']); + + if ($hasEmployee) { + if ($employee) { + $employee->join_date = $validated['join_date']; + $employee->employment_status = $validated['employment_status']; + $employee->base_salary = $validated['base_salary']; + $employee->save(); + } else { + Employee::create([ + 'user_id' => $user->id, + 'join_date' => $validated['join_date'], + 'employment_status' => $validated['employment_status'], + 'base_salary' => $validated['base_salary'], + ]); + } + } else { + if ($employee) { + $employee->delete(); + } + } $user->syncRoles([$validated['role']]); }); diff --git a/database/seeders/PayrollSeeder.php b/database/seeders/PayrollSeeder.php index fb8c846..39ea165 100644 --- a/database/seeders/PayrollSeeder.php +++ b/database/seeders/PayrollSeeder.php @@ -21,8 +21,8 @@ public function run(): void Payroll::factory()->create([ 'payroll_period_id' => $period->id, 'employee_id' => $employee->id, - 'base_salary' => $employee->base_salary, - 'total_amount' => $employee->base_salary, + 'base_salary' => $employee->base_salary ?? 0, + 'total_amount' => $employee->base_salary ?? 0, ]); }); } diff --git a/resources/js/components/admin/hr/EmployeeForm.vue b/resources/js/components/admin/hr/EmployeeForm.vue index f283f33..c1e44f8 100644 --- a/resources/js/components/admin/hr/EmployeeForm.vue +++ b/resources/js/components/admin/hr/EmployeeForm.vue @@ -56,12 +56,19 @@ const form = useForm({ birth_date: props.initialData?.birth_date ?? '', address: props.initialData?.address ?? '', join_date: props.initialData?.join_date ?? '', - employment_status: props.initialData?.employment_status ?? 'full_time', + employment_status: props.initialData?.employment_status ?? '', base_salary: props.initialData?.base_salary ?? '', role: props.initialData?.role ?? '', }); function submit() { + form.transform((data) => ({ + ...data, + join_date: data.join_date === '' ? null : data.join_date, + employment_status: data.employment_status === 'none' || data.employment_status === '' ? null : data.employment_status, + base_salary: data.base_salary === '' ? null : data.base_salary, + })); + const options = { onError: () => { toast.error('Gagal menyimpan data. Periksa kembali formulir.'); @@ -173,23 +180,24 @@ function submit() {
- Tanggal Bergabung + Tanggal Bergabung - Gaji Pokok + Gaji Pokok - Status Kepegawaian + Status Kepegawaian