update api register
This commit is contained in:
@@ -66,7 +66,7 @@ class AccessService
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $actor->isTenantOwner() || ! $tenantId || $target->isMasterAdmin()) {
|
||||
if (! $actor->isTenantOwner($tenantId) || ! $tenantId || $target->isMasterAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Access;
|
||||
|
||||
use App\Models\Customer;
|
||||
use App\Models\CustomerUserLink;
|
||||
use App\Models\MenuGroup;
|
||||
use App\Models\MenuList;
|
||||
use App\Models\RoleApplication;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Models\UserMenuGroup;
|
||||
use App\Models\UserTenant;
|
||||
use App\Models\VoucherAgent;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class RoleApplicationService
|
||||
{
|
||||
public function mine(User $user)
|
||||
{
|
||||
return $user->roleApplications()->with(['tenant:id,tenant_code,tenant_name', 'reviewer:id,name'])->latest('id')->get();
|
||||
}
|
||||
|
||||
public function submit(array $data, User $user): RoleApplication
|
||||
{
|
||||
$type = $data['application_type'];
|
||||
if (RoleApplication::where('applicant_user_id', $user->id)->where('application_type', $type)->where('status', 'pending')->exists()) {
|
||||
throw ValidationException::withMessages(['application_type' => 'Masih ada pengajuan sejenis yang menunggu persetujuan.']);
|
||||
}
|
||||
if ($type === 'tenant' && $user->userTenants()->where('role', 'tenant_owner')->exists()) {
|
||||
throw ValidationException::withMessages(['application_type' => 'Satu user hanya boleh menjadi Tenant Owner pada satu tenant.']);
|
||||
}
|
||||
if ($type === 'agent' && VoucherAgent::where('user_id', $user->id)->where('tenant_id', $data['tenant_id'])->where('status', 'active')->exists()) {
|
||||
throw ValidationException::withMessages(['tenant_id' => 'User sudah menjadi agen aktif pada tenant tersebut.']);
|
||||
}
|
||||
if ($type === 'staff' && $user->userTenants()->where('tenant_id', $data['tenant_id'])->where('role', 'tenant_owner')->exists()) {
|
||||
throw ValidationException::withMessages(['tenant_id' => 'Tenant Owner tidak dapat mengajukan diri sebagai staff pada tenant miliknya.']);
|
||||
}
|
||||
|
||||
return RoleApplication::create([
|
||||
'uuid' => Str::uuid(), 'applicant_user_id' => $user->id,
|
||||
'tenant_id' => in_array($type, ['agent', 'staff'], true) ? $data['tenant_id'] : null, 'application_type' => $type,
|
||||
'status' => 'pending', 'payload' => collect($data)->except(['application_type', 'tenant_id'])->all(),
|
||||
'submitted_at' => now(),
|
||||
])->load('tenant:id,tenant_code,tenant_name');
|
||||
}
|
||||
|
||||
public function reviewList(string $type, array $filters, User $reviewer, ?int $tenantId)
|
||||
{
|
||||
$this->authorizeReviewer($type, $reviewer, $tenantId);
|
||||
|
||||
return RoleApplication::with(['applicant:id,name,username,email,whatsapp_number', 'tenant:id,tenant_code,tenant_name', 'reviewer:id,name'])
|
||||
->where('application_type', $type)
|
||||
->when(in_array($type, ['agent', 'staff'], true) && ! $reviewer->isMasterAdmin(), fn ($q) => $q->where('tenant_id', $tenantId))
|
||||
->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
|
||||
->when($filters['search'] ?? null, fn ($q, $search) => $q->whereHas('applicant', fn ($x) => $x->where('name', 'ilike', "%{$search}%")->orWhere('email', 'ilike', "%{$search}%")))
|
||||
->latest('id')->paginate($filters['per_page'] ?? 10);
|
||||
}
|
||||
|
||||
public function tenantMenuGroupOptions(User $reviewer): array
|
||||
{
|
||||
$this->authorizeReviewer('tenant', $reviewer, null);
|
||||
|
||||
return MenuGroup::query()
|
||||
->whereNull('tenant_id')
|
||||
->where('is_system', true)
|
||||
->where('is_active', true)
|
||||
->withCount('menus')
|
||||
->orderBy('name')
|
||||
->get(['id', 'name', 'description'])
|
||||
->map(fn (MenuGroup $group) => [
|
||||
'id' => $group->id,
|
||||
'name' => $group->name,
|
||||
'description' => $group->description,
|
||||
'menus_count' => $group->menus_count,
|
||||
])->all();
|
||||
}
|
||||
|
||||
public function staffMenuGroupOptions(User $reviewer, ?int $tenantId): array
|
||||
{
|
||||
$this->authorizeReviewer('staff', $reviewer, $tenantId);
|
||||
|
||||
$assignedGroupIds = UserMenuGroup::query()
|
||||
->where('user_id', $reviewer->id)
|
||||
->where('tenant_id', $tenantId)
|
||||
->pluck('menu_group_id');
|
||||
|
||||
return MenuGroup::query()
|
||||
->where('is_active', true)
|
||||
->where(function ($query) use ($tenantId, $assignedGroupIds) {
|
||||
$query->where('tenant_id', $tenantId)
|
||||
->orWhereIn('id', $assignedGroupIds);
|
||||
})
|
||||
->withCount('menus')
|
||||
->orderBy('name')
|
||||
->get(['id', 'name', 'description', 'tenant_id'])
|
||||
->map(fn (MenuGroup $group) => [
|
||||
'id' => $group->id,
|
||||
'name' => $group->name,
|
||||
'description' => $group->description,
|
||||
'menus_count' => $group->menus_count,
|
||||
'is_tenant_group' => (int) $group->tenant_id === (int) $tenantId,
|
||||
])->all();
|
||||
}
|
||||
|
||||
public function review(RoleApplication $application, array $data, User $reviewer, ?int $tenantId): RoleApplication
|
||||
{
|
||||
$this->authorizeReviewer($application->application_type, $reviewer, $tenantId, $application->tenant_id);
|
||||
if ($application->status !== 'pending') {
|
||||
throw ValidationException::withMessages(['application' => 'Pengajuan ini sudah ditinjau.']);
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($application, $data, $reviewer) {
|
||||
if ($data['decision'] === 'approved') {
|
||||
if ($application->application_type === 'agent' && empty($data['payment_type'])) {
|
||||
throw ValidationException::withMessages(['payment_type' => 'Kategori pembayaran agen wajib dipilih.']);
|
||||
}
|
||||
match ($application->application_type) {
|
||||
'tenant' => $this->approveTenant($application, (int) $data['menu_group_id']),
|
||||
'staff' => $this->approveStaff($application, (int) $data['menu_group_id'], $reviewer),
|
||||
default => $this->approveAgent($application, $data),
|
||||
};
|
||||
}
|
||||
$application->update([
|
||||
'status' => $data['decision'], 'reviewed_by' => $reviewer->id,
|
||||
'review_notes' => $data['review_notes'] ?? null, 'reviewed_at' => now(),
|
||||
]);
|
||||
|
||||
return $application->fresh(['applicant:id,name,username,email', 'tenant:id,tenant_code,tenant_name', 'reviewer:id,name']);
|
||||
});
|
||||
}
|
||||
|
||||
public function linkCustomer(array $data, User $reviewer, int $tenantId): CustomerUserLink
|
||||
{
|
||||
abort_unless($reviewer->isMasterAdmin() || in_array($reviewer->tenantRole($tenantId), ['tenant_owner', 'staff'], true), 403);
|
||||
$customer = Customer::whereKey($data['customer_id'])->where('tenant_id', $tenantId)->firstOrFail();
|
||||
$link = CustomerUserLink::updateOrCreate(['customer_id' => $customer->id, 'user_id' => $data['user_id']], [
|
||||
'tenant_id' => $tenantId, 'linked_by' => $reviewer->id,
|
||||
'relationship' => $data['relationship'] ?? 'owner', 'is_primary' => $data['is_primary'] ?? true,
|
||||
]);
|
||||
if (! $customer->user_id && ($data['is_primary'] ?? true)) {
|
||||
$customer->update(['user_id' => $data['user_id']]);
|
||||
}
|
||||
|
||||
return $link->load(['customer:id,tenant_id,customer_code,name', 'user:id,name,username,email']);
|
||||
}
|
||||
|
||||
private function approveTenant(RoleApplication $application, int $templateGroupId): void
|
||||
{
|
||||
$payload = $application->payload;
|
||||
$group = MenuGroup::query()
|
||||
->whereNull('tenant_id')
|
||||
->where('is_system', true)
|
||||
->where('is_active', true)
|
||||
->findOrFail($templateGroupId);
|
||||
$tenant = Tenant::create([
|
||||
'tenant_code' => $this->uniqueTenantCode($payload['tenant_code'] ?? null), 'tenant_name' => $payload['tenant_name'],
|
||||
'phone' => $payload['phone'] ?? null, 'email' => $payload['email'] ?? null,
|
||||
'address' => $payload['address'] ?? null, 'status' => 'active',
|
||||
]);
|
||||
$application->tenant_id = $tenant->id;
|
||||
if (UserTenant::where('user_id', $application->applicant_user_id)->where('role', 'tenant_owner')->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'application' => 'User sudah menjadi Tenant Owner pada tenant lain.',
|
||||
]);
|
||||
}
|
||||
UserTenant::where('user_id', $application->applicant_user_id)->update(['is_default' => false]);
|
||||
UserTenant::updateOrCreate(
|
||||
['user_id' => $application->applicant_user_id, 'tenant_id' => $tenant->id],
|
||||
['role' => 'tenant_owner', 'is_default' => true]
|
||||
);
|
||||
$application->applicant()->update(['access_level' => 'tenant_owner']);
|
||||
|
||||
UserMenuGroup::create([
|
||||
'user_id' => $application->applicant_user_id,
|
||||
'tenant_id' => $tenant->id,
|
||||
'menu_group_id' => $group->id,
|
||||
]);
|
||||
|
||||
$application->payload = array_merge($payload, [
|
||||
'approved_menu_group_id' => $group->id,
|
||||
'approved_menu_group_name' => $group->name,
|
||||
]);
|
||||
}
|
||||
|
||||
private function approveStaff(RoleApplication $application, int $menuGroupId, User $reviewer): void
|
||||
{
|
||||
$availableGroupIds = collect($this->staffMenuGroupOptions($reviewer, $application->tenant_id))->pluck('id');
|
||||
if (! $availableGroupIds->contains($menuGroupId)) {
|
||||
throw ValidationException::withMessages([
|
||||
'menu_group_id' => 'Group Menu tidak tersedia pada tenant aktif.',
|
||||
]);
|
||||
}
|
||||
|
||||
$existingTenantRole = UserTenant::query()
|
||||
->where('user_id', $application->applicant_user_id)
|
||||
->where('tenant_id', $application->tenant_id)
|
||||
->value('role');
|
||||
if ($existingTenantRole === 'tenant_owner') {
|
||||
throw ValidationException::withMessages([
|
||||
'application' => 'Tenant Owner tidak dapat diubah menjadi staff pada tenant miliknya.',
|
||||
]);
|
||||
}
|
||||
|
||||
UserTenant::updateOrCreate(
|
||||
['user_id' => $application->applicant_user_id, 'tenant_id' => $application->tenant_id],
|
||||
['role' => 'staff', 'is_default' => ! UserTenant::where('user_id', $application->applicant_user_id)->exists()]
|
||||
);
|
||||
if (! $application->applicant->userTenants()->where('role', 'tenant_owner')->exists()) {
|
||||
$application->applicant()->update(['access_level' => 'staff']);
|
||||
}
|
||||
UserMenuGroup::where('user_id', $application->applicant_user_id)
|
||||
->where('tenant_id', $application->tenant_id)
|
||||
->delete();
|
||||
UserMenuGroup::create([
|
||||
'user_id' => $application->applicant_user_id,
|
||||
'tenant_id' => $application->tenant_id,
|
||||
'menu_group_id' => $menuGroupId,
|
||||
]);
|
||||
|
||||
$group = MenuGroup::findOrFail($menuGroupId);
|
||||
$application->payload = array_merge($application->payload, [
|
||||
'approved_menu_group_id' => $group->id,
|
||||
'approved_menu_group_name' => $group->name,
|
||||
]);
|
||||
}
|
||||
|
||||
private function approveAgent(RoleApplication $application, array $data): void
|
||||
{
|
||||
UserTenant::updateOrCreate(['user_id' => $application->applicant_user_id, 'tenant_id' => $application->tenant_id], [
|
||||
'is_default' => ! UserTenant::where('user_id', $application->applicant_user_id)->exists(),
|
||||
]);
|
||||
VoucherAgent::updateOrCreate(['tenant_id' => $application->tenant_id, 'user_id' => $application->applicant_user_id], [
|
||||
'uuid' => Str::uuid(), 'agent_code' => 'AGT-'.$application->tenant_id.'-'.Str::upper(Str::random(6)),
|
||||
'payment_type' => $data['payment_type'], 'price_mode' => $data['price_mode'] ?? 'discount',
|
||||
'discount_percent' => $data['discount_percent'] ?? 0, 'commission_percent' => $data['commission_percent'] ?? 0,
|
||||
'credit_limit' => $data['payment_type'] === 'postpaid' ? ($data['credit_limit'] ?? 0) : 0,
|
||||
'status' => 'active', 'notes' => $data['review_notes'] ?? null,
|
||||
]);
|
||||
$this->assignDefaultGroup($application->applicant, Tenant::findOrFail($application->tenant_id), 'agent');
|
||||
}
|
||||
|
||||
private function assignDefaultGroup(User $user, Tenant $tenant, string $role): void
|
||||
{
|
||||
$name = $role === 'tenant_owner' ? 'Tenant Owner Default' : 'Agen Voucher Default';
|
||||
$group = MenuGroup::firstOrCreate(['tenant_id' => $tenant->id, 'name' => $name], [
|
||||
'description' => 'Dibuat otomatis dari approval pengajuan.', 'is_system' => true, 'is_active' => true,
|
||||
]);
|
||||
$slugs = $role === 'tenant_owner'
|
||||
? MenuList::where('is_active', true)->whereNotIn('slug', ['tenants', 'role-applications-tenants'])->pluck('id')
|
||||
: MenuList::whereIn('slug', ['voucher-hotspot', 'voucher-hotspot-sales', 'wallet'])->pluck('id');
|
||||
$group->menus()->syncWithoutDetaching($slugs->mapWithKeys(fn ($id) => [$id => [
|
||||
'can_view' => true, 'can_create' => true, 'can_update' => $role === 'tenant_owner',
|
||||
'can_delete' => $role === 'tenant_owner', 'can_approve' => $role === 'tenant_owner', 'can_export' => true,
|
||||
]])->all());
|
||||
UserMenuGroup::firstOrCreate(['user_id' => $user->id, 'tenant_id' => $tenant->id, 'menu_group_id' => $group->id]);
|
||||
}
|
||||
|
||||
private function authorizeReviewer(string $type, User $reviewer, ?int $tenantId, ?int $applicationTenantId = null): void
|
||||
{
|
||||
if ($type === 'tenant') {
|
||||
abort_unless($reviewer->isMasterAdmin() || $reviewer->is_platform_staff, 403);
|
||||
}
|
||||
if ($type === 'agent' && ! $reviewer->isMasterAdmin()) {
|
||||
abort_unless($tenantId && (! $applicationTenantId || $tenantId === $applicationTenantId)
|
||||
&& in_array($reviewer->tenantRole($tenantId), ['tenant_owner', 'staff'], true), 403);
|
||||
}
|
||||
if ($type === 'staff') {
|
||||
abort_unless($tenantId
|
||||
&& (! $applicationTenantId || $tenantId === $applicationTenantId)
|
||||
&& $reviewer->isTenantOwner($tenantId), 403);
|
||||
}
|
||||
}
|
||||
|
||||
private function uniqueTenantCode(?string $requested): string
|
||||
{
|
||||
$base = Str::upper(Str::slug($requested ?: 'TNT-'.Str::random(6), ''));
|
||||
$code = $base;
|
||||
$counter = 1;
|
||||
while (Tenant::where('tenant_code', $code)->exists()) {
|
||||
$code = $base.'-'.$counter++;
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user