refactor: add methods for retrieving assignable roles and their values in Role enum, and enhance Permission enum with values method for improved role management

This commit is contained in:
Yoga Pangestu 2026-06-22 18:12:43 +07:00
parent da3e3a6895
commit 86956b1c9f
11 changed files with 199 additions and 44 deletions

View File

@ -3,6 +3,7 @@
namespace App\Enums;
use App\Traits\ProvidesEnumOptions;
use InvalidArgumentException;
enum CuttingStatus: string
{
@ -24,4 +25,62 @@ public function label(): string
self::REJECTED => 'Ditolak',
};
}
public function isEditable(): bool
{
return in_array($this, [self::IN_PROGRESS, self::REJECTED], true);
}
public function canTransitionTo(self $status): bool
{
return match ($this) {
self::IN_PROGRESS => $status === self::COMPLETED,
self::COMPLETED => in_array($status, [self::PENDING_VERIFICATION, self::REJECTED, self::IN_PROGRESS], true),
self::PENDING_VERIFICATION => in_array($status, [self::VERIFIED, self::REJECTED, self::COMPLETED], true),
self::REJECTED => $status === self::IN_PROGRESS,
default => false,
};
}
public function transitionPermission(): Permission
{
return match ($this) {
self::COMPLETED => Permission::CUTTINGS_COMPLETE,
self::PENDING_VERIFICATION => Permission::CUTTINGS_VERIFY,
self::VERIFIED => Permission::CUTTINGS_VERIFY,
self::REJECTED => Permission::CUTTINGS_REJECT,
self::IN_PROGRESS => Permission::CUTTINGS_UPDATE,
default => throw new InvalidArgumentException('Status tidak mendukung transisi.'),
};
}
/**
* @return list<array{status: string, label: string, destructive: bool, permission: string, icon_only: bool}>
*/
public function availableActions(): array
{
return match ($this) {
self::IN_PROGRESS => [
[
'status' => self::COMPLETED->value,
'label' => 'Selesai',
'destructive' => false,
'permission' => Permission::CUTTINGS_COMPLETE->value,
'icon_only' => false,
],
],
self::COMPLETED => [],
self::PENDING_VERIFICATION => [],
self::REJECTED => [
[
'status' => self::IN_PROGRESS->value,
'label' => 'Kembalikan ke Proses',
'destructive' => false,
'permission' => Permission::CUTTINGS_UPDATE->value,
'icon_only' => false,
],
],
default => [],
};
}
}

View File

@ -3,6 +3,7 @@
namespace App\Enums;
use App\Traits\ProvidesEnumOptions;
use InvalidArgumentException;
enum OrderStatus: string
{
@ -22,4 +23,70 @@ public function label(): string
self::CANCELLED => 'Dibatalkan',
};
}
public function isEditable(): bool
{
return in_array($this, [self::PENDING, self::PROCESSING], true);
}
public function canTransitionTo(self $status): bool
{
return match ($this) {
self::PENDING => in_array($status, [self::PROCESSING, self::CANCELLED], true),
self::PROCESSING => in_array($status, [self::COMPLETED, self::CANCELLED], true),
self::COMPLETED, self::CANCELLED => false,
};
}
public function transitionPermission(): Permission
{
return match ($this) {
self::PROCESSING => Permission::ORDERS_SEND,
self::COMPLETED => Permission::ORDERS_COMPLETE,
self::CANCELLED => Permission::ORDERS_CANCEL,
default => throw new InvalidArgumentException('Status tidak mendukung transisi.'),
};
}
/**
* @return list<array{status: string, label: string, destructive: bool, permission: string, icon_only: bool}>
*/
public function availableActions(): array
{
return match ($this) {
self::PENDING => [
[
'status' => self::PROCESSING->value,
'label' => 'Kirim',
'destructive' => false,
'permission' => Permission::ORDERS_SEND->value,
'icon_only' => true,
],
[
'status' => self::CANCELLED->value,
'label' => 'Batalkan',
'destructive' => true,
'permission' => Permission::ORDERS_CANCEL->value,
'icon_only' => true,
],
],
self::PROCESSING => [
[
'status' => self::COMPLETED->value,
'label' => 'Selesai',
'destructive' => false,
'permission' => Permission::ORDERS_COMPLETE->value,
'icon_only' => true,
],
[
'status' => self::CANCELLED->value,
'label' => 'Batalkan',
'destructive' => true,
'permission' => Permission::ORDERS_CANCEL->value,
'icon_only' => true,
],
],
default => [],
};
}
}

View File

@ -267,4 +267,12 @@ public function group(): string
self::ROLES_DELETE => 'Role & Permission',
};
}
/**
* @return list<string>
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
}

View File

@ -285,4 +285,44 @@ public function permissions(): array
],
};
}
public function isAssignable(): bool
{
return $this !== self::DEVELOPER;
}
/**
* @return list<array{value: string, label: string}>
*/
public static function assignableSelectOptions(): array
{
return collect(self::cases())
->filter(fn (self $role) => $role->isAssignable())
->map(fn (self $role) => [
'value' => $role->value,
'label' => $role->label(),
])
->values()
->all();
}
/**
* @return list<string>
*/
public static function assignableValues(): array
{
return collect(self::cases())
->filter(fn (self $role) => $role->isAssignable())
->map(fn (self $role) => $role->value)
->values()
->all();
}
/**
* @return list<string>
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
}

View File

@ -27,16 +27,7 @@ public function __construct(
private function assignableRoleOptions(): array
{
$assignable = config('roles.assignable', []);
return collect(Role::cases())
->filter(fn (Role $role) => in_array($role->value, $assignable, true))
->map(fn (Role $role) => [
'value' => $role->value,
'label' => $role->label(),
])
->values()
->all();
return Role::assignableSelectOptions();
}
public function index(Request $request): Response

View File

@ -36,7 +36,7 @@ public function rules(): array
'gender' => ['nullable', Rule::enum(Gender::class)],
'birth_date' => ['nullable', 'date', 'before:today'],
'address' => ['nullable', 'string'],
'role' => ['required', Rule::in(config('roles.assignable', []))],
'role' => ['required', Rule::in(Role::assignableValues())],
];
if (! $isOwner) {

View File

@ -26,10 +26,13 @@ public function authorize(): bool
|| false;
}
$permissions = config('cutting-status.permissions', $status);
$permission = $permissions[$status->value] ?? null;
try {
$permission = $status->transitionPermission();
} catch (\InvalidArgumentException) {
return false;
}
return $permission ? ($this->user()?->can($permission->value) ?? false) : false;
return $this->user()?->can($permission->value) ?? false;
}
/**
@ -76,10 +79,7 @@ public function withValidator(Validator $validator): void
return;
}
$transitions = config('cutting-status.transitions', []);
$allowedTargets = $transitions[$cutting->status->value] ?? [];
if (! in_array($status, $allowedTargets, true)) {
if (! $cutting->status->canTransitionTo($status)) {
$validator->errors()->add('status', 'Status cutting tidak dapat diubah.');
}

View File

@ -90,7 +90,7 @@ public function withValidator(Validator $validator): void
/** @var Order $order */
$order = $this->route('order');
if (! in_array($order->status, config('order-status.editable'), true)) {
if (! $order->status->isEditable()) {
$validator->errors()->add('status', 'Pesanan tidak dapat diubah.');
}
});

View File

@ -18,10 +18,13 @@ public function authorize(): bool
return false;
}
$permissions = config('order-status.permissions', []);
$permission = $permissions[$status->value] ?? null;
try {
$permission = $status->transitionPermission();
} catch (\InvalidArgumentException) {
return false;
}
return $permission ? ($this->user()?->can($permission->value) ?? false) : false;
return $this->user()?->can($permission->value) ?? false;
}
/**
@ -55,10 +58,7 @@ public function withValidator(Validator $validator): void
return;
}
$transitions = config('order-status.transitions', []);
$allowedTargets = $transitions[$order->status->value] ?? [];
if (! in_array($status, $allowedTargets, true)) {
if (! $order->status->canTransitionTo($status)) {
$validator->errors()->add('status', 'Status pesanan tidak dapat diubah.');
}
});

View File

@ -30,22 +30,17 @@ public function __construct(
public function isEditable(CuttingStatus $status): bool
{
return in_array($status, config('cutting-status.editable'), true);
return $status->isEditable();
}
public function canTransitionTo(CuttingStatus $from, CuttingStatus $to): bool
{
$transitions = config('cutting-status.transitions', []);
return in_array($to, $transitions[$from->value] ?? [], true);
return $from->canTransitionTo($to);
}
public function transitionPermission(CuttingStatus $status): Permission
{
$permissions = config('cutting-status.permissions', []);
return $permissions[$status->value]
?? throw new \InvalidArgumentException('Status tidak mendukung transisi.');
return $status->transitionPermission();
}
/**
@ -53,7 +48,7 @@ public function transitionPermission(CuttingStatus $status): Permission
*/
public function availableActions(CuttingStatus $status): array
{
return config('cutting-status.actions.'.$status->value, []);
return $status->availableActions();
}
/**

View File

@ -36,22 +36,17 @@ public function __construct(
public function isEditable(OrderStatus $status): bool
{
return in_array($status, config('order-status.editable'), true);
return $status->isEditable();
}
public function canTransitionTo(OrderStatus $from, OrderStatus $to): bool
{
$transitions = config('order-status.transitions', []);
return in_array($to, $transitions[$from->value] ?? [], true);
return $from->canTransitionTo($to);
}
public function transitionPermission(OrderStatus $status): Permission
{
$permissions = config('order-status.permissions', []);
return $permissions[$status->value]
?? throw new \InvalidArgumentException('Status tidak mendukung transisi.');
return $status->transitionPermission();
}
/**
@ -59,7 +54,7 @@ public function transitionPermission(OrderStatus $status): Permission
*/
public function availableActions(OrderStatus $status): array
{
return config('order-status.actions.'.$status->value, []);
return $status->availableActions();
}
public function defaultPriceType(OrderChannel $channel): ?PriceType