Remove EmployeeStatus enum and related references from the codebase. Update Employee and User models to eliminate employee status handling. Refactor EmployeeController and related components to remove employee status filtering and display. Adjust EmployeeRequest and migration files accordingly.

This commit is contained in:
Yoga Pangestu 2026-06-10 01:31:50 +07:00
parent 926da48a8e
commit 184b012fd9
12 changed files with 63 additions and 170 deletions

View File

@ -1,23 +0,0 @@
<?php
namespace App\Enums;
use App\Traits\ProvidesEnumOptions;
enum EmployeeStatus: string
{
use ProvidesEnumOptions;
case ACTIVE = 'active';
case INACTIVE = 'inactive';
case RESIGNED = 'resigned';
public function label(): string
{
return match ($this) {
self::ACTIVE => 'Aktif',
self::INACTIVE => 'Tidak Aktif',
self::RESIGNED => 'Resign',
};
}
}

View File

@ -2,7 +2,6 @@
namespace App\Http\Controllers\Admin\Hr; namespace App\Http\Controllers\Admin\Hr;
use App\Enums\EmployeeStatus;
use App\Enums\EmploymentStatus; use App\Enums\EmploymentStatus;
use App\Enums\Gender; use App\Enums\Gender;
use App\Http\Controllers\Concerns\ParsesDataTableQuery; use App\Http\Controllers\Concerns\ParsesDataTableQuery;
@ -31,7 +30,6 @@ public function index(Request $request): Response
$direction = $tableQuery['direction']; $direction = $tableQuery['direction'];
$employmentStatus = $request->string('employment_status')->toString(); $employmentStatus = $request->string('employment_status')->toString();
$employeeStatus = $request->string('employee_status')->toString();
$query = User::query() $query = User::query()
->with(['profile', 'employee']) ->with(['profile', 'employee'])
@ -43,19 +41,12 @@ public function index(Request $request): Response
->orWhereHas('profile', function ($query) use ($search): void { ->orWhereHas('profile', function ($query) use ($search): void {
$query->where('full_name', 'like', "%{$search}%") $query->where('full_name', 'like', "%{$search}%")
->orWhere('phone_number', 'like', "%{$search}%"); ->orWhere('phone_number', 'like', "%{$search}%");
})
->orWhereHas('employee', function ($query) use ($search): void {
$query->where('employee_code', 'like', "%{$search}%");
}); });
}); });
}) })
->when( ->when(
$employmentStatus !== '', $employmentStatus !== '',
fn ($query) => $query->whereHas('employee', fn ($query) => $query->where('employment_status', $employmentStatus)) fn ($query) => $query->whereHas('employee', fn ($query) => $query->where('employment_status', $employmentStatus))
)
->when(
$employeeStatus !== '',
fn ($query) => $query->whereHas('employee', fn ($query) => $query->where('employee_status', $employeeStatus))
); );
$this->applySorting($query, $sort, $direction); $this->applySorting($query, $sort, $direction);
@ -69,10 +60,8 @@ public function index(Request $request): Response
'employees' => $employees, 'employees' => $employees,
'filters' => $this->dataTableFilters($tableQuery, [ 'filters' => $this->dataTableFilters($tableQuery, [
'employment_status' => $employmentStatus, 'employment_status' => $employmentStatus,
'employee_status' => $employeeStatus,
]), ]),
'employmentStatuses' => EmploymentStatus::selectOptions(), 'employmentStatuses' => EmploymentStatus::selectOptions(),
'employeeStatuses' => EmployeeStatus::selectOptions(),
]); ]);
} }
@ -124,7 +113,7 @@ public function edit(User $user): Response
return Inertia::render('admin/hr/employees/Edit', [ return Inertia::render('admin/hr/employees/Edit', [
'genders' => Gender::selectOptions(), 'genders' => Gender::selectOptions(),
'employmentStatuses' => EmploymentStatus::selectOptions(), 'employmentStatuses' => EmploymentStatus::selectOptions(),
'employee' => $this->transformEmployee($user), 'employee' => $this->transformEmployeeForForm($user),
]); ]);
} }
@ -203,11 +192,9 @@ public function destroy(User $user): RedirectResponse
private function applySorting(Builder $query, string $sort, string $direction): void private function applySorting(Builder $query, string $sort, string $direction): void
{ {
$employeeSorts = [ $employeeSorts = [
'employee_code',
'join_date', 'join_date',
'base_salary', 'base_salary',
'employment_status', 'employment_status',
'employee_status',
]; ];
if (in_array($sort, $employeeSorts, true)) { if (in_array($sort, $employeeSorts, true)) {
@ -241,6 +228,29 @@ private function applySorting(Builder $query, string $sort, string $direction):
$query->latest(); $query->latest();
} }
/**
* @return array<string, mixed>
*/
private function transformEmployeeForForm(User $user): array
{
$employee = $user->employee;
$profile = $user->profile;
return [
'id' => $user->id,
'email' => $user->email,
'username' => $user->username,
'full_name' => $profile->full_name,
'phone_number' => $profile->phone_number,
'gender' => $profile->gender?->value,
'birth_date' => $profile->birth_date?->format('Y-m-d'),
'address' => $profile->address,
'join_date' => $employee->join_date?->format('Y-m-d'),
'employment_status' => $employee->employment_status?->value,
'base_salary' => $employee->base_salary,
];
}
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
@ -251,13 +261,10 @@ private function transformEmployee(User $user): array
return [ return [
'id' => $user->id, 'id' => $user->id,
'employee_code' => $employee->employee_code,
'join_date' => $employee->join_date_formatted, 'join_date' => $employee->join_date_formatted,
'resign_date' => $employee->resign_date_formatted, 'resign_date' => $employee->resign_date_formatted,
'employment_status' => $employee->employment_status?->value, 'employment_status' => $employee->employment_status?->value,
'employment_status_label' => $employee->employment_status?->label(), 'employment_status_label' => $employee->employment_status?->label(),
'employee_status' => $employee->employee_status?->value,
'employee_status_label' => $employee->employee_status?->label(),
'base_salary' => $employee->base_salary, 'base_salary' => $employee->base_salary,
'base_salary_formatted' => $employee->base_salary_formatted, 'base_salary_formatted' => $employee->base_salary_formatted,
'email' => $user->email, 'email' => $user->email,

View File

@ -14,13 +14,6 @@ public function authorize(): bool
return auth()->check(); return auth()->check();
} }
protected function prepareForValidation(): void
{
$this->merge([
'base_salary' => $this->integer('base_salary'),
]);
}
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */

View File

@ -2,13 +2,10 @@
namespace App\Models; namespace App\Models;
use App\Enums\EmployeeStatus;
use App\Enums\EmploymentStatus; use App\Enums\EmploymentStatus;
use App\Observers\EmployeeObserver;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Appends;
use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Attributes\Guarded;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Attributes\Scope; use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
@ -18,7 +15,6 @@
#[Guarded(['id'])] #[Guarded(['id'])]
#[Appends(['base_salary_formatted', 'join_date_formatted', 'resign_date_formatted'])] #[Appends(['base_salary_formatted', 'join_date_formatted', 'resign_date_formatted'])]
#[ObservedBy([EmployeeObserver::class])]
class Employee extends Model class Employee extends Model
{ {
use SoftDeletes; use SoftDeletes;
@ -29,29 +25,10 @@ protected function casts(): array
'join_date' => 'date', 'join_date' => 'date',
'resign_date' => 'date', 'resign_date' => 'date',
'employment_status' => EmploymentStatus::class, 'employment_status' => EmploymentStatus::class,
'employee_status' => EmployeeStatus::class,
'base_salary' => 'integer', 'base_salary' => 'integer',
]; ];
} }
#[Scope]
public function active(Builder $query): void
{
$query->where('employee_status', EmployeeStatus::ACTIVE->value);
}
#[Scope]
public function inactive(Builder $query): void
{
$query->where('employee_status', EmployeeStatus::INACTIVE->value);
}
#[Scope]
public function resigned(Builder $query): void
{
$query->where('employee_status', EmployeeStatus::RESIGNED->value);
}
public function fullTime(Builder $query): void public function fullTime(Builder $query): void
{ {
$query->where('employment_status', EmploymentStatus::FULL_TIME->value); $query->where('employment_status', EmploymentStatus::FULL_TIME->value);
@ -60,25 +37,25 @@ public function fullTime(Builder $query): void
#[Scope] #[Scope]
public function partTime(Builder $query): void public function partTime(Builder $query): void
{ {
$query->where('employee_status', EmploymentStatus::PART_TIME->value); $query->where('employment_status', EmploymentStatus::PART_TIME->value);
} }
#[Scope] #[Scope]
public function contract(Builder $query): void public function contract(Builder $query): void
{ {
$query->where('employee_status', EmploymentStatus::CONTRACT->value); $query->where('employment_status', EmploymentStatus::CONTRACT->value);
} }
#[Scope] #[Scope]
public function temporary(Builder $query): void public function temporary(Builder $query): void
{ {
$query->where('employee_status', EmploymentStatus::TEMPORARY->value); $query->where('employment_status', EmploymentStatus::TEMPORARY->value);
} }
public function baseSalaryFormatted(): Attribute public function baseSalaryFormatted(): Attribute
{ {
return Attribute::make( return Attribute::make(
get: fn () => number_format($this->base_salary, 0, ',', '.'), get: fn () => 'Rp '.number_format($this->base_salary, 0, ',', '.'),
); );
} }

View File

@ -4,6 +4,8 @@
use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Attributes\Guarded;
use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -25,6 +27,18 @@ protected function casts(): array
]; ];
} }
#[Scope]
public function active(Builder $query): void
{
$query->where('is_active', true);
}
#[Scope]
public function inactive(Builder $query): void
{
$query->where('is_active', false);
}
public function profile(): HasOne public function profile(): HasOne
{ {
return $this->hasOne(UserProfile::class); return $this->hasOne(UserProfile::class);

View File

@ -1,35 +0,0 @@
<?php
namespace App\Observers;
use App\Models\Employee;
use Illuminate\Support\Facades\DB;
class EmployeeObserver
{
public function creating(Employee $employee): void
{
if (filled($employee->employee_code)) {
return;
}
$year = now()->format('Y');
$prefix = "EMP-{$year}-";
$employee->employee_code = DB::transaction(function () use ($prefix): string {
$latestCode = Employee::withTrashed()
->where('employee_code', 'like', $prefix.'%')
->lockForUpdate()
->orderByDesc('employee_code')
->value('employee_code');
$sequence = 1;
if ($latestCode !== null) {
$sequence = (int) substr($latestCode, strlen($prefix)) + 1;
}
return $prefix.str_pad((string) $sequence, 4, '0', STR_PAD_LEFT);
});
}
}

View File

@ -1,6 +1,5 @@
<?php <?php
use App\Enums\EmployeeStatus;
use App\Enums\EmploymentStatus; use App\Enums\EmploymentStatus;
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
@ -18,11 +17,9 @@ public function up(): void
$table->foreignId('user_id')->constrained()->cascadeOnDelete()->unique(); $table->foreignId('user_id')->constrained()->cascadeOnDelete()->unique();
$table->string('employee_code', 20)->unique();
$table->date('join_date'); $table->date('join_date');
$table->date('resign_date')->nullable(); $table->date('resign_date')->nullable();
$table->enum('employment_status', array_column(EmploymentStatus::cases(), 'value'))->default(EmploymentStatus::FULL_TIME->value); $table->enum('employment_status', array_column(EmploymentStatus::cases(), 'value'))->default(EmploymentStatus::FULL_TIME->value);
$table->enum('employee_status', array_column(EmployeeStatus::cases(), 'value'))->default(EmployeeStatus::ACTIVE->value);
$table->unsignedInteger('base_salary'); $table->unsignedInteger('base_salary');
$table->timestamp('created_at')->useCurrent(); $table->timestamp('created_at')->useCurrent();

View File

@ -3,21 +3,8 @@ import { h } from 'vue';
import { DataTableColumnHeader } from '@/components/data-table'; import { DataTableColumnHeader } from '@/components/data-table';
import DataTableActions from '@/components/hr/employees/data-table-actions.vue'; import DataTableActions from '@/components/hr/employees/data-table-actions.vue';
import EmployeeStatusToggle from '@/components/hr/employees/employee-status-toggle.vue'; import EmployeeStatusToggle from '@/components/hr/employees/employee-status-toggle.vue';
import { Badge } from '@/components/ui/badge';
import type { EmployeeListItem } from '@/types/employee'; import type { EmployeeListItem } from '@/types/employee';
function employeeStatusVariant(status: string): 'default' | 'secondary' | 'destructive' {
if (status === 'active') {
return 'default';
}
if (status === 'resigned') {
return 'destructive';
}
return 'secondary';
}
function formatDate(value: string | null): string { function formatDate(value: string | null): string {
if (!value) { if (!value) {
return '-'; return '-';
@ -37,12 +24,6 @@ function formatDate(value: string | null): string {
} }
export const columns: ColumnDef<EmployeeListItem>[] = [ export const columns: ColumnDef<EmployeeListItem>[] = [
{
accessorKey: 'employee_code',
enableSorting: true,
header: () => h(DataTableColumnHeader, { title: 'Kode', column: 'employee_code' }),
cell: ({ row }) => h('span', { class: 'font-medium' }, row.getValue('employee_code')),
},
{ {
accessorKey: 'full_name', accessorKey: 'full_name',
enableSorting: true, enableSorting: true,
@ -107,29 +88,14 @@ export const columns: ColumnDef<EmployeeListItem>[] = [
enableSorting: true, enableSorting: true,
header: () => h(DataTableColumnHeader, { title: 'Status Kepegawaian', column: 'employment_status' }), header: () => h(DataTableColumnHeader, { title: 'Status Kepegawaian', column: 'employment_status' }),
}, },
{
accessorKey: 'employee_status_label',
enableSorting: true,
header: () => h(DataTableColumnHeader, { title: 'Status Pegawai', column: 'employee_status' }),
cell: ({ row }) => h(
Badge,
{ variant: employeeStatusVariant(row.original.employee_status) },
() => row.original.employee_status_label,
),
},
{ {
accessorKey: 'base_salary_formatted', accessorKey: 'base_salary_formatted',
enableSorting: true, enableSorting: true,
header: () => h(DataTableColumnHeader, { header: () => h(DataTableColumnHeader, {
title: 'Gaji Pokok', title: 'Gaji Pokok',
column: 'base_salary', column: 'base_salary',
class: 'justify-end',
}), }),
cell: ({ row }) => h( cell: ({ row }) => row.original.base_salary_formatted,
'div',
{ class: 'text-right' },
`Rp ${row.original.base_salary_formatted}`,
),
}, },
{ {
id: 'status_toggle', id: 'status_toggle',

View File

@ -42,11 +42,7 @@ function toggleStatus(checked: boolean) {
<template> <template>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Switch <Switch :model-value="isActive" :disabled="processing" @update:model-value="toggleStatus" />
:model-value="isActive"
:disabled="processing || employee.employee_status === 'resigned'"
@update:model-value="toggleStatus"
/>
<Badge :variant="isActive ? 'default' : 'secondary'"> <Badge :variant="isActive ? 'default' : 'secondary'">
{{ isActive ? 'Aktif' : 'Nonaktif' }} {{ isActive ? 'Aktif' : 'Nonaktif' }}
</Badge> </Badge>

View File

@ -5,10 +5,10 @@ import { computed } from 'vue';
import EmployeeForm from '@/components/hr/EmployeeForm.vue'; import EmployeeForm from '@/components/hr/EmployeeForm.vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import AdminLayout from '@/layouts/AdminLayout.vue'; import AdminLayout from '@/layouts/AdminLayout.vue';
import type { EmployeeListItem, EnumOption } from '@/types/employee'; import type { EmployeeEditItem, EnumOption } from '@/types/employee';
const props = defineProps<{ const props = defineProps<{
employee: EmployeeListItem; employee: EmployeeEditItem;
genders: EnumOption[]; genders: EnumOption[];
employmentStatuses: EnumOption[]; employmentStatuses: EnumOption[];
}>(); }>();
@ -22,8 +22,8 @@ const initialData = computed(() => ({
birth_date: props.employee.birth_date ?? '', birth_date: props.employee.birth_date ?? '',
address: props.employee.address ?? '', address: props.employee.address ?? '',
join_date: props.employee.join_date ?? '', join_date: props.employee.join_date ?? '',
employment_status: props.employee.employment_status, employment_status: props.employee.employment_status ?? 'full_time',
base_salary: String(props.employee.base_salary), base_salary: props.employee.base_salary != null ? String(props.employee.base_salary) : '',
})); }));
</script> </script>

View File

@ -18,10 +18,8 @@ const props = defineProps<{
sort?: string; sort?: string;
direction?: 'asc' | 'desc'; direction?: 'asc' | 'desc';
employment_status?: string; employment_status?: string;
employee_status?: string;
}; };
employmentStatuses: EnumOption[]; employmentStatuses: EnumOption[];
employeeStatuses: EnumOption[];
}>(); }>();
const search = ref(props.filters.search ?? ''); const search = ref(props.filters.search ?? '');
@ -29,7 +27,7 @@ const search = ref(props.filters.search ?? '');
const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({ const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({
url: '/admin/hr/employees', url: '/admin/hr/employees',
initial: { ...props.filters }, initial: { ...props.filters },
filterKeys: ['employment_status', 'employee_status'], filterKeys: ['employment_status'],
}); });
useDataTableQuerySync(() => props.filters, syncFromServer); useDataTableQuerySync(() => props.filters, syncFromServer);
@ -41,17 +39,10 @@ const filterDefs = computed<DataTableFilterDef[]>(() => [
type: 'select', type: 'select',
options: props.employmentStatuses, options: props.employmentStatuses,
}, },
{
key: 'employee_status',
label: 'Status Pegawai',
type: 'select',
options: props.employeeStatuses,
},
]); ]);
const filterValues = computed(() => ({ const filterValues = computed(() => ({
employment_status: query.value.employment_status ?? '', employment_status: query.value.employment_status ?? '',
employee_status: query.value.employee_status ?? '',
})); }));
const currentSort = computed<DataTableSort | null>(() => { const currentSort = computed<DataTableSort | null>(() => {

View File

@ -3,15 +3,26 @@ export type EnumOption = {
label: string; label: string;
}; };
export type EmployeeEditItem = {
id: number;
email: string | null;
username: string | null;
full_name: string | null;
phone_number: string | null;
gender: string | null;
birth_date: string | null;
address: string | null;
join_date: string | null;
employment_status: string | null;
base_salary: number | null;
};
export type EmployeeListItem = { export type EmployeeListItem = {
id: number; id: number;
employee_code: string;
join_date: string | null; join_date: string | null;
resign_date: string | null; resign_date: string | null;
employment_status: string; employment_status: string;
employment_status_label: string; employment_status_label: string;
employee_status: string;
employee_status_label: string;
base_salary: number; base_salary: number;
base_salary_formatted: string; base_salary_formatted: string;
email: string | null; email: string | null;
@ -43,7 +54,6 @@ export type EmployeeFilters = {
sort?: string; sort?: string;
direction?: 'asc' | 'desc' | null; direction?: 'asc' | 'desc' | null;
employment_status?: string; employment_status?: string;
employee_status?: string;
}; };
export type PaginatedEmployees = { export type PaginatedEmployees = {