big update base struktur tahap 1

This commit is contained in:
Wian Drs
2026-07-27 16:24:09 +07:00
parent 2b3592c4c2
commit 1dd02baa72
169 changed files with 24405 additions and 977 deletions
+392
View File
@@ -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);
}
});
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace App\Services\Menu;
use App\Models\MenuList;
class MenuListService
{
public function list(array $filters = [])
{
return MenuList::query()
->when(
!empty($filters['search']),
fn($q) => $q->where(function ($x) use ($filters) {
$x->where('name', 'ILIKE', "%{$filters['search']}%")
->orWhere('slug', 'ILIKE', "%{$filters['search']}%")
->orWhere('url', 'ILIKE', "%{$filters['search']}%");
})
)
->when(
isset($filters['is_active']) && $filters['is_active'] !== '',
fn($q) => $q->where(
'is_active',
filter_var($filters['is_active'], FILTER_VALIDATE_BOOLEAN)
)
)
->latest('id')
->paginate($filters['per_page'] ?? 10);
}
public function create(array $data): MenuList
{
return MenuList::create($data);
}
public function update(MenuList $menuList, array $data): MenuList
{
$menuList->update($data);
return $menuList->fresh();
}
public function delete(MenuList $menuList): bool
{
return $menuList->delete();
}
}