Files
2026-07-27 16:24:09 +07:00

282 lines
8.9 KiB
PHP

<?php
namespace App\Services\User;
use App\Enums\UserAccessLevel;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class UserService
{
public function list(array $filters = [], ?User $actor = null, ?int $tenantId = null)
{
return User::query()
->with(['profilePhoto', 'userProfile', 'userTenants.tenant', 'userMenuGroups.menuGroup', 'userMenuGroups.tenant'])
->when(
! empty($filters['search']),
fn ($q) => $q->where(function ($x) use ($filters) {
$x->where('name', 'ILIKE', "%{$filters['search']}%")
->orWhere('username', 'ILIKE', "%{$filters['search']}%")
->orWhere('email', 'ILIKE', "%{$filters['search']}%")
->orWhere('whatsapp_number', 'ILIKE', "%{$filters['search']}%");
})
)
->when(
isset($filters['status']) && $filters['status'] !== '',
fn ($q) => $q->where('status', $filters['status'])
)
->when(
! empty($filters['access_level']),
fn ($q) => $q->where('access_level', $filters['access_level'])
)
->when(
$actor && ! $actor->isMasterAdmin(),
fn ($q) => $q->whereHas(
'userTenants',
fn ($tenantQuery) => $tenantQuery->where('tenant_id', $tenantId)
)->where('is_master', false)
)
->orderBy(
$filters['sort_by'] ?? 'id',
$filters['sort_direction'] ?? 'desc'
)
->paginate($filters['per_page'] ?? 10);
}
public function create(array $payload, ?User $actor = null): User
{
$this->guardPrivilegeChange($payload, $actor);
$this->guardTenantPayload($payload, $actor);
if ($actor && ! $actor->isMasterAdmin() && empty($payload['user_tenants'])) {
throw ValidationException::withMessages([
'user_tenants' => 'Tenant Owner wajib membuat user pada tenant aktif.',
]);
}
return DB::transaction(function () use ($payload) {
$baseData = Arr::only($payload, [
'name',
'username',
'email',
'whatsapp_number',
'profile_photo_file_id',
'password',
'is_master',
'access_level',
'status',
]);
$user = User::create($baseData);
$this->syncProfile($user, $payload['user_profile'] ?? null);
$this->syncTenants($user, $payload['user_tenants'] ?? []);
$this->syncMenuGroups($user, $payload['user_menu_groups'] ?? []);
return $user->fresh([
'userProfile',
'profilePhoto',
'userTenants.tenant',
'userMenuGroups.menuGroup',
'userMenuGroups.tenant',
]);
});
}
public function show(User $user): User
{
return $user->load([
'userProfile',
'profilePhoto',
'userTenants.tenant',
'userMenuGroups.menuGroup',
'userMenuGroups.tenant',
]);
}
public function update(User $user, array $payload, ?User $actor = null): User
{
$this->guardPrivilegeChange($payload, $actor, $user);
$this->guardTenantPayload($payload, $actor);
return DB::transaction(function () use ($user, $payload) {
$baseData = Arr::only($payload, [
'name',
'username',
'email',
'whatsapp_number',
'profile_photo_file_id',
'is_master',
'access_level',
'status',
]);
if (! empty($payload['password'])) {
$baseData['password'] = $payload['password'];
}
if (! empty($baseData)) {
$user->update($baseData);
}
if (array_key_exists('user_profile', $payload)) {
$this->syncProfile($user, $payload['user_profile']);
}
if (array_key_exists('user_tenants', $payload)) {
$this->syncTenants($user, $payload['user_tenants'] ?? []);
}
if (array_key_exists('user_menu_groups', $payload)) {
$this->syncMenuGroups($user, $payload['user_menu_groups'] ?? []);
}
return $user->fresh([
'userProfile',
'profilePhoto',
'userTenants.tenant',
'userMenuGroups.menuGroup',
'userMenuGroups.tenant',
]);
});
}
public function delete(User $user): void
{
DB::transaction(function () use ($user) {
$user->userProfile()->delete();
$user->userTenants()->delete();
$user->userMenuGroups()->delete();
$user->delete();
});
}
protected function syncProfile(User $user, ?array $profile): void
{
if ($profile === null) {
return;
}
$data = Arr::only($profile, [
'nik',
'address',
'provinsi_id',
'kabupaten_id',
'kecamatan_id',
'desa_id',
]);
$user->userProfile()->updateOrCreate(
['user_id' => $user->id],
$data
);
}
protected function syncTenants(User $user, array $rows): void
{
$normalized = collect($rows)
->map(fn ($row) => [
'tenant_id' => $row['tenant_id'] ?? null,
'is_default' => (bool) ($row['is_default'] ?? false),
])
->filter(fn ($row) => ! empty($row['tenant_id']))
->unique('tenant_id')
->values()
->all();
$defaultSet = false;
foreach ($normalized as $index => $row) {
if ($row['is_default'] && ! $defaultSet) {
$defaultSet = true;
} else {
$normalized[$index]['is_default'] = false;
}
}
if (! $defaultSet && count($normalized) > 0) {
$normalized[0]['is_default'] = true;
}
$user->userTenants()->delete();
if (! empty($normalized)) {
$user->userTenants()->createMany($normalized);
}
}
protected function syncMenuGroups(User $user, array $rows): void
{
$normalized = collect($rows)
->map(fn ($row) => [
'menu_group_id' => $row['menu_group_id'] ?? null,
'tenant_id' => $row['tenant_id'] ?? null,
])
->filter(fn ($row) => ! empty($row['menu_group_id']))
->unique(fn ($row) => ($row['menu_group_id'] ?? '').'|'.($row['tenant_id'] ?? ''))
->values()
->all();
$user->userMenuGroups()->delete();
if (! empty($normalized)) {
$user->userMenuGroups()->createMany($normalized);
}
}
protected function guardPrivilegeChange(array &$payload, ?User $actor, ?User $target = null): void
{
if (! $actor) {
unset($payload['is_master'], $payload['access_level']);
return;
}
if ($actor->isMasterAdmin()) {
if (($payload['access_level'] ?? null) === UserAccessLevel::MASTER_ADMIN->value) {
$payload['is_master'] = true;
} elseif (array_key_exists('access_level', $payload)) {
$payload['is_master'] = false;
}
return;
}
if ($target?->isMasterAdmin()) {
throw ValidationException::withMessages([
'user' => 'Master Admin hanya dapat diubah oleh Master Admin.',
]);
}
if (($payload['is_master'] ?? false)
|| ($payload['access_level'] ?? null) === UserAccessLevel::MASTER_ADMIN->value
|| ($payload['access_level'] ?? null) === UserAccessLevel::TENANT_OWNER->value) {
throw ValidationException::withMessages([
'access_level' => 'Anda tidak dapat memberikan level akses ini.',
]);
}
unset($payload['is_master']);
}
protected function guardTenantPayload(array $payload, ?User $actor): void
{
if (! $actor || $actor->isMasterAdmin()) {
return;
}
$tenantId = request()->attributes->get('tenant_id');
foreach (['user_tenants', 'user_menu_groups'] as $key) {
foreach ($payload[$key] ?? [] as $row) {
if ((int) ($row['tenant_id'] ?? 0) !== (int) $tenantId) {
throw ValidationException::withMessages([
$key => 'Assignment hanya boleh dibuat untuk tenant yang sedang aktif.',
]);
}
}
}
}
}