store/app/Services/Hr/EmployeeService.php

340 lines
12 KiB
PHP

<?php
namespace App\Services\Hr;
use App\Enums\Role;
use App\Models\Employee;
use App\Models\User;
use App\Models\UserProfile;
use App\Services\Concerns\CachesQuery;
use App\Services\Concerns\RunsInTransaction;
use App\Services\Concerns\SyncsPhotos;
use App\Services\Media\MediaService;
use App\Services\System\PushNotificationService;
use App\Support\Media\MediaPresenter;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class EmployeeService
{
use CachesQuery, RunsInTransaction, SyncsPhotos;
public function __construct(
private readonly MediaService $mediaService,
private readonly PushNotificationService $pushNotificationService,
) {}
public function paginateForIndex(
array $tableQuery,
User $authUser,
string $role = '',
string $gender = '',
string $employmentStatus = '',
string $isActive = '',
): LengthAwarePaginator {
$query = User::query()
->with(['profile', 'employee', 'roles'])
->whereDoesntHave('roles', fn ($query) => $query->where('name', Role::DEVELOPER->value))
->when(
$authUser->hasRole(Role::ADMIN_BAHAN_BAKU->value),
fn ($query) => $query->whereHas('roles', fn ($query) => $query->where('name', Role::ADMIN_BAHAN_BAKU->value))
)
->when(
$authUser->hasAnyRole([Role::DIREKTUR->value, Role::ADMIN_TOKO->value]),
fn ($query) => $query
->whereDoesntHave('roles', fn ($query) => $query->where('name', Role::OWNER->value))
->whereDoesntHave('roles', fn ($query) => $query->where('name', Role::ADMIN_BAHAN_BAKU->value))
)
->when($tableQuery['search'] !== '', function ($query) use ($tableQuery): void {
$search = $tableQuery['search'];
$query->where(function ($query) use ($search): void {
$query->where('email', 'like', "%{$search}%")
->orWhere('username', 'like', "%{$search}%")
->orWhereHas('profile', function ($query) use ($search): void {
$query->where('full_name', 'like', "%{$search}%")
->orWhere('phone_number', 'like', "%{$search}%");
});
});
})
->when($role !== '', fn ($query) => $query->whereHas('roles', fn ($query) => $query->where('name', $role)))
->when($gender !== '', fn ($query) => $query->whereHas('profile', fn ($query) => $query->where('gender', $gender)))
->when($employmentStatus !== '', function ($query) use ($employmentStatus): void {
$query->whereHas('employee', fn ($query) => $query->where('employment_status', $employmentStatus));
})
->when($isActive !== '', fn ($query) => $query->where('is_active', $isActive === '1'));
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
return $query
->paginate(25)
->withQueryString();
}
public function assignableRoleOptions(): array
{
return $this->cacheRemember('system:assignable_roles', 86400, function (): array {
return Role::assignableSelectOptions();
});
}
public function findForEdit(User $user): array
{
$user->load(['profile', 'employee', 'roles']);
return [
'employee' => $user,
'profilePhoto' => $user->profile
? MediaPresenter::first($user->profile, 'profile_photo')
: null,
];
}
public function create(array $validated, User $user): void
{
$this->runInTransaction(
function () use ($validated): User {
$user = User::create([
'email' => $validated['email'],
'username' => $validated['username'],
'password' => Hash::make(config('auth.password_default')),
]);
$profile = UserProfile::create([
'user_id' => $user->id,
'full_name' => $validated['full_name'],
'phone_number' => $validated['phone_number'],
'gender' => $validated['gender'],
'birth_date' => $validated['birth_date'],
'address' => $validated['address'],
]);
$this->syncProfilePhoto($profile, $validated);
if ($validated['role'] !== Role::OWNER->value) {
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']]);
return $user;
},
'Gagal membuat karyawan',
);
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('hr:employees:*');
$this->notifyOwner(
'Tambah Pegawai',
"Pegawai '{$validated['full_name']}' telah ditambahkan oleh {$user->profile?->full_name}.",
route('admin.hr.employees.index'),
);
}
public function update(User $user, array $validated, User $authUser): void
{
$employee = $user->employee;
$this->runInTransaction(
function () use ($validated, $user, $employee): void {
$user->update([
'email' => $validated['email'],
'username' => $validated['username'],
]);
$profile = $user->profile()->updateOrCreate(
['user_id' => $user->id],
[
'full_name' => $validated['full_name'],
'phone_number' => $validated['phone_number'],
'gender' => $validated['gender'],
'birth_date' => $validated['birth_date'],
'address' => $validated['address'],
],
);
$this->syncProfilePhoto($profile, $validated);
$hasEmployee = ! empty($validated['join_date']) && ! empty($validated['employment_status']) && ! empty($validated['base_salary']);
if ($hasEmployee) {
if ($employee) {
$employee->update([
'join_date' => $validated['join_date'],
'employment_status' => $validated['employment_status'],
'base_salary' => $validated['base_salary'],
]);
} 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']]);
},
'Gagal memperbarui karyawan',
);
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('hr:employees:*');
$this->notifyOwner(
'Ubah Pegawai',
"Pegawai '{$user->profile?->full_name}' telah diperbarui oleh {$authUser->profile?->full_name}.",
route('admin.hr.employees.index'),
);
}
public function toggleStatus(User $user, array $validated, User $authUser): void
{
$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();
}
},
'Gagal memperbarui status karyawan',
);
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('hr:employees:*');
$statusLabel = $validated['is_active'] ? 'aktif' : 'nonaktif';
$this->notifyOwner(
'Ubah Status Pegawai',
"Status pegawai '{$user->profile?->full_name}' telah diubah menjadi {$statusLabel} oleh {$authUser->profile?->full_name}.",
route('admin.hr.employees.index'),
);
}
public function resetPassword(User $user, User $authUser): void
{
$this->runInTransaction(
function () use ($user): void {
$user->update([
'password' => config('auth.password_default'),
]);
DB::table('sessions')->where('user_id', $user->id)->delete();
},
'Gagal mereset kata sandi karyawan',
);
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('hr:employees:*');
$this->notifyOwner(
'Reset Kata Sandi Pegawai',
"Kata sandi pegawai '{$user->profile?->full_name}' telah direset oleh {$authUser->profile?->full_name}.",
route('admin.hr.employees.index'),
);
}
public function delete(User $user, User $authUser): void
{
$name = $user->profile?->full_name;
$this->runInTransaction(
function () use ($user): void {
$user->employee?->delete();
$user->profile?->delete();
$user->delete();
},
'Gagal menghapus karyawan',
);
$this->cacheForget('system:assignable_roles');
$this->cacheForgetByPattern('hr:employees:*');
$this->notifyOwner(
'Hapus Pegawai',
"Pegawai '{$name}' telah dihapus oleh {$authUser->profile?->full_name}.",
route('admin.hr.employees.index'),
);
}
private function notifyOwner(string $typeLabel, string $body, string $url): void
{
$this->pushNotificationService->sendToRoles(
"📦 {$typeLabel}",
$body,
['owner', 'developer'],
$url,
);
}
private function syncProfilePhoto(UserProfile $profile, array $validated): void
{
$this->syncPhotos(
$profile,
[
'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',
);
}
private function applySorting(Builder $query, string $sort, string $direction): void
{
$employeeSorts = [
'join_date',
'base_salary',
'employment_status',
];
if (in_array($sort, $employeeSorts, true)) {
$query->orderBy(
Employee::select($sort)
->whereColumn('employees.user_id', 'users.id')
->limit(1),
$direction
);
return;
}
if ($sort === 'full_name') {
$query->orderBy(
UserProfile::select('full_name')
->whereColumn('user_profiles.user_id', 'users.id')
->limit(1),
$direction
);
return;
}
if ($sort === 'email') {
$query->orderBy('email', $direction);
return;
}
$query->latest();
}
}