feat: refactor EmployeeController and EmployeeService to utilize Role enum for role options; implement transaction management in EmployeeService methods for create, update, toggle status, and delete operations; enhance Employee model date formatting methods; update frontend components for improved UI and code clarity
This commit is contained in:
parent
786d0fdb39
commit
f1c3658166
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Enums\EmploymentStatus;
|
||||
use App\Enums\Gender;
|
||||
use App\Enums\Role;
|
||||
use App\Http\Controllers\Concerns\FlashesEntityMessage;
|
||||
use App\Http\Controllers\Concerns\ParsesDataTableQuery;
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -46,7 +47,7 @@ public function index(Request $request): Response
|
||||
'employment_status' => $employmentStatus,
|
||||
'is_active' => $isActive,
|
||||
]),
|
||||
'roles' => $this->employeeService->assignableRoleOptions(),
|
||||
'roles' => Role::assignableSelectOptions(),
|
||||
'genders' => Gender::selectOptions(),
|
||||
'employmentStatuses' => EmploymentStatus::selectOptions(),
|
||||
]);
|
||||
@ -57,7 +58,7 @@ public function create(): Response
|
||||
return Inertia::render('admin/hr/employees/Create', [
|
||||
'genders' => Gender::selectOptions(),
|
||||
'employmentStatuses' => EmploymentStatus::selectOptions(),
|
||||
'roles' => $this->employeeService->assignableRoleOptions(),
|
||||
'roles' => Role::assignableSelectOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -77,7 +78,7 @@ public function edit(User $user): Response
|
||||
return Inertia::render('admin/hr/employees/Edit', [
|
||||
'genders' => Gender::selectOptions(),
|
||||
'employmentStatuses' => EmploymentStatus::selectOptions(),
|
||||
'roles' => $this->employeeService->assignableRoleOptions(),
|
||||
'roles' => Role::assignableSelectOptions(),
|
||||
'employee' => $editData['employee'],
|
||||
'profilePhoto' => $editData['profilePhoto'],
|
||||
]);
|
||||
|
||||
@ -78,7 +78,7 @@ public function employmentStatusLabel(): Attribute
|
||||
public function joinDateFormatted(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->join_date ? Carbon::parse($this->join_date)->translatedFormat('l, d F Y') : '-',
|
||||
get: fn () => $this->join_date?->translatedFormat('l, d F Y') ?? '-',
|
||||
);
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ public function joinDateInput(): Attribute
|
||||
public function resignDateFormatted(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->resign_date ? Carbon::parse($this->resign_date)->translatedFormat('l, d F Y') : null,
|
||||
get: fn () => $this->resign_date?->translatedFormat('l, d F Y'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
use App\Models\Employee;
|
||||
use App\Models\User;
|
||||
use App\Models\UserProfile;
|
||||
use App\Services\Concerns\RunsInTransaction;
|
||||
use App\Services\Concerns\SyncsPhotos;
|
||||
use App\Services\Media\MediaService;
|
||||
use App\Support\Media\MediaPresenter;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
@ -17,6 +19,8 @@
|
||||
|
||||
class EmployeeService
|
||||
{
|
||||
use RunsInTransaction, SyncsPhotos;
|
||||
|
||||
public function __construct(
|
||||
private readonly MediaService $mediaService,
|
||||
) {}
|
||||
@ -75,8 +79,8 @@ public function findForEdit(User $user): array
|
||||
|
||||
public function create(array $validated): void
|
||||
{
|
||||
try {
|
||||
DB::transaction(function () use ($validated): void {
|
||||
$this->runInTransaction(
|
||||
function () use ($validated): void {
|
||||
$user = User::create([
|
||||
'email' => $validated['email'],
|
||||
'username' => $validated['username'],
|
||||
@ -104,26 +108,17 @@ public function create(array $validated): void
|
||||
}
|
||||
|
||||
$user->syncRoles([$validated['role']]);
|
||||
});
|
||||
} catch (ValidationException $e) {
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Gagal membuat karyawan: '.$e->getMessage(), [
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
||||
]);
|
||||
}
|
||||
},
|
||||
'Gagal membuat karyawan',
|
||||
);
|
||||
}
|
||||
|
||||
public function update(User $user, array $validated): void
|
||||
{
|
||||
$employee = $user->employee;
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($validated, $user, $employee): void {
|
||||
$this->runInTransaction(
|
||||
function () use ($validated, $user, $employee): void {
|
||||
$user->update([
|
||||
'email' => $validated['email'],
|
||||
'username' => $validated['username'],
|
||||
@ -166,73 +161,65 @@ public function update(User $user, array $validated): void
|
||||
}
|
||||
|
||||
$user->syncRoles([$validated['role']]);
|
||||
});
|
||||
} catch (ValidationException $e) {
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Gagal memperbarui karyawan: '.$e->getMessage(), [
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
||||
]);
|
||||
}
|
||||
},
|
||||
'Gagal memperbarui karyawan',
|
||||
);
|
||||
}
|
||||
|
||||
public function toggleStatus(User $user, array $validated): void
|
||||
{
|
||||
$user->update([
|
||||
'is_active' => $validated['is_active'],
|
||||
]);
|
||||
$this->runInTransaction(
|
||||
function () use ($user, $validated): void {
|
||||
$user->update([
|
||||
'is_active' => $validated['is_active'],
|
||||
]);
|
||||
|
||||
if (! $validated['is_active']) {
|
||||
DB::table('sessions')->where('user_id', $user->id)->delete();
|
||||
}
|
||||
if (! $validated['is_active']) {
|
||||
DB::table('sessions')->where('user_id', $user->id)->delete();
|
||||
}
|
||||
},
|
||||
'Gagal memperbarui status karyawan',
|
||||
);
|
||||
}
|
||||
|
||||
public function resetPassword(User $user): void
|
||||
{
|
||||
$user->update([
|
||||
'password' => config('auth.password_default'),
|
||||
]);
|
||||
$this->runInTransaction(
|
||||
function () use ($user): void {
|
||||
$user->update([
|
||||
'password' => config('auth.password_default'),
|
||||
]);
|
||||
|
||||
DB::table('sessions')->where('user_id', $user->id)->delete();
|
||||
DB::table('sessions')->where('user_id', $user->id)->delete();
|
||||
},
|
||||
'Gagal mereset kata sandi karyawan',
|
||||
);
|
||||
}
|
||||
|
||||
public function delete(User $user): void
|
||||
{
|
||||
try {
|
||||
DB::transaction(function () use ($user): void {
|
||||
$this->runInTransaction(
|
||||
function () use ($user): void {
|
||||
$user->employee?->delete();
|
||||
$user->profile?->delete();
|
||||
$user->delete();
|
||||
});
|
||||
} catch (ValidationException $e) {
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Gagal menghapus karyawan: '.$e->getMessage(), [
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
||||
]);
|
||||
}
|
||||
},
|
||||
'Gagal menghapus karyawan',
|
||||
);
|
||||
}
|
||||
|
||||
private function syncProfilePhoto(UserProfile $profile, array $validated): void
|
||||
{
|
||||
$s3Keys = ! empty($validated['profile_s3_key']) ? [$validated['profile_s3_key']] : null;
|
||||
$removeIds = $validated['remove_profile_photo_ids'] ?? null;
|
||||
|
||||
$this->mediaService->syncCollection(
|
||||
$this->syncPhotos(
|
||||
$profile,
|
||||
'profile_photo',
|
||||
null,
|
||||
$removeIds,
|
||||
1,
|
||||
s3Keys: $s3Keys,
|
||||
[
|
||||
'photos' => $validated['profile_photo'] ?? null,
|
||||
'remove_media_ids' => $validated['remove_profile_photo_ids'] ?? null,
|
||||
's3_keys' => ! empty($validated['profile_s3_key']) ? [$validated['profile_s3_key']] : null,
|
||||
],
|
||||
maxPhotos: 1,
|
||||
required: false,
|
||||
collection: 'profile_photo',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import CreateButton from '@/components/button/CreateButton.vue';
|
||||
import { DataTable } from '@/components/data-table';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
@ -9,12 +11,10 @@ import {
|
||||
} from '@/composables/useDataTableQuery';
|
||||
import { ActiveStatus } from '@/constants/active-status';
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||
import { index, create } from '@/routes/admin/hr/employees';
|
||||
import type { DataTableFilterDef, DataTableSort } from '@/types/data-table';
|
||||
import type { EnumOption, PaginatedEmployees } from '@/types/employee';
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { columns } from './table/columns';
|
||||
import { index, create } from '@/routes/admin/hr/employees';
|
||||
|
||||
const props = defineProps<{
|
||||
employees: PaginatedEmployees;
|
||||
@ -112,37 +112,24 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<Head title="Pegawai" />
|
||||
|
||||
<AdminLayout>
|
||||
<div
|
||||
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="space-y-1">
|
||||
<h2 class="text-2xl font-bold tracking-tight">Pegawai</h2>
|
||||
</div>
|
||||
|
||||
<CreateButton
|
||||
v-if="can('employees.create')"
|
||||
:href="create.url()"
|
||||
/>
|
||||
<CreateButton v-if="can('employees.create')" :href="create.url()" />
|
||||
</div>
|
||||
|
||||
<Card class="min-w-0">
|
||||
<CardContent class="min-w-0">
|
||||
<DataTable
|
||||
v-model:search="search"
|
||||
:columns="columns"
|
||||
:data="employees.data"
|
||||
:pagination="pagination"
|
||||
:pagination-links="employees.links"
|
||||
:sort="currentSort"
|
||||
:filter-defs="filterDefs"
|
||||
:filter-values="filterValues"
|
||||
@sort-change="setSort"
|
||||
@filter-change="setFilter"
|
||||
@filters-reset="resetFilters"
|
||||
/>
|
||||
<DataTable v-model:search="search" :columns="columns" :data="employees.data" :pagination="pagination"
|
||||
:pagination-links="employees.links" :sort="currentSort" :filter-defs="filterDefs"
|
||||
:filter-values="filterValues" @sort-change="setSort" @filter-change="setFilter"
|
||||
@filters-reset="resetFilters" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</AdminLayout>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { Save } from '@lucide/vue';
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { PhoneNumberInput } from '@/components/form/phone-number-input';
|
||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
||||
@ -131,8 +131,8 @@ function submit() {
|
||||
<FieldSet class="grid gap-4 md:grid-cols-3">
|
||||
<Field>
|
||||
<FieldLabel for="email" required>Email</FieldLabel>
|
||||
<Input id="email" v-model="form.email" type="email"
|
||||
placeholder="Masukkan email" :maxlength="FIELD_LIMITS.email" />
|
||||
<Input id="email" v-model="form.email" type="email" placeholder="Masukkan email"
|
||||
:maxlength="FIELD_LIMITS.email" />
|
||||
<FieldError :errors="formErrors(form, 'email')" />
|
||||
</Field>
|
||||
<Field>
|
||||
@ -201,8 +201,7 @@ function submit() {
|
||||
:errors="formErrors(form, 'profile_photo')" class="md:col-span-3" />
|
||||
<Field class="md:col-span-3">
|
||||
<FieldLabel for="address">Alamat</FieldLabel>
|
||||
<Textarea id="address" v-model="form.address"
|
||||
placeholder="Masukkan alamat" rows="3" />
|
||||
<Textarea id="address" v-model="form.address" placeholder="Masukkan alamat" rows="3" />
|
||||
<FieldError :errors="formErrors(form, 'address')" />
|
||||
</Field>
|
||||
</FieldSet>
|
||||
|
||||
@ -2,6 +2,7 @@ import type { ColumnDef } from '@tanstack/vue-table';
|
||||
import { h } from 'vue';
|
||||
import { DataTableColumnHeader } from '@/components/data-table';
|
||||
import MediaThumbnailCell from '@/components/media/MediaThumbnailCell.vue';
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import type { EmployeeListItem } from '@/types/employee';
|
||||
import type { MediaItem } from '@/types/media';
|
||||
@ -9,24 +10,24 @@ import DataTableActions from './data-table-actions.vue';
|
||||
import EmployeeStatusToggle from './employee-status-toggle.vue';
|
||||
|
||||
export const columns: ColumnDef<EmployeeListItem>[] = [
|
||||
{
|
||||
id: 'photo',
|
||||
enableSorting: false,
|
||||
header: () => 'Foto',
|
||||
cell: ({ row }) => {
|
||||
const photoUrl = row.original.profile?.profile_photo_url;
|
||||
const items: MediaItem[] = photoUrl
|
||||
? [{ id: 0, url: photoUrl, thumb_url: photoUrl }]
|
||||
: [];
|
||||
|
||||
return h(MediaThumbnailCell, { items, maxVisible: 1 });
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'profile.full_name',
|
||||
enableSorting: true,
|
||||
header: () => h(DataTableColumnHeader, { title: 'Nama', column: 'full_name' }),
|
||||
cell: ({ row }) => row.original.profile?.full_name ?? '-',
|
||||
cell: ({ row }) => {
|
||||
const photoUrl = row.original.profile?.profile_photo_url;
|
||||
const fullName = row.original.profile?.full_name ?? '-';
|
||||
|
||||
const photoEl = photoUrl
|
||||
? h('div', { class: 'size-8 overflow-hidden rounded-full' }, [
|
||||
h(MediaThumbnailCell, { items: [{ id: 0, url: photoUrl, thumb_url: photoUrl } as MediaItem], maxVisible: 1 }),
|
||||
])
|
||||
: h(Avatar, { class: 'size-8' }, () => [
|
||||
h(AvatarFallback, () => (row.original.username ?? '').slice(0, 2).toUpperCase()),
|
||||
]);
|
||||
|
||||
return h('div', { class: 'flex items-center gap-2' }, [photoEl, h('span', {}, fullName)]);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'account',
|
||||
|
||||
@ -628,13 +628,7 @@
|
||||
Route::prefix('employees')->name('employees.')
|
||||
->middleware('permission:'.Permission::EMPLOYEES_VIEW->value)
|
||||
->group(function () {
|
||||
Route::post('{user}/reset-password', [EmployeeController::class, 'resetPassword'])
|
||||
->middleware('permission:'.Permission::EMPLOYEES_RESET_PASSWORD->value)
|
||||
->name('reset_password');
|
||||
|
||||
Route::patch('{user}/toggle-status', [EmployeeController::class, 'toggleStatus'])
|
||||
->middleware('permission:'.Permission::EMPLOYEES_TOGGLE_STATUS->value)
|
||||
->name('toggle_status');
|
||||
Route::get('/', [EmployeeController::class, 'index'])->name('index');
|
||||
|
||||
Route::get('create', [EmployeeController::class, 'create'])
|
||||
->middleware('permission:'.Permission::EMPLOYEES_CREATE->value)
|
||||
@ -656,7 +650,13 @@
|
||||
->middleware('permission:'.Permission::EMPLOYEES_DELETE->value)
|
||||
->name('destroy');
|
||||
|
||||
Route::get('/', [EmployeeController::class, 'index'])->name('index');
|
||||
Route::post('{user}/reset-password', [EmployeeController::class, 'resetPassword'])
|
||||
->middleware('permission:'.Permission::EMPLOYEES_RESET_PASSWORD->value)
|
||||
->name('reset_password');
|
||||
|
||||
Route::patch('{user}/toggle-status', [EmployeeController::class, 'toggleStatus'])
|
||||
->middleware('permission:'.Permission::EMPLOYEES_TOGGLE_STATUS->value)
|
||||
->name('toggle_status');
|
||||
});
|
||||
|
||||
Route::prefix('leave-requests')->name('leave_requests.')
|
||||
|
||||
@ -6,11 +6,15 @@
|
||||
use App\Models\UserProfile;
|
||||
use Database\Seeders\RolePermissionSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->seed(RolePermissionSeeder::class);
|
||||
$disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
|
||||
Storage::fake($disk);
|
||||
Storage::fake('public');
|
||||
});
|
||||
|
||||
// ─── Helper ───────────────────────────────────────────────
|
||||
@ -41,7 +45,7 @@ function createEmployeeWithProfile(): User
|
||||
'gender' => fake()->randomElement(['male', 'female']),
|
||||
]);
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
$user->assignRole('marketing');
|
||||
$user->assignRole('marketing-offline');
|
||||
|
||||
return $user;
|
||||
}
|
||||
@ -70,7 +74,7 @@ function validEmployeePayload(): array
|
||||
'gender' => 'male',
|
||||
'birth_date' => '1995-01-15',
|
||||
'address' => 'Jl. Merdeka No. 1',
|
||||
'role' => 'marketing',
|
||||
'role' => 'marketing-offline',
|
||||
'join_date' => '2024-01-01',
|
||||
'employment_status' => 'full_time',
|
||||
'base_salary' => 5000000,
|
||||
@ -129,7 +133,7 @@ function validEmployeePayload(): array
|
||||
createEmployeeWithProfile();
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('admin.hr.employees.index', ['role' => 'marketing']))
|
||||
->get(route('admin.hr.employees.index', ['role' => 'marketing-offline']))
|
||||
->assertOk();
|
||||
});
|
||||
|
||||
@ -347,6 +351,24 @@ function validEmployeePayload(): array
|
||||
expect($newUser->profile->full_name)->toBe('Pegawai Baru');
|
||||
expect($newUser->employee->base_salary)->toBe(5000000);
|
||||
});
|
||||
|
||||
test('creating employee with profile photo s3 key links the photo', function () {
|
||||
$user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_CREATE);
|
||||
|
||||
$disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
|
||||
$imageContent = \Illuminate\Http\UploadedFile::fake()->image('avatar.jpg')->get();
|
||||
Storage::disk($disk)->put('fake-s3-key.jpg', $imageContent);
|
||||
|
||||
$payload = validEmployeePayload();
|
||||
$payload['profile_s3_key'] = 'fake-s3-key.jpg';
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('admin.hr.employees.store'), $payload);
|
||||
|
||||
$newUser = User::where('email', 'pegawai@example.com')->first();
|
||||
expect($newUser)->not->toBeNull();
|
||||
expect($newUser->profile->getMedia('profile_photo')->count())->toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Edit ─────────────────────────────────────────────────
|
||||
@ -430,6 +452,34 @@ function validEmployeePayload(): array
|
||||
->put(route('admin.hr.employees.update', $employee), $payload)
|
||||
->assertRedirect(route('admin.hr.employees.index'));
|
||||
});
|
||||
|
||||
test('updating employee can upload new photo and delete existing photo', function () {
|
||||
$user = createEmployeeUserWithPermission(PermissionEnum::EMPLOYEES_VIEW, PermissionEnum::EMPLOYEES_UPDATE);
|
||||
|
||||
$employee = createEmployeeWithProfile();
|
||||
|
||||
$imageContent = \Illuminate\Http\UploadedFile::fake()->image('initial.jpg')->get();
|
||||
Storage::disk('public')->put('initial.jpg', $imageContent);
|
||||
$employee->profile->addMediaFromDisk('initial.jpg', 'public')->toMediaCollection('profile_photo');
|
||||
expect($employee->profile->fresh()->getMedia('profile_photo')->count())->toBe(1);
|
||||
$mediaId = $employee->profile->fresh()->getFirstMedia('profile_photo')->id;
|
||||
|
||||
$disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
|
||||
$imageContent2 = \Illuminate\Http\UploadedFile::fake()->image('updated.jpg')->get();
|
||||
Storage::disk($disk)->put('updated.jpg', $imageContent2);
|
||||
$payload = validEmployeePayload();
|
||||
$payload['email'] = $employee->email;
|
||||
$payload['username'] = $employee->username;
|
||||
$payload['profile_s3_key'] = 'updated.jpg';
|
||||
$payload['remove_profile_photo_ids'] = [$mediaId];
|
||||
|
||||
$this->actingAs($user)
|
||||
->put(route('admin.hr.employees.update', $employee), $payload);
|
||||
|
||||
$freshProfile = $employee->fresh()->profile;
|
||||
expect($freshProfile->getMedia('profile_photo')->count())->toBe(1);
|
||||
expect($freshProfile->getFirstMedia('profile_photo')->id)->not->toBe($mediaId);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Toggle Status ────────────────────────────────────────
|
||||
@ -565,3 +615,36 @@ function validEmployeePayload(): array
|
||||
$this->assertSoftDeleted('employees', ['id' => $employeeId]);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Employee Model ───────────────────────────────────────
|
||||
|
||||
describe('Employee Model Attributes', function () {
|
||||
test('join_date_formatted returns translated formatted date or fallback', function () {
|
||||
$employee = Employee::factory()->create(['join_date' => '2024-01-15']);
|
||||
expect($employee->join_date_formatted)->toBe('Senin, 15 Januari 2024');
|
||||
|
||||
$employeeNull = Employee::factory()->make(['join_date' => null]);
|
||||
expect($employeeNull->join_date_formatted)->toBe('-');
|
||||
});
|
||||
|
||||
test('resign_date_formatted returns translated formatted date or null', function () {
|
||||
$employee = Employee::factory()->create(['resign_date' => '2024-06-20']);
|
||||
expect($employee->resign_date_formatted)->toBe('Kamis, 20 Juni 2024');
|
||||
|
||||
$employeeNull = Employee::factory()->make(['resign_date' => null]);
|
||||
expect($employeeNull->resign_date_formatted)->toBeNull();
|
||||
});
|
||||
|
||||
test('join_date_input returns date formatted as Y-m-d', function () {
|
||||
$employee = Employee::factory()->create(['join_date' => '2024-01-15']);
|
||||
expect($employee->join_date_input)->toBe('2024-01-15');
|
||||
});
|
||||
|
||||
test('base_salary_formatted returns formatted rupiah or fallback', function () {
|
||||
$employee = Employee::factory()->create(['base_salary' => 5000000]);
|
||||
expect($employee->base_salary_formatted)->toBe('Rp 5.000.000');
|
||||
|
||||
$employeeNull = Employee::factory()->make(['base_salary' => null]);
|
||||
expect($employeeNull->base_salary_formatted)->toBe('-');
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user