big update base struktur tahap 1
This commit is contained in:
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Menu;
|
||||
|
||||
use App\Models\MenuGroup;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class MenuGroupService
|
||||
{
|
||||
public function list(array $filters = [], ?User $actor = null, ?int $tenantId = null)
|
||||
{
|
||||
return MenuGroup::query()
|
||||
->with('tenant')
|
||||
->when(
|
||||
$actor && ! $actor->isMasterAdmin(),
|
||||
fn ($q) => $q->where('tenant_id', $tenantId)
|
||||
)
|
||||
->when(
|
||||
! empty($filters['search']),
|
||||
fn ($q) => $q->where(function ($x) use ($filters) {
|
||||
$x->where('name', 'ILIKE', "%{$filters['search']}%")
|
||||
->orWhere('description', 'ILIKE', "%{$filters['search']}%");
|
||||
})
|
||||
)
|
||||
->when(
|
||||
array_key_exists('is_active', $filters) && $filters['is_active'] !== null && $filters['is_active'] !== '',
|
||||
fn ($q) => $q->where('is_active', filter_var($filters['is_active'], FILTER_VALIDATE_BOOLEAN))
|
||||
)
|
||||
->orderBy(
|
||||
$filters['sort_by'] ?? 'id',
|
||||
$filters['sort_direction'] ?? 'desc'
|
||||
)
|
||||
->paginate($filters['per_page'] ?? 10);
|
||||
}
|
||||
|
||||
public function create(array $data, User $actor, ?int $tenantId): MenuGroup
|
||||
{
|
||||
if (! array_key_exists('is_active', $data) || $data['is_active'] === null) {
|
||||
$data['is_active'] = true;
|
||||
}
|
||||
|
||||
$scopeTenantId = $actor->isMasterAdmin()
|
||||
? ($data['tenant_id'] ?? null)
|
||||
: $tenantId;
|
||||
|
||||
if (! $actor->isMasterAdmin() && ! $scopeTenantId) {
|
||||
throw ValidationException::withMessages([
|
||||
'tenant_id' => 'Tenant aktif wajib tersedia untuk membuat Menu Group.',
|
||||
]);
|
||||
}
|
||||
|
||||
$data['tenant_id'] = $scopeTenantId;
|
||||
$data['is_system'] = $actor->isMasterAdmin() && ! $scopeTenantId;
|
||||
|
||||
return MenuGroup::create($data)->load('tenant');
|
||||
}
|
||||
|
||||
public function update(
|
||||
MenuGroup $menuGroup,
|
||||
array $data,
|
||||
User $actor,
|
||||
?int $tenantId
|
||||
): MenuGroup {
|
||||
if (array_key_exists('is_active', $data) && $data['is_active'] === null) {
|
||||
unset($data['is_active']);
|
||||
}
|
||||
|
||||
if ($actor->isMasterAdmin()) {
|
||||
if (array_key_exists('tenant_id', $data)) {
|
||||
$data['is_system'] = ! $data['tenant_id'];
|
||||
}
|
||||
} else {
|
||||
unset($data['tenant_id'], $data['is_system']);
|
||||
if ((int) $menuGroup->tenant_id !== (int) $tenantId) {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
|
||||
$menuGroup->update($data);
|
||||
|
||||
return $menuGroup->fresh('tenant');
|
||||
}
|
||||
|
||||
public function delete(MenuGroup $menuGroup): bool
|
||||
{
|
||||
$hasMenuMappings = $menuGroup->menus()->exists();
|
||||
$hasUserMappings = $menuGroup->userMenuGroups()->exists();
|
||||
|
||||
if ($hasMenuMappings || $hasUserMappings) {
|
||||
throw ValidationException::withMessages([
|
||||
'menu_group' => 'Menu Group tidak dapat dihapus karena masih memiliki relasi menu/user.',
|
||||
]);
|
||||
}
|
||||
|
||||
return $menuGroup->delete();
|
||||
}
|
||||
|
||||
public function listMenus(MenuGroup $menuGroup): array
|
||||
{
|
||||
$rows = DB::table('menu_lists as m')
|
||||
->leftJoin('menu_group_menus as mgm', function ($join) use ($menuGroup) {
|
||||
$join->on('mgm.menu_id', '=', 'm.id')
|
||||
->where('mgm.menu_group_id', '=', $menuGroup->id);
|
||||
})
|
||||
->where('m.is_active', true)
|
||||
->select(
|
||||
'm.id',
|
||||
'm.parent_id',
|
||||
'm.name',
|
||||
'm.slug',
|
||||
'm.url',
|
||||
'm.icon',
|
||||
'm.component',
|
||||
'm.sort_order',
|
||||
DB::raw('COALESCE(mgm.can_view, false) as can_view'),
|
||||
DB::raw('COALESCE(mgm.can_create, false) as can_create'),
|
||||
DB::raw('COALESCE(mgm.can_update, false) as can_update'),
|
||||
DB::raw('COALESCE(mgm.can_delete, false) as can_delete'),
|
||||
DB::raw('COALESCE(mgm.can_approve, false) as can_approve'),
|
||||
DB::raw('COALESCE(mgm.can_export, false) as can_export')
|
||||
)
|
||||
->orderBy('m.sort_order')
|
||||
->orderBy('m.id')
|
||||
->get();
|
||||
|
||||
return $rows->map(function ($row) {
|
||||
return [
|
||||
'menu_id' => (int) $row->id,
|
||||
'parent_id' => $row->parent_id ? (int) $row->parent_id : null,
|
||||
'name' => $row->name,
|
||||
'slug' => $row->slug,
|
||||
'url' => $row->url,
|
||||
'icon' => $row->icon,
|
||||
'component' => $row->component,
|
||||
'sort_order' => (int) $row->sort_order,
|
||||
'permissions' => [
|
||||
'can_view' => (bool) $row->can_view,
|
||||
'can_create' => (bool) $row->can_create,
|
||||
'can_update' => (bool) $row->can_update,
|
||||
'can_delete' => (bool) $row->can_delete,
|
||||
'can_approve' => (bool) $row->can_approve,
|
||||
'can_export' => (bool) $row->can_export,
|
||||
],
|
||||
'available_permissions' => [
|
||||
'can_view' => true,
|
||||
'can_create' => true,
|
||||
'can_update' => true,
|
||||
'can_delete' => true,
|
||||
'can_approve' => true,
|
||||
'can_export' => true,
|
||||
],
|
||||
];
|
||||
})->all();
|
||||
}
|
||||
|
||||
public function syncMenus(MenuGroup $menuGroup, array $menus, ?User $actor = null): void
|
||||
{
|
||||
if ($actor && ! $actor->isMasterAdmin()) {
|
||||
$allowed = $this->getAllowedMenuPermissionsForUser(
|
||||
$actor,
|
||||
request()->attributes->get('tenant_id')
|
||||
);
|
||||
|
||||
foreach ($menus as $menu) {
|
||||
$actorPermissions = $allowed[(int) $menu['menu_id']] ?? null;
|
||||
|
||||
if (! $actorPermissions) {
|
||||
throw ValidationException::withMessages([
|
||||
'menus' => 'Terdapat menu di luar hak akses user login.',
|
||||
]);
|
||||
}
|
||||
|
||||
foreach (['view', 'create', 'update', 'delete', 'approve', 'export'] as $permission) {
|
||||
if (($menu["can_$permission"] ?? false) && ! $actorPermissions["can_$permission"]) {
|
||||
throw ValidationException::withMessages([
|
||||
'menus' => "Permission can_$permission tidak boleh melebihi akses Anda.",
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$syncData = [];
|
||||
|
||||
foreach ($menus as $menu) {
|
||||
$syncData[$menu['menu_id']] = [
|
||||
'can_view' => (bool) ($menu['can_view'] ?? false),
|
||||
'can_create' => (bool) ($menu['can_create'] ?? false),
|
||||
'can_update' => (bool) ($menu['can_update'] ?? false),
|
||||
'can_delete' => (bool) ($menu['can_delete'] ?? false),
|
||||
'can_approve' => (bool) ($menu['can_approve'] ?? false),
|
||||
'can_export' => (bool) ($menu['can_export'] ?? false),
|
||||
'updated_at' => now(),
|
||||
'created_at' => now(),
|
||||
];
|
||||
}
|
||||
|
||||
$menuGroup->menus()->sync($syncData);
|
||||
}
|
||||
|
||||
public function listAvailableMenusForGroup(MenuGroup $menuGroup, User $actor): array
|
||||
{
|
||||
if ($actor->isMasterAdmin()) {
|
||||
return $this->listMenus($menuGroup);
|
||||
}
|
||||
|
||||
$allowedPermissions = $this->getAllowedMenuPermissionsForUser(
|
||||
$actor,
|
||||
request()->attributes->get('tenant_id')
|
||||
);
|
||||
$allowedMenuIds = array_keys($allowedPermissions);
|
||||
|
||||
$rows = DB::table('menu_lists as m')
|
||||
->leftJoin('menu_group_menus as mgm', function ($join) use ($menuGroup) {
|
||||
$join->on('mgm.menu_id', '=', 'm.id')
|
||||
->where('mgm.menu_group_id', '=', $menuGroup->id);
|
||||
})
|
||||
->where('m.is_active', true)
|
||||
->whereIn('m.id', $allowedMenuIds)
|
||||
->select(
|
||||
'm.id',
|
||||
'm.parent_id',
|
||||
'm.name',
|
||||
'm.sort_order',
|
||||
DB::raw('COALESCE(mgm.can_view, false) as can_view'),
|
||||
DB::raw('COALESCE(mgm.can_create, false) as can_create'),
|
||||
DB::raw('COALESCE(mgm.can_update, false) as can_update'),
|
||||
DB::raw('COALESCE(mgm.can_delete, false) as can_delete'),
|
||||
DB::raw('COALESCE(mgm.can_approve, false) as can_approve'),
|
||||
DB::raw('COALESCE(mgm.can_export, false) as can_export')
|
||||
)
|
||||
->orderBy('m.sort_order')
|
||||
->orderBy('m.id')
|
||||
->get();
|
||||
|
||||
return $rows->map(function ($row) use ($allowedPermissions) {
|
||||
$available = $allowedPermissions[(int) $row->id];
|
||||
|
||||
return [
|
||||
'menu_id' => (int) $row->id,
|
||||
'parent_id' => $row->parent_id ? (int) $row->parent_id : null,
|
||||
'name' => $row->name,
|
||||
'sort_order' => (int) $row->sort_order,
|
||||
'permissions' => [
|
||||
'can_view' => (bool) $row->can_view,
|
||||
'can_create' => (bool) $row->can_create,
|
||||
'can_update' => (bool) $row->can_update,
|
||||
'can_delete' => (bool) $row->can_delete,
|
||||
'can_approve' => (bool) $row->can_approve,
|
||||
'can_export' => (bool) $row->can_export,
|
||||
],
|
||||
'available_permissions' => $available,
|
||||
];
|
||||
})->all();
|
||||
}
|
||||
|
||||
public function getAllowedMenuIdsForUser(User $user, ?int $tenantId = null): array
|
||||
{
|
||||
if ($user->isMasterAdmin()) {
|
||||
return DB::table('menu_lists')
|
||||
->where('is_active', true)
|
||||
->pluck('id')
|
||||
->map(fn ($id) => (int) $id)
|
||||
->all();
|
||||
}
|
||||
|
||||
return DB::table('menu_group_menus as mgm')
|
||||
->join('user_menu_groups as umg', 'umg.menu_group_id', '=', 'mgm.menu_group_id')
|
||||
->join('menu_lists as m', 'm.id', '=', 'mgm.menu_id')
|
||||
->where('umg.user_id', $user->id)
|
||||
->where('umg.tenant_id', $tenantId)
|
||||
->where('m.is_active', true)
|
||||
->where(function ($q) {
|
||||
$q->where('mgm.can_view', true)
|
||||
->orWhere('mgm.can_create', true)
|
||||
->orWhere('mgm.can_update', true)
|
||||
->orWhere('mgm.can_delete', true)
|
||||
->orWhere('mgm.can_approve', true)
|
||||
->orWhere('mgm.can_export', true);
|
||||
})
|
||||
->distinct()
|
||||
->pluck('mgm.menu_id')
|
||||
->map(fn ($id) => (int) $id)
|
||||
->all();
|
||||
}
|
||||
|
||||
public function getAllowedMenuPermissionsForUser(User $user, ?int $tenantId): array
|
||||
{
|
||||
return DB::table('menu_group_menus as mgm')
|
||||
->join('user_menu_groups as umg', 'umg.menu_group_id', '=', 'mgm.menu_group_id')
|
||||
->join('menu_groups as mg', 'mg.id', '=', 'mgm.menu_group_id')
|
||||
->join('menu_lists as m', 'm.id', '=', 'mgm.menu_id')
|
||||
->where('umg.user_id', $user->id)
|
||||
->where('umg.tenant_id', $tenantId)
|
||||
->where('mg.is_active', true)
|
||||
->where('m.is_active', true)
|
||||
->groupBy('mgm.menu_id')
|
||||
->select(
|
||||
'mgm.menu_id',
|
||||
DB::raw('MAX(CASE WHEN mgm.can_view THEN 1 ELSE 0 END) as can_view'),
|
||||
DB::raw('MAX(CASE WHEN mgm.can_create THEN 1 ELSE 0 END) as can_create'),
|
||||
DB::raw('MAX(CASE WHEN mgm.can_update THEN 1 ELSE 0 END) as can_update'),
|
||||
DB::raw('MAX(CASE WHEN mgm.can_delete THEN 1 ELSE 0 END) as can_delete'),
|
||||
DB::raw('MAX(CASE WHEN mgm.can_approve THEN 1 ELSE 0 END) as can_approve'),
|
||||
DB::raw('MAX(CASE WHEN mgm.can_export THEN 1 ELSE 0 END) as can_export')
|
||||
)
|
||||
->get()
|
||||
->mapWithKeys(fn ($row) => [(int) $row->menu_id => [
|
||||
'can_view' => (bool) $row->can_view,
|
||||
'can_create' => (bool) $row->can_create,
|
||||
'can_update' => (bool) $row->can_update,
|
||||
'can_delete' => (bool) $row->can_delete,
|
||||
'can_approve' => (bool) $row->can_approve,
|
||||
'can_export' => (bool) $row->can_export,
|
||||
]])
|
||||
->all();
|
||||
}
|
||||
|
||||
public function listUserAssignments(User $user, ?int $tenantId = null): array
|
||||
{
|
||||
return DB::table('user_menu_groups as umg')
|
||||
->join('menu_groups as mg', 'mg.id', '=', 'umg.menu_group_id')
|
||||
->leftJoin('tenants as t', 't.id', '=', 'umg.tenant_id')
|
||||
->where('umg.user_id', $user->id)
|
||||
->when($tenantId, fn ($q) => $q->where('umg.tenant_id', $tenantId))
|
||||
->select(
|
||||
'umg.menu_group_id',
|
||||
'mg.name as menu_group_name',
|
||||
'mg.is_active as menu_group_is_active',
|
||||
'umg.tenant_id',
|
||||
't.tenant_name as tenant_name',
|
||||
'umg.created_at',
|
||||
'umg.updated_at'
|
||||
)
|
||||
->orderBy('umg.menu_group_id')
|
||||
->orderBy('umg.tenant_id')
|
||||
->get()
|
||||
->map(function ($row) {
|
||||
return [
|
||||
'menu_group_id' => (int) $row->menu_group_id,
|
||||
'menu_group_name' => $row->menu_group_name,
|
||||
'menu_group_is_active' => (bool) $row->menu_group_is_active,
|
||||
'tenant_id' => $row->tenant_id ? (int) $row->tenant_id : null,
|
||||
'tenant_name' => $row->tenant_name,
|
||||
'created_at' => $row->created_at,
|
||||
'updated_at' => $row->updated_at,
|
||||
];
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
public function syncUserAssignments(User $user, array $assignments, ?int $tenantId = null): void
|
||||
{
|
||||
$rows = [];
|
||||
|
||||
foreach ($assignments as $assignment) {
|
||||
if ($tenantId && (int) ($assignment['tenant_id'] ?? 0) !== $tenantId) {
|
||||
throw ValidationException::withMessages([
|
||||
'assignments' => 'Assignment hanya boleh dibuat pada tenant aktif.',
|
||||
]);
|
||||
}
|
||||
|
||||
$group = MenuGroup::find($assignment['menu_group_id']);
|
||||
if ($tenantId && $group && (int) $group->tenant_id !== $tenantId) {
|
||||
throw ValidationException::withMessages([
|
||||
'assignments' => 'Menu group bukan milik tenant aktif.',
|
||||
]);
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'user_id' => $user->id,
|
||||
'menu_group_id' => (int) $assignment['menu_group_id'],
|
||||
'tenant_id' => $assignment['tenant_id'] ?? null,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($user, $rows) {
|
||||
DB::table('user_menu_groups')
|
||||
->where('user_id', $user->id)
|
||||
->when($tenantId, fn ($q) => $q->where('tenant_id', $tenantId))
|
||||
->delete();
|
||||
|
||||
if (! empty($rows)) {
|
||||
DB::table('user_menu_groups')->insert($rows);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user