diff --git a/app/Console/Commands/ApplyAttendancePenalties.php b/app/Console/Commands/ApplyAttendancePenalties.php index 50e8770..49f1684 100644 --- a/app/Console/Commands/ApplyAttendancePenalties.php +++ b/app/Console/Commands/ApplyAttendancePenalties.php @@ -3,7 +3,6 @@ namespace App\Console\Commands; use App\Enums\PayrollAdjustmentType; -use App\Enums\PayrollPeriodStatus; use App\Models\Attendance; use App\Models\Employee; use App\Models\LeaveRequest; diff --git a/app/Enums/ActivityEventLabel.php b/app/Enums/ActivityEventLabel.php index 794f6b6..6fc94e6 100644 --- a/app/Enums/ActivityEventLabel.php +++ b/app/Enums/ActivityEventLabel.php @@ -2,8 +2,12 @@ namespace App\Enums; +use App\Traits\ProvidesEnumOptions; + enum ActivityEventLabel: string { + use ProvidesEnumOptions; + case CREATED = 'created'; case UPDATED = 'updated'; case DELETED = 'deleted'; @@ -20,26 +24,4 @@ public function label(): string self::LOGOUT => 'Keluar', }; } - - public static function labelFor(?string $event): string - { - if ($event === null || $event === '') { - return '-'; - } - - return self::tryFrom($event)?->label() ?? ucfirst($event); - } - - /** - * @return list - */ - public static function selectOptions(): array - { - return collect(self::cases()) - ->map(fn (self $event) => [ - 'value' => $event->value, - 'label' => $event->label(), - ]) - ->all(); - } } diff --git a/app/Enums/CuttingStatus.php b/app/Enums/CuttingStatus.php index af9c7c9..6cc2076 100644 --- a/app/Enums/CuttingStatus.php +++ b/app/Enums/CuttingStatus.php @@ -3,7 +3,6 @@ namespace App\Enums; use App\Traits\ProvidesEnumOptions; -use InvalidArgumentException; enum CuttingStatus: string { @@ -25,62 +24,4 @@ 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 - */ - 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 => [], - }; - } } diff --git a/app/Enums/OrderChannel.php b/app/Enums/OrderChannel.php index e1c88cb..5c37808 100644 --- a/app/Enums/OrderChannel.php +++ b/app/Enums/OrderChannel.php @@ -20,13 +20,4 @@ public function label(): string self::TIKTOK => 'TikTok', }; } - - public function defaultPriceType(): ?PriceType - { - return match ($this) { - self::STORE => null, - self::SHOPEE => PriceType::SHOPEE, - self::TIKTOK => PriceType::TIKTOK, - }; - } } diff --git a/app/Enums/OrderStatus.php b/app/Enums/OrderStatus.php index 4b19b82..169120d 100644 --- a/app/Enums/OrderStatus.php +++ b/app/Enums/OrderStatus.php @@ -3,7 +3,6 @@ namespace App\Enums; use App\Traits\ProvidesEnumOptions; -use InvalidArgumentException; enum OrderStatus: string { @@ -23,70 +22,4 @@ 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 - */ - 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 => [], - }; - } } diff --git a/app/Enums/Permission.php b/app/Enums/Permission.php index 94aa1c9..58c7441 100644 --- a/app/Enums/Permission.php +++ b/app/Enums/Permission.php @@ -222,57 +222,4 @@ public function label(): string self::ROLES_DELETE => 'Hapus Role', }; } - - public function group(): string - { - return match ($this) { - self::DASHBOARD_VIEW => 'Umum', - self::EMPLOYEES_VIEW, self::EMPLOYEES_CREATE, self::EMPLOYEES_UPDATE, - self::EMPLOYEES_DELETE, self::EMPLOYEES_RESET_PASSWORD, self::EMPLOYEES_TOGGLE_STATUS => 'Pegawai', - self::ATTENDANCES_VIEW, self::ATTENDANCES_CREATE, self::ATTENDANCES_DELETE, - self::ATTENDANCES_MANAGE => 'Presensi', - self::LEAVE_REQUESTS_VIEW, self::LEAVE_REQUESTS_CREATE, self::LEAVE_REQUESTS_UPDATE, - self::LEAVE_REQUESTS_DELETE, self::LEAVE_REQUESTS_VERIFY => 'Cuti', - self::CATEGORIES_VIEW, self::CATEGORIES_CREATE, self::CATEGORIES_UPDATE, - self::CATEGORIES_DELETE => 'Kategori', - self::SUPPLIERS_VIEW, self::SUPPLIERS_CREATE, self::SUPPLIERS_UPDATE, - self::SUPPLIERS_DELETE => 'Supplier', - self::CUSTOMERS_VIEW, self::CUSTOMERS_CREATE, self::CUSTOMERS_UPDATE, - self::CUSTOMERS_DELETE => 'Pelanggan', - self::PRODUCTS_VIEW, self::PRODUCTS_CREATE, self::PRODUCTS_UPDATE, - self::PRODUCTS_DELETE, self::PRODUCTS_TOGGLE_STATUS => 'Produk', - self::RAW_MATERIALS_VIEW, self::RAW_MATERIALS_CREATE, self::RAW_MATERIALS_UPDATE, - self::RAW_MATERIALS_DELETE, self::RAW_MATERIALS_TOGGLE_STATUS => 'Bahan Baku', - self::PURCHASES_VIEW, self::PURCHASES_CREATE, self::PURCHASES_UPDATE, - self::PURCHASES_DELETE => 'Belanja', - self::ORDERS_VIEW, self::ORDERS_CREATE, self::ORDERS_UPDATE, - self::ORDERS_DELETE, self::ORDERS_SEND, self::ORDERS_COMPLETE, - self::ORDERS_CANCEL => 'Pesanan', - self::CUTTINGS_VIEW, self::CUTTINGS_CREATE, self::CUTTINGS_UPDATE, - self::CUTTINGS_DELETE, self::CUTTINGS_COMPLETE, self::CUTTINGS_VERIFY, - self::CUTTINGS_REJECT => 'Cutting', - self::OWNER_VERIFICATIONS_VIEW, self::OWNER_VERIFICATIONS_VERIFY, - self::OWNER_VERIFICATIONS_REJECT => 'Verifikasi Owner', - self::STOCKS_VIEW => 'Stok', - self::CASH_VIEW, self::CASH_DEPOSIT, self::CASH_WITHDRAW, self::CASH_UPDATE, self::CASH_DELETE => 'Kas', - self::EXPENSES_VIEW, self::EXPENSES_CREATE, self::EXPENSES_UPDATE, - self::EXPENSES_DELETE => 'Pengeluaran', - self::EMPLOYEE_ADVANCES_VIEW, self::EMPLOYEE_ADVANCES_CREATE, self::EMPLOYEE_ADVANCES_UPDATE, - self::EMPLOYEE_ADVANCES_DELETE, self::EMPLOYEE_ADVANCES_VERIFY, - self::EMPLOYEE_ADVANCES_PAY => 'Kasbon', - self::PAYROLL_VIEW, self::PAYROLL_ADJUST => 'Gaji', - self::SETTINGS_VIEW, self::SETTINGS_UPDATE => 'Pengaturan Aplikasi', - self::ACTIVITY_LOGS_VIEW => 'Log Aktivitas', - self::ROLES_VIEW, self::ROLES_CREATE, self::ROLES_UPDATE, - self::ROLES_DELETE => 'Role & Permission', - }; - } - - /** - * @return list - */ - public static function values(): array - { - return array_column(self::cases(), 'value'); - } } diff --git a/app/Enums/PriceType.php b/app/Enums/PriceType.php index f2fa4ff..bc23ec6 100644 --- a/app/Enums/PriceType.php +++ b/app/Enums/PriceType.php @@ -30,12 +30,4 @@ public function label(): string self::HARGA_MODAL => 'Harga Modal', }; } - - /** - * @return list - */ - public static function values(): array - { - return array_column(self::cases(), 'value'); - } } diff --git a/app/Enums/ProductStockQuality.php b/app/Enums/ProductStockQuality.php index d9b8252..1b99e98 100644 --- a/app/Enums/ProductStockQuality.php +++ b/app/Enums/ProductStockQuality.php @@ -18,12 +18,4 @@ public function label(): string self::REJECT => 'Reject', }; } - - public function stockColumn(): string - { - return match ($this) { - self::GOOD => 'stock', - self::REJECT => 'reject_stock', - }; - } } diff --git a/app/Enums/RawMaterialUnit.php b/app/Enums/RawMaterialUnit.php index 7d712d0..4c90c51 100644 --- a/app/Enums/RawMaterialUnit.php +++ b/app/Enums/RawMaterialUnit.php @@ -12,16 +12,6 @@ enum RawMaterialUnit: string case METER = 'meter'; case KILOGRAM = 'kilogram'; - private const MIN_STOCK_YARD = 20; - - private const MIN_STOCK_METER = 10; - - private const MIN_STOCK_KILOGRAM = 5; - - private const CM_PER_YARD = 91.44; - - private const CM_PER_METER = 100.0; - public function label(): string { return match ($this) { @@ -39,71 +29,4 @@ public function abbreviation(): string self::KILOGRAM => 'kg', }; } - - /** - * @return list - */ - public static function values(): array - { - return array_column(self::cases(), 'value'); - } - - public function minStock(): float - { - return match ($this) { - self::YARD => self::MIN_STOCK_YARD, - self::METER => self::MIN_STOCK_METER, - self::KILOGRAM => self::MIN_STOCK_KILOGRAM, - }; - } - - public function usesLengthUnit(): bool - { - return match ($this) { - self::YARD, self::METER => true, - self::KILOGRAM => false, - }; - } - - public function cmPerUnit(): ?float - { - return match ($this) { - self::YARD => self::CM_PER_YARD, - self::METER => self::CM_PER_METER, - self::KILOGRAM => null, - }; - } - - public function toCm(float $value): ?float - { - $cmPerUnit = $this->cmPerUnit(); - - if ($cmPerUnit === null) { - return null; - } - - return $value * $cmPerUnit; - } - - public function fromCm(float $cm): float - { - $cmPerUnit = $this->cmPerUnit(); - - if ($cmPerUnit === null || $cmPerUnit <= 0) { - return $cm; - } - - return $cm / $cmPerUnit; - } - - public function pricePerCm(int $pricePerUnit): ?float - { - $cmPerUnit = $this->cmPerUnit(); - - if ($cmPerUnit === null || $cmPerUnit <= 0) { - return null; - } - - return $pricePerUnit / $cmPerUnit; - } } diff --git a/app/Enums/Role.php b/app/Enums/Role.php index 514ce5c..d8019e5 100644 --- a/app/Enums/Role.php +++ b/app/Enums/Role.php @@ -30,302 +30,4 @@ public function label(): string self::NON_OPERATOR => 'Non Operator', }; } - - /** - * @return list - */ - public function permissions(): array - { - return match ($this) { - self::DEVELOPER, self::OWNER => array_values(array_filter( - Permission::cases(), - fn (Permission $permission) => ! in_array($permission, [ - Permission::ATTENDANCES_CREATE, - Permission::ATTENDANCES_DELETE, - ], true) - )), - - self::DIREKTUR => [ - Permission::DASHBOARD_VIEW, - - Permission::EMPLOYEES_VIEW, - - Permission::STOCKS_VIEW, - - Permission::ATTENDANCES_VIEW, - Permission::ATTENDANCES_CREATE, - Permission::ATTENDANCES_DELETE, - Permission::ATTENDANCES_MANAGE, - - Permission::LEAVE_REQUESTS_VIEW, - Permission::LEAVE_REQUESTS_CREATE, - Permission::LEAVE_REQUESTS_UPDATE, - Permission::LEAVE_REQUESTS_DELETE, - - Permission::CATEGORIES_VIEW, - - Permission::SUPPLIERS_VIEW, - - Permission::CUSTOMERS_VIEW, - - Permission::PRODUCTS_VIEW, - - Permission::RAW_MATERIALS_VIEW, - - Permission::PURCHASES_VIEW, - - Permission::ORDERS_VIEW, - - Permission::CUTTINGS_VIEW, - - Permission::CASH_VIEW, - - Permission::EXPENSES_VIEW, - - Permission::EMPLOYEE_ADVANCES_VIEW, - Permission::EMPLOYEE_ADVANCES_CREATE, - Permission::EMPLOYEE_ADVANCES_UPDATE, - Permission::EMPLOYEE_ADVANCES_DELETE, - Permission::EMPLOYEE_ADVANCES_PAY, - - Permission::PAYROLL_VIEW, - ], - - self::ADMIN_TOKO => [ - Permission::DASHBOARD_VIEW, - - Permission::STOCKS_VIEW, - - Permission::CUTTINGS_VERIFY, - - Permission::ATTENDANCES_VIEW, - Permission::ATTENDANCES_CREATE, - Permission::ATTENDANCES_DELETE, - - Permission::LEAVE_REQUESTS_VIEW, - Permission::LEAVE_REQUESTS_CREATE, - Permission::LEAVE_REQUESTS_UPDATE, - Permission::LEAVE_REQUESTS_DELETE, - - Permission::CATEGORIES_VIEW, - Permission::CATEGORIES_CREATE, - Permission::CATEGORIES_UPDATE, - Permission::CATEGORIES_DELETE, - - Permission::SUPPLIERS_VIEW, - Permission::SUPPLIERS_CREATE, - Permission::SUPPLIERS_UPDATE, - Permission::SUPPLIERS_DELETE, - - Permission::CUSTOMERS_VIEW, - Permission::CUSTOMERS_CREATE, - Permission::CUSTOMERS_UPDATE, - Permission::CUSTOMERS_DELETE, - - Permission::PRODUCTS_VIEW, - Permission::PRODUCTS_CREATE, - Permission::PRODUCTS_UPDATE, - Permission::PRODUCTS_DELETE, - Permission::PRODUCTS_TOGGLE_STATUS, - - Permission::ORDERS_VIEW, - Permission::ORDERS_CREATE, - Permission::ORDERS_UPDATE, - Permission::ORDERS_DELETE, - Permission::ORDERS_SEND, - Permission::ORDERS_COMPLETE, - Permission::ORDERS_CANCEL, - - Permission::CASH_VIEW, - Permission::CASH_DEPOSIT, - Permission::CASH_WITHDRAW, - Permission::CASH_UPDATE, - Permission::CASH_DELETE, - - Permission::EXPENSES_VIEW, - Permission::EXPENSES_CREATE, - Permission::EXPENSES_UPDATE, - Permission::EXPENSES_DELETE, - - Permission::EMPLOYEE_ADVANCES_VIEW, - Permission::EMPLOYEE_ADVANCES_CREATE, - Permission::EMPLOYEE_ADVANCES_UPDATE, - Permission::EMPLOYEE_ADVANCES_DELETE, - Permission::EMPLOYEE_ADVANCES_PAY, - - Permission::PAYROLL_VIEW, - Permission::PAYROLL_ADJUST, - ], - - self::CASHIER => [ - Permission::DASHBOARD_VIEW, - - Permission::ATTENDANCES_VIEW, - Permission::ATTENDANCES_CREATE, - Permission::ATTENDANCES_DELETE, - - Permission::LEAVE_REQUESTS_VIEW, - Permission::LEAVE_REQUESTS_CREATE, - Permission::LEAVE_REQUESTS_UPDATE, - Permission::LEAVE_REQUESTS_DELETE, - - Permission::CUSTOMERS_VIEW, - Permission::CUSTOMERS_CREATE, - Permission::CUSTOMERS_UPDATE, - Permission::CUSTOMERS_DELETE, - - Permission::PRODUCTS_VIEW, - - Permission::ORDERS_VIEW, - Permission::ORDERS_CREATE, - Permission::ORDERS_UPDATE, - Permission::ORDERS_DELETE, - Permission::ORDERS_SEND, - Permission::ORDERS_COMPLETE, - Permission::ORDERS_CANCEL, - - Permission::EMPLOYEE_ADVANCES_VIEW, - Permission::EMPLOYEE_ADVANCES_CREATE, - Permission::EMPLOYEE_ADVANCES_UPDATE, - Permission::EMPLOYEE_ADVANCES_DELETE, - Permission::EMPLOYEE_ADVANCES_PAY, - - Permission::PAYROLL_VIEW, - Permission::PAYROLL_ADJUST, - ], - - self::ADMIN_BAHAN_BAKU => [ - Permission::DASHBOARD_VIEW, - - Permission::ATTENDANCES_VIEW, - Permission::ATTENDANCES_CREATE, - - Permission::LEAVE_REQUESTS_VIEW, - Permission::LEAVE_REQUESTS_CREATE, - Permission::LEAVE_REQUESTS_UPDATE, - Permission::LEAVE_REQUESTS_DELETE, - - Permission::SUPPLIERS_VIEW, - Permission::SUPPLIERS_CREATE, - Permission::SUPPLIERS_UPDATE, - Permission::SUPPLIERS_DELETE, - - Permission::RAW_MATERIALS_VIEW, - Permission::RAW_MATERIALS_CREATE, - Permission::RAW_MATERIALS_UPDATE, - Permission::RAW_MATERIALS_DELETE, - Permission::RAW_MATERIALS_TOGGLE_STATUS, - - Permission::PRODUCTS_VIEW, - - Permission::CUTTINGS_VIEW, - Permission::CUTTINGS_CREATE, - Permission::CUTTINGS_UPDATE, - Permission::CUTTINGS_DELETE, - Permission::CUTTINGS_COMPLETE, - Permission::CUTTINGS_VERIFY, - - Permission::PURCHASES_VIEW, - Permission::PURCHASES_CREATE, - Permission::PURCHASES_UPDATE, - Permission::PURCHASES_DELETE, - - Permission::EMPLOYEE_ADVANCES_VIEW, - Permission::EMPLOYEE_ADVANCES_CREATE, - Permission::EMPLOYEE_ADVANCES_UPDATE, - Permission::EMPLOYEE_ADVANCES_DELETE, - Permission::EMPLOYEE_ADVANCES_PAY, - - Permission::PAYROLL_VIEW, - ], - - self::MARKETING => [ - Permission::DASHBOARD_VIEW, - - Permission::ATTENDANCES_VIEW, - Permission::ATTENDANCES_CREATE, - - Permission::LEAVE_REQUESTS_VIEW, - Permission::LEAVE_REQUESTS_CREATE, - Permission::LEAVE_REQUESTS_UPDATE, - Permission::LEAVE_REQUESTS_DELETE, - - Permission::CUSTOMERS_VIEW, - - Permission::PRODUCTS_VIEW, - - Permission::ORDERS_VIEW, - - Permission::EMPLOYEE_ADVANCES_VIEW, - Permission::EMPLOYEE_ADVANCES_CREATE, - Permission::EMPLOYEE_ADVANCES_UPDATE, - Permission::EMPLOYEE_ADVANCES_DELETE, - Permission::EMPLOYEE_ADVANCES_PAY, - - Permission::PAYROLL_VIEW, - ], - - self::NON_OPERATOR => [ - Permission::DASHBOARD_VIEW, - - Permission::DASHBOARD_VIEW, - - Permission::ATTENDANCES_VIEW, - Permission::ATTENDANCES_CREATE, - - Permission::LEAVE_REQUESTS_VIEW, - Permission::LEAVE_REQUESTS_CREATE, - Permission::LEAVE_REQUESTS_UPDATE, - Permission::LEAVE_REQUESTS_DELETE, - - Permission::EMPLOYEE_ADVANCES_VIEW, - Permission::EMPLOYEE_ADVANCES_CREATE, - Permission::EMPLOYEE_ADVANCES_UPDATE, - Permission::EMPLOYEE_ADVANCES_DELETE, - Permission::EMPLOYEE_ADVANCES_PAY, - - Permission::PAYROLL_VIEW, - ], - }; - } - - public function isAssignable(): bool - { - return $this !== self::DEVELOPER; - } - - /** - * @return list - */ - 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 - */ - 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 - */ - public static function values(): array - { - return array_column(self::cases(), 'value'); - } } diff --git a/app/Http/Controllers/Admin/Hr/EmployeeController.php b/app/Http/Controllers/Admin/Hr/EmployeeController.php index 49169ae..7339d33 100644 --- a/app/Http/Controllers/Admin/Hr/EmployeeController.php +++ b/app/Http/Controllers/Admin/Hr/EmployeeController.php @@ -25,6 +25,20 @@ public function __construct( private readonly EmployeeService $employeeService, ) {} + 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(); + } + public function index(Request $request): Response { $tableQuery = $this->parseDataTableQuery($request); @@ -47,7 +61,7 @@ public function index(Request $request): Response 'employment_status' => $employmentStatus, 'is_active' => $isActive, ]), - 'roles' => Role::assignableSelectOptions(), + 'roles' => $this->assignableRoleOptions(), 'genders' => Gender::selectOptions(), 'employmentStatuses' => EmploymentStatus::selectOptions(), ]); @@ -58,7 +72,7 @@ public function create(): Response return Inertia::render('admin/hr/employees/Create', [ 'genders' => Gender::selectOptions(), 'employmentStatuses' => EmploymentStatus::selectOptions(), - 'roles' => Role::assignableSelectOptions(), + 'roles' => $this->assignableRoleOptions(), ]); } @@ -78,7 +92,7 @@ public function edit(User $user): Response return Inertia::render('admin/hr/employees/Edit', [ 'genders' => Gender::selectOptions(), 'employmentStatuses' => EmploymentStatus::selectOptions(), - 'roles' => Role::assignableSelectOptions(), + 'roles' => $this->assignableRoleOptions(), 'employee' => $user, ]); } @@ -94,20 +108,20 @@ public function update(EmployeeRequest $request, User $user): RedirectResponse public function toggleStatus(ToggleStatusRequest $request, User $user): RedirectResponse { - $this->employeeService->toggleStatus($user, $request->validated()); + $this->employeeService->toggleStatus($user); - $this->flashStatusUpdated('pegawai'); + $this->flashSuccess('Status pegawai berhasil diubah.'); - return back(); + return redirect()->route('admin.hr.employees.index'); } public function resetPassword(User $user): RedirectResponse { $this->employeeService->resetPassword($user); - $this->flashSuccess('Kata sandi berhasil direset. Pengguna telah logout dari semua sesi.'); + $this->flashSuccess('Kata sandi pegawai berhasil direset.'); - return back(); + return redirect()->route('admin.hr.employees.index'); } public function destroy(User $user): RedirectResponse diff --git a/app/Http/Controllers/Admin/Manage/CuttingController.php b/app/Http/Controllers/Admin/Manage/CuttingController.php index 470faf8..43167dd 100644 --- a/app/Http/Controllers/Admin/Manage/CuttingController.php +++ b/app/Http/Controllers/Admin/Manage/CuttingController.php @@ -57,7 +57,7 @@ public function store(CuttingRequest $request): RedirectResponse public function edit(Cutting $cutting): Response|RedirectResponse { - if (! $cutting->status->isEditable()) { + if (! $this->cuttingService->isEditable($cutting->status)) { $this->flashError('Proses cutting tidak dapat diubah.'); return redirect()->route('admin.manage.cuttings.index'); diff --git a/app/Http/Controllers/Admin/Manage/OrderController.php b/app/Http/Controllers/Admin/Manage/OrderController.php index 1c06c68..dac13a6 100644 --- a/app/Http/Controllers/Admin/Manage/OrderController.php +++ b/app/Http/Controllers/Admin/Manage/OrderController.php @@ -75,7 +75,7 @@ public function store(OrderRequest $request): RedirectResponse public function edit(Order $order): Response|RedirectResponse { - if (! $order->status->isEditable()) { + if (! $this->orderService->isEditable($order->status)) { $this->flashError('Pesanan tidak dapat diubah.'); return redirect()->route('admin.manage.orders.index'); diff --git a/app/Http/Controllers/Admin/System/RoleController.php b/app/Http/Controllers/Admin/System/RoleController.php index 5939e13..b4b9d6a 100644 --- a/app/Http/Controllers/Admin/System/RoleController.php +++ b/app/Http/Controllers/Admin/System/RoleController.php @@ -35,11 +35,13 @@ public function index(Request $request): Response public function create(): Response { + $permissionGroups = config('permissions.groups'); + $permissions = collect(PermissionEnum::cases()) ->map(fn (PermissionEnum $permission) => [ 'value' => $permission->value, 'label' => $permission->label(), - 'group' => $permission->group(), + 'group' => $permissionGroups[$permission->value] ?? '-', ]) ->values() ->all(); @@ -62,11 +64,13 @@ public function edit(Role $role): Response { $role->load('permissions'); + $permissionGroups = config('permissions.groups'); + $permissions = collect(PermissionEnum::cases()) ->map(fn (PermissionEnum $permission) => [ 'value' => $permission->value, 'label' => $permission->label(), - 'group' => $permission->group(), + 'group' => $permissionGroups[$permission->value] ?? '-', ]) ->values() ->all(); @@ -78,10 +82,10 @@ public function edit(Role $role): Response 'role' => [ 'id' => $role->id, 'name' => $role->name, - 'permission_names' => $role->permissions->pluck('name')->all(), + 'permissions' => $role->permissions->pluck('name'), + 'is_protected' => $isProtected, ], 'permissions' => $permissions, - 'isProtected' => $isProtected, ]); } @@ -96,12 +100,9 @@ public function update(RoleRequest $request, Role $role): RedirectResponse public function destroy(Role $role): RedirectResponse { - try { - $this->roleService->delete($role); - $this->flashDeleted('Role'); - } catch (\InvalidArgumentException $exception) { - $this->flashError($exception->getMessage()); - } + $this->roleService->delete($role); + + $this->flashDeleted('Role'); return redirect()->route('admin.system.roles.index'); } diff --git a/app/Http/Requests/Admin/Hr/EmployeeRequest.php b/app/Http/Requests/Admin/Hr/EmployeeRequest.php index 4d4b238..5993eae 100644 --- a/app/Http/Requests/Admin/Hr/EmployeeRequest.php +++ b/app/Http/Requests/Admin/Hr/EmployeeRequest.php @@ -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(Role::assignableValues())], + 'role' => ['required', Rule::in(config('roles.assignable', []))], ]; if (! $isOwner) { @@ -61,10 +61,10 @@ public function attributes(): array 'gender' => 'jenis kelamin', 'birth_date' => 'tanggal lahir', 'address' => 'alamat', + 'role' => 'role', 'join_date' => 'tanggal bergabung', 'employment_status' => 'status kepegawaian', 'base_salary' => 'gaji pokok', - 'role' => 'role', ]; } } diff --git a/app/Http/Requests/Admin/Manage/CuttingStatusTransitionRequest.php b/app/Http/Requests/Admin/Manage/CuttingStatusTransitionRequest.php index d5436fd..e181b71 100644 --- a/app/Http/Requests/Admin/Manage/CuttingStatusTransitionRequest.php +++ b/app/Http/Requests/Admin/Manage/CuttingStatusTransitionRequest.php @@ -26,7 +26,10 @@ public function authorize(): bool || false; } - return $this->user()?->can($status->transitionPermission()->value) ?? false; + $permissions = config('cutting-status.permissions', $status); + $permission = $permissions[$status->value] ?? null; + + return $permission ? ($this->user()?->can($permission->value) ?? false) : false; } /** @@ -73,7 +76,10 @@ public function withValidator(Validator $validator): void return; } - if (! $cutting->status->canTransitionTo($status)) { + $transitions = config('cutting-status.transitions', []); + $allowedTargets = $transitions[$cutting->status->value] ?? []; + + if (! in_array($status, $allowedTargets, true)) { $validator->errors()->add('status', 'Status cutting tidak dapat diubah.'); } diff --git a/app/Http/Requests/Admin/Manage/OrderRequest.php b/app/Http/Requests/Admin/Manage/OrderRequest.php index 9095ca0..f1ff257 100644 --- a/app/Http/Requests/Admin/Manage/OrderRequest.php +++ b/app/Http/Requests/Admin/Manage/OrderRequest.php @@ -90,7 +90,7 @@ public function withValidator(Validator $validator): void /** @var Order $order */ $order = $this->route('order'); - if (! $order->status->isEditable()) { + if (! in_array($order->status, config('order-status.editable'), true)) { $validator->errors()->add('status', 'Pesanan tidak dapat diubah.'); } }); diff --git a/app/Http/Requests/Admin/Manage/OrderStatusTransitionRequest.php b/app/Http/Requests/Admin/Manage/OrderStatusTransitionRequest.php index 21291c8..b95234a 100644 --- a/app/Http/Requests/Admin/Manage/OrderStatusTransitionRequest.php +++ b/app/Http/Requests/Admin/Manage/OrderStatusTransitionRequest.php @@ -18,7 +18,10 @@ public function authorize(): bool return false; } - return $this->user()?->can($status->transitionPermission()->value) ?? false; + $permissions = config('order-status.permissions', []); + $permission = $permissions[$status->value] ?? null; + + return $permission ? ($this->user()?->can($permission->value) ?? false) : false; } /** @@ -52,7 +55,10 @@ public function withValidator(Validator $validator): void return; } - if (! $order->status->canTransitionTo($status)) { + $transitions = config('order-status.transitions', []); + $allowedTargets = $transitions[$order->status->value] ?? []; + + if (! in_array($status, $allowedTargets, true)) { $validator->errors()->add('status', 'Status pesanan tidak dapat diubah.'); } }); diff --git a/app/Services/Manage/CuttingService.php b/app/Services/Manage/CuttingService.php index 1ca62d5..73eb44c 100644 --- a/app/Services/Manage/CuttingService.php +++ b/app/Services/Manage/CuttingService.php @@ -3,6 +3,7 @@ namespace App\Services\Manage; use App\Enums\CuttingStatus; +use App\Enums\Permission; use App\Models\Cutting; use App\Models\CuttingMaterial; use App\Models\CuttingResult; @@ -27,6 +28,34 @@ public function __construct( private readonly PushNotificationService $pushNotificationService, ) {} + public function isEditable(CuttingStatus $status): bool + { + return in_array($status, config('cutting-status.editable'), true); + } + + public function canTransitionTo(CuttingStatus $from, CuttingStatus $to): bool + { + $transitions = config('cutting-status.transitions', []); + + return in_array($to, $transitions[$from->value] ?? [], true); + } + + public function transitionPermission(CuttingStatus $status): Permission + { + $permissions = config('cutting-status.permissions', []); + + return $permissions[$status->value] + ?? throw new \InvalidArgumentException('Status tidak mendukung transisi.'); + } + + /** + * @return list + */ + public function availableActions(CuttingStatus $status): array + { + return config('cutting-status.actions.'.$status->value, []); + } + /** * @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery */ @@ -61,13 +90,13 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi ->paginate(10) ->withQueryString() ->through(function (Cutting $cutting) use ($user) { - $actions = collect($cutting->status->availableActions()) + $actions = collect($this->availableActions($cutting->status)) ->filter(fn (array $action) => $user->can($action['permission'])) ->values() ->all(); $cutting->setAttribute('available_actions', $actions); - $cutting->setAttribute('is_editable', $cutting->status->isEditable()); + $cutting->setAttribute('is_editable', $this->isEditable($cutting->status)); $this->appendCostPreview($cutting); return $cutting; @@ -91,13 +120,13 @@ public function getInProgressCuttings(User $user): Collection ->latest() ->get() ->each(function (Cutting $cutting) use ($user): void { - $actions = collect($cutting->status->availableActions()) + $actions = collect($this->availableActions($cutting->status)) ->filter(fn (array $action) => $user->can($action['permission'])) ->values() ->all(); $cutting->setAttribute('available_actions', $actions); - $cutting->setAttribute('is_editable', $cutting->status->isEditable()); + $cutting->setAttribute('is_editable', $this->isEditable($cutting->status)); $this->appendCostPreview($cutting); }); } @@ -119,13 +148,13 @@ public function getCompletedCuttings(User $user): Collection ->latest() ->get() ->each(function (Cutting $cutting) use ($user): void { - $actions = collect($cutting->status->availableActions()) + $actions = collect($this->availableActions($cutting->status)) ->filter(fn (array $action) => $user->can($action['permission'])) ->values() ->all(); $cutting->setAttribute('available_actions', $actions); - $cutting->setAttribute('is_editable', $cutting->status->isEditable()); + $cutting->setAttribute('is_editable', $this->isEditable($cutting->status)); $this->appendCostPreview($cutting); }); } @@ -463,7 +492,7 @@ public function create(array $validated, User $user): Cutting */ public function update(Cutting $cutting, array $validated): void { - if (! $cutting->status->isEditable()) { + if (! $this->isEditable($cutting->status)) { throw ValidationException::withMessages([ 'status' => 'Proses cutting tidak dapat diubah.', ]); @@ -557,7 +586,7 @@ public function transitionStatus( ?array $results = null, ?array $resultPrices = null, ): void { - if (! $cutting->status->canTransitionTo($status)) { + if (! $this->canTransitionTo($cutting->status, $status)) { throw ValidationException::withMessages([ 'status' => 'Status proses cutting tidak dapat diubah.', ]); diff --git a/app/Services/Manage/OrderService.php b/app/Services/Manage/OrderService.php index f40c471..d10a96c 100644 --- a/app/Services/Manage/OrderService.php +++ b/app/Services/Manage/OrderService.php @@ -5,6 +5,7 @@ use App\Enums\OrderChannel; use App\Enums\OrderStatus; use App\Enums\PaymentType; +use App\Enums\Permission; use App\Enums\PriceType; use App\Enums\ProductStockQuality; use App\Models\Customer; @@ -33,6 +34,51 @@ public function __construct( private readonly CuttingResultPriceResolver $cuttingResultPriceResolver, ) {} + public function isEditable(OrderStatus $status): bool + { + return in_array($status, config('order-status.editable'), true); + } + + public function canTransitionTo(OrderStatus $from, OrderStatus $to): bool + { + $transitions = config('order-status.transitions', []); + + return in_array($to, $transitions[$from->value] ?? [], true); + } + + public function transitionPermission(OrderStatus $status): Permission + { + $permissions = config('order-status.permissions', []); + + return $permissions[$status->value] + ?? throw new \InvalidArgumentException('Status tidak mendukung transisi.'); + } + + /** + * @return list + */ + public function availableActions(OrderStatus $status): array + { + return config('order-status.actions.'.$status->value, []); + } + + public function defaultPriceType(OrderChannel $channel): ?PriceType + { + return match ($channel) { + OrderChannel::STORE => null, + OrderChannel::SHOPEE => PriceType::SHOPEE, + OrderChannel::TIKTOK => PriceType::TIKTOK, + }; + } + + public function stockColumn(ProductStockQuality $quality): string + { + return match ($quality) { + ProductStockQuality::GOOD => 'stock', + ProductStockQuality::REJECT => 'reject_stock', + }; + } + /** * @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery */ @@ -67,13 +113,13 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi ->paginate(10) ->withQueryString() ->through(function (Order $order) use ($user) { - $actions = collect($order->status->availableActions()) + $actions = collect($this->availableActions($order->status)) ->filter(fn (array $action) => $user->can($action['permission'])) ->values() ->all(); $order->setAttribute('available_actions', $actions); - $order->setAttribute('is_editable', $order->status->isEditable()); + $order->setAttribute('is_editable', $this->isEditable($order->status)); return $order; }); @@ -239,12 +285,12 @@ public function findForShow(Order $order): Order } }); - $availableActions = collect($order->status->availableActions()) + $availableActions = collect($this->availableActions($order->status)) ->values() ->all(); $order->setAttribute('available_actions', $availableActions); - $order->setAttribute('is_editable', $order->status->isEditable()); + $order->setAttribute('is_editable', $this->isEditable($order->status)); return $order; } @@ -453,7 +499,7 @@ public function create(array $validated, User $user): Order */ public function update(Order $order, array $validated): void { - if (! $order->status->isEditable()) { + if (! $this->isEditable($order->status)) { throw ValidationException::withMessages([ 'status' => 'Pesanan tidak dapat diubah.', ]); @@ -534,7 +580,7 @@ public function delete(Order $order): void DB::transaction(function () use ($order): void { $order->load('items'); - if ($order->status->isEditable()) { + if ($this->isEditable($order->status)) { foreach ($order->items as $item) { $this->incrementStock($item); } @@ -558,7 +604,7 @@ public function delete(Order $order): void public function transitionStatus(Order $order, OrderStatus $status): void { - if (! $order->status->canTransitionTo($status)) { + if (! $this->canTransitionTo($order->status, $status)) { throw ValidationException::withMessages([ 'status' => 'Status pesanan tidak dapat diubah.', ]); @@ -635,7 +681,7 @@ private function resolvePriceType(string $channel, string $priceType): PriceType $channelEnum = OrderChannel::from($channel); $priceTypeEnum = PriceType::from($priceType); - $defaultPriceType = $channelEnum->defaultPriceType(); + $defaultPriceType = $this->defaultPriceType($channelEnum); if ($defaultPriceType !== null && $priceTypeEnum !== $defaultPriceType) { throw ValidationException::withMessages([ @@ -705,7 +751,7 @@ private function decrementStock(OrderItem $item): void ProductVariant::query() ->whereKey($item->product_variant_id) - ->decrement($stockQuality->stockColumn(), $item->quantity); + ->decrement($this->stockColumn($stockQuality), $item->quantity); } private function incrementStock(OrderItem $item): void @@ -714,12 +760,12 @@ private function incrementStock(OrderItem $item): void ProductVariant::query() ->whereKey($item->product_variant_id) - ->increment($stockQuality->stockColumn(), $item->quantity); + ->increment($this->stockColumn($stockQuality), $item->quantity); } private function availableStock(ProductVariant $variant, ProductStockQuality $stockQuality): int { - return (int) $variant->{$stockQuality->stockColumn()}; + return (int) $variant->{$this->stockColumn($stockQuality)}; } /** diff --git a/app/Services/Master/RawMaterialService.php b/app/Services/Master/RawMaterialService.php index 1b85637..21b6f39 100644 --- a/app/Services/Master/RawMaterialService.php +++ b/app/Services/Master/RawMaterialService.php @@ -42,12 +42,18 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $st $query->whereHas('prices', fn (Builder $priceQuery) => $priceQuery->where('stock', '<=', 0)); }) ->when($stockStatus === 'low_stock', function (Builder $query): void { - $query->whereHas('prices', function (Builder $priceQuery): void { - $priceQuery->where('stock', '>', 0)->where(function (Builder $priceQuery): void { + $minStock = [ + RawMaterialUnit::YARD => 20, + RawMaterialUnit::METER => 10, + RawMaterialUnit::KILOGRAM => 5, + ]; + + $query->whereHas('prices', function (Builder $priceQuery) use ($minStock): void { + $priceQuery->where('stock', '>', 0)->where(function (Builder $priceQuery) use ($minStock): void { foreach (RawMaterialUnit::cases() as $unit) { - $priceQuery->orWhere(function (Builder $priceQuery) use ($unit): void { + $priceQuery->orWhere(function (Builder $priceQuery) use ($unit, $minStock): void { $priceQuery->whereHas('rawMaterial', fn (Builder $rawMaterialQuery) => $rawMaterialQuery->where('unit', $unit)) - ->where('stock', '<', $unit->minStock()); + ->where('stock', '<', $minStock[$unit]); }); } }); diff --git a/app/Traits/ProvidesEnumOptions.php b/app/Traits/ProvidesEnumOptions.php index 7ddc6d4..fe05dfa 100644 --- a/app/Traits/ProvidesEnumOptions.php +++ b/app/Traits/ProvidesEnumOptions.php @@ -22,4 +22,12 @@ public static function selectOptions(): array ]) ->all(); } + + /** + * @return list + */ + public static function values(): array + { + return array_column(static::cases(), 'value'); + } } diff --git a/database/factories/OrderFactory.php b/database/factories/OrderFactory.php index 1138831..091efe2 100644 --- a/database/factories/OrderFactory.php +++ b/database/factories/OrderFactory.php @@ -19,7 +19,14 @@ class OrderFactory extends Factory public function definition(): array { $channel = fake()->randomElement(OrderChannel::cases()); - $priceType = $channel->defaultPriceType() ?? fake()->randomElement(PriceType::cases()); + + $defaultPriceType = match ($channel) { + OrderChannel::SHOPEE => PriceType::SHOPEE, + OrderChannel::TIKTOK => PriceType::TIKTOK, + default => null, + }; + + $priceType = $defaultPriceType ?? fake()->randomElement(PriceType::cases()); $subtotal = fake()->numberBetween(100_000, 5_000_000); $discount = fake()->numberBetween(0, (int) ($subtotal * 0.15)); diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php index 438437f..cb35c54 100644 --- a/database/seeders/RolePermissionSeeder.php +++ b/database/seeders/RolePermissionSeeder.php @@ -23,11 +23,15 @@ public function run(): void $registrar->forgetCachedPermissions(); + $rolePermissions = config('roles.permissions'); + foreach (RoleEnum::cases() as $role) { $roleModel = Role::findOrCreate($role->value, 'web'); + $rolePermissionsList = $rolePermissions[$role->value] ?? []; + $roleModel->syncPermissions( - collect($role->permissions()) + collect($rolePermissionsList) ->map(fn (PermissionEnum $permission) => $permissions[$permission->value]) ->all() ); diff --git a/resources/js/composables/useAppearance.ts b/resources/js/composables/useAppearance.ts index f6db0e1..2bb5a30 100644 --- a/resources/js/composables/useAppearance.ts +++ b/resources/js/composables/useAppearance.ts @@ -1,11 +1,12 @@ -import type { AppearanceMode } from '@/types/account'; +import { AppearanceMode } from '@/constants/appearance-mode'; +import type { AppearanceMode as AppearanceModeType } from '@/types/account'; -export function applyAppearance(mode: AppearanceMode): void { +export function applyAppearance(mode: AppearanceModeType): void { const root = document.documentElement; root.classList.remove('dark'); - if (mode === 'dark' || (mode === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) { + if (mode === AppearanceMode.DARK || (mode === AppearanceMode.SYSTEM && window.matchMedia('(prefers-color-scheme: dark)').matches)) { root.classList.add('dark'); } } diff --git a/resources/js/constants/active-status.ts b/resources/js/constants/active-status.ts new file mode 100644 index 0000000..2a35d4e --- /dev/null +++ b/resources/js/constants/active-status.ts @@ -0,0 +1,4 @@ +export const ActiveStatus = { + ACTIVE: '1', + INACTIVE: '0', +} as const; diff --git a/resources/js/constants/appearance-mode.ts b/resources/js/constants/appearance-mode.ts new file mode 100644 index 0000000..fd98c14 --- /dev/null +++ b/resources/js/constants/appearance-mode.ts @@ -0,0 +1,5 @@ +export const AppearanceMode = { + LIGHT: 'light', + DARK: 'dark', + SYSTEM: 'system', +} as const; diff --git a/resources/js/constants/cash-reference-type.ts b/resources/js/constants/cash-reference-type.ts new file mode 100644 index 0000000..759bafe --- /dev/null +++ b/resources/js/constants/cash-reference-type.ts @@ -0,0 +1,7 @@ +export const CashReferenceType = { + MANUAL: 'manual', + EXPENSE: 'App\\Models\\Expense', + EMPLOYEE_ADVANCE: 'App\\Models\\EmployeeAdvance', + PAYROLL: 'App\\Models\\Payroll', + ORDER: 'App\\Models\\Order', +} as const; diff --git a/resources/js/constants/cash-transaction-type.ts b/resources/js/constants/cash-transaction-type.ts new file mode 100644 index 0000000..2d03aa4 --- /dev/null +++ b/resources/js/constants/cash-transaction-type.ts @@ -0,0 +1,4 @@ +export const CashTransactionType = { + DEPOSIT: 'deposit', + WITHDRAWAL: 'withdrawal', +} as const; diff --git a/resources/js/constants/catalog-sort-option.ts b/resources/js/constants/catalog-sort-option.ts new file mode 100644 index 0000000..9958808 --- /dev/null +++ b/resources/js/constants/catalog-sort-option.ts @@ -0,0 +1,6 @@ +export const CatalogSortOption = { + DEFAULT: 'default', + PRICE_ASC: 'price_asc', + PRICE_DESC: 'price_desc', + NAME_ASC: 'name_asc', +} as const; diff --git a/resources/js/constants/cutting-status.ts b/resources/js/constants/cutting-status.ts new file mode 100644 index 0000000..741e4c0 --- /dev/null +++ b/resources/js/constants/cutting-status.ts @@ -0,0 +1,6 @@ +export const CuttingStatus = { + IN_PROGRESS: 'in_progress', + COMPLETED: 'completed', + VERIFIED: 'verified', + REJECTED: 'rejected', +} as const; diff --git a/resources/js/constants/employee-advance-status.ts b/resources/js/constants/employee-advance-status.ts new file mode 100644 index 0000000..72b2ee5 --- /dev/null +++ b/resources/js/constants/employee-advance-status.ts @@ -0,0 +1,6 @@ +export const EmployeeAdvanceStatus = { + PENDING: 'pending', + APPROVED: 'approved', + REJECTED: 'rejected', + PAID: 'paid', +} as const; diff --git a/resources/js/constants/index.ts b/resources/js/constants/index.ts new file mode 100644 index 0000000..df8d09f --- /dev/null +++ b/resources/js/constants/index.ts @@ -0,0 +1,19 @@ +export * from './order-channel'; +export * from './order-status'; +export * from './cutting-status'; +export * from './employee-advance-status'; +export * from './leave-request-status'; +export * from './payroll-status'; +export * from './cash-transaction-type'; +export * from './cash-reference-type'; +export * from './order-payment-type'; +export * from './stock-quality'; +export * from './stock-status'; +export * from './active-status'; +export * from './marketplace-platform'; +export * from './marketplace-fee-scope'; +export * from './marketplace-fee-value-type'; +export * from './setting-section'; +export * from './appearance-mode'; +export * from './catalog-sort-option'; +export * from './paper-size'; diff --git a/resources/js/constants/leave-request-status.ts b/resources/js/constants/leave-request-status.ts new file mode 100644 index 0000000..cbf0d2f --- /dev/null +++ b/resources/js/constants/leave-request-status.ts @@ -0,0 +1,5 @@ +export const LeaveRequestStatus = { + PENDING: 'pending', + APPROVED: 'approved', + REJECTED: 'rejected', +} as const; diff --git a/resources/js/constants/marketplace-fee-scope.ts b/resources/js/constants/marketplace-fee-scope.ts new file mode 100644 index 0000000..e87e9fe --- /dev/null +++ b/resources/js/constants/marketplace-fee-scope.ts @@ -0,0 +1,4 @@ +export const MarketplaceFeeScope = { + PRODUCT: 'product', + TRANSACTION: 'transaction', +} as const; diff --git a/resources/js/constants/marketplace-fee-value-type.ts b/resources/js/constants/marketplace-fee-value-type.ts new file mode 100644 index 0000000..557c446 --- /dev/null +++ b/resources/js/constants/marketplace-fee-value-type.ts @@ -0,0 +1,4 @@ +export const MarketplaceFeeValueType = { + FLAT: 'flat', + PERCENT: 'percent', +} as const; diff --git a/resources/js/constants/marketplace-platform.ts b/resources/js/constants/marketplace-platform.ts new file mode 100644 index 0000000..ae93399 --- /dev/null +++ b/resources/js/constants/marketplace-platform.ts @@ -0,0 +1,4 @@ +export const MarketplacePlatform = { + TIKTOK_SHOP: 'tiktok_shop', + SHOPEE: 'shopee', +} as const; diff --git a/resources/js/constants/order-payment-type.ts b/resources/js/constants/order-payment-type.ts new file mode 100644 index 0000000..fd94673 --- /dev/null +++ b/resources/js/constants/order-payment-type.ts @@ -0,0 +1,4 @@ +export const OrderPaymentType = { + CASH: 'cash', + MARKETPLACE: 'marketplace', +} as const; diff --git a/resources/js/constants/order-status.ts b/resources/js/constants/order-status.ts new file mode 100644 index 0000000..fccbfcf --- /dev/null +++ b/resources/js/constants/order-status.ts @@ -0,0 +1,5 @@ +export const OrderStatus = { + PROCESSING: 'processing', + COMPLETED: 'completed', + CANCELLED: 'cancelled', +} as const; diff --git a/resources/js/constants/paper-size.ts b/resources/js/constants/paper-size.ts new file mode 100644 index 0000000..6a1344c --- /dev/null +++ b/resources/js/constants/paper-size.ts @@ -0,0 +1,4 @@ +export const PaperSize = { + MM_58: '58mm', + MM_80: '80mm', +} as const; diff --git a/resources/js/constants/payroll-status.ts b/resources/js/constants/payroll-status.ts new file mode 100644 index 0000000..53f28f9 --- /dev/null +++ b/resources/js/constants/payroll-status.ts @@ -0,0 +1,4 @@ +export const PayrollStatus = { + UNPAID: 'unpaid', + PAID: 'paid', +} as const; diff --git a/resources/js/constants/setting-section.ts b/resources/js/constants/setting-section.ts new file mode 100644 index 0000000..d7aeb2b --- /dev/null +++ b/resources/js/constants/setting-section.ts @@ -0,0 +1,7 @@ +export const SettingSection = { + SYSTEM: 'system', + SOCIAL: 'social', + MARKETPLACE: 'marketplace', + HR: 'hr', + HOMEPAGE: 'homepage', +} as const; diff --git a/resources/js/constants/stock-quality.ts b/resources/js/constants/stock-quality.ts new file mode 100644 index 0000000..d94fb5d --- /dev/null +++ b/resources/js/constants/stock-quality.ts @@ -0,0 +1,4 @@ +export const StockQuality = { + GOOD: 'good', + REJECT: 'reject', +} as const; diff --git a/resources/js/constants/stock-status.ts b/resources/js/constants/stock-status.ts new file mode 100644 index 0000000..5000fc4 --- /dev/null +++ b/resources/js/constants/stock-status.ts @@ -0,0 +1,4 @@ +export const StockStatus = { + OUT_OF_STOCK: 'out_of_stock', + LOW_STOCK: 'low_stock', +} as const; diff --git a/resources/js/layouts/SettingLayout.vue b/resources/js/layouts/SettingLayout.vue index d00f51b..a10a68f 100644 --- a/resources/js/layouts/SettingLayout.vue +++ b/resources/js/layouts/SettingLayout.vue @@ -1,5 +1,6 @@ diff --git a/resources/js/pages/Home.vue b/resources/js/pages/Home.vue index 832b619..e8de1f0 100644 --- a/resources/js/pages/Home.vue +++ b/resources/js/pages/Home.vue @@ -18,6 +18,7 @@ import { Filter } from '@lucide/vue'; import { ref, computed } from 'vue'; +import { CatalogSortOption } from '@/constants/catalog-sort-option'; import RupiahInput from '@/components/form/rupiah-input/RupiahInput.vue'; import Select from '@/components/ui/select/Select.vue'; import SelectContent from '@/components/ui/select/SelectContent.vue'; @@ -97,7 +98,7 @@ const searchInput = ref(''); const selectedCategory = ref(null); const minPrice = ref(null); const maxPrice = ref(null); -const sortBy = ref('default'); // 'default', 'price_asc', 'price_desc', 'name_asc' +const sortBy = ref(CatalogSortOption.DEFAULT); const showFilters = ref(false); // Hardcoded order steps @@ -155,11 +156,11 @@ const filteredProducts = computed(() => { }); // Sorting - if (sortBy.value === 'price_asc') { + if (sortBy.value === CatalogSortOption.PRICE_ASC) { result.sort((a, b) => getPrimaryEcerPrice(a) - getPrimaryEcerPrice(b)); - } else if (sortBy.value === 'price_desc') { + } else if (sortBy.value === CatalogSortOption.PRICE_DESC) { result.sort((a, b) => getPrimaryEcerPrice(b) - getPrimaryEcerPrice(a)); - } else if (sortBy.value === 'name_asc') { + } else if (sortBy.value === CatalogSortOption.NAME_ASC) { result.sort((a, b) => a.name.localeCompare(b.name)); } @@ -359,7 +360,7 @@ return; const resetPriceFilters = () => { minPrice.value = null; maxPrice.value = null; - sortBy.value = 'default'; + sortBy.value = CatalogSortOption.DEFAULT; }; @@ -645,10 +646,10 @@ const resetPriceFilters = () => { - Default (Urutan Utama) - Harga Terendah - Harga Tertinggi - Nama Produk (A-Z) + Default (Urutan Utama) + Harga Terendah + Harga Tertinggi + Nama Produk (A-Z) diff --git a/resources/js/pages/admin/account/Appearance.vue b/resources/js/pages/admin/account/Appearance.vue index dbfb137..4d6f3ef 100644 --- a/resources/js/pages/admin/account/Appearance.vue +++ b/resources/js/pages/admin/account/Appearance.vue @@ -13,6 +13,7 @@ import { import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { applyAppearance } from '@/composables/useAppearance'; +import { AppearanceMode as AppearanceModeConst } from '@/constants/appearance-mode'; import AccountLayout from '@/layouts/AccountLayout.vue'; import { formErrors } from '@/lib/form'; import { cn } from '@/lib/utils'; @@ -33,19 +34,19 @@ const options: Array<{ icon: typeof Sun; }> = [ { - value: 'light', + value: AppearanceModeConst.LIGHT, label: 'Terang', description: 'Tampilan terang untuk lingkungan yang cukup pencahayaan.', icon: Sun, }, { - value: 'dark', + value: AppearanceModeConst.DARK, label: 'Gelap', description: 'Tampilan gelap yang nyaman di malam hari.', icon: Moon, }, { - value: 'system', + value: AppearanceModeConst.SYSTEM, label: 'Sistem', description: 'Mengikuti pengaturan tampilan perangkat Anda.', icon: Monitor, diff --git a/resources/js/pages/admin/finance/cash/Index.vue b/resources/js/pages/admin/finance/cash/Index.vue index 28474e5..980c992 100644 --- a/resources/js/pages/admin/finance/cash/Index.vue +++ b/resources/js/pages/admin/finance/cash/Index.vue @@ -2,16 +2,18 @@ import { Head } from '@inertiajs/vue3'; import { ArrowDownCircle, ArrowUpCircle, Wallet } from '@lucide/vue'; import { computed, ref, watch } from 'vue'; -import CashTransactionFormModal from './form/CashTransactionFormModal.vue'; -import { createColumns } from './table/columns'; import { DataTable } from '@/components/data-table'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { useCan } from '@/composables/useCan'; +import { CashReferenceType } from '@/constants/cash-reference-type'; +import { CashTransactionType } from '@/constants/cash-transaction-type'; import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery'; import AdminLayout from '@/layouts/AdminLayout.vue'; import type { CashAccount, CashTransactionListItem, PaginatedCashTransactions } from '@/types/cash'; import type { DataTableFilterDef, DataTableSort } from '@/types/data-table'; +import CashTransactionFormModal from './form/CashTransactionFormModal.vue'; +import { createColumns } from './table/columns'; const props = defineProps<{ cashAccount: CashAccount; @@ -27,7 +29,7 @@ const props = defineProps<{ const { can } = useCan(); const search = ref(props.filters.search ?? ''); const formModalOpen = ref(false); -const formModalMode = ref<'deposit' | 'withdrawal'>('deposit'); +const formModalMode = ref<'deposit' | 'withdrawal'>(CashTransactionType.DEPOSIT); const editingTransaction = ref(null); const { query, setSearch, setSort, setFilter, resetFilters, syncFromServer } = useDataTableQuery({ @@ -57,11 +59,11 @@ const filterDefs = computed(() => [ label: 'Sumber', type: 'select', options: [ - { value: 'manual', label: 'Setor/Tarik Kas' }, - { value: 'App\\Models\\Expense', label: 'Pengeluaran' }, - { value: 'App\\Models\\EmployeeAdvance', label: 'Kasbon Pegawai' }, - { value: 'App\\Models\\Payroll', label: 'Gaji Pegawai' }, - { value: 'App\\Models\\Order', label: 'Pesanan' }, + { value: CashReferenceType.MANUAL, label: 'Setor/Tarik Kas' }, + { value: CashReferenceType.EXPENSE, label: 'Pengeluaran' }, + { value: CashReferenceType.EMPLOYEE_ADVANCE, label: 'Kasbon Pegawai' }, + { value: CashReferenceType.PAYROLL, label: 'Gaji Pegawai' }, + { value: CashReferenceType.ORDER, label: 'Pesanan' }, ], }, ]); @@ -77,7 +79,7 @@ const pagination = computed(() => ({ total: props.transactions.total, })); -function openCreateModal(mode: 'deposit' | 'withdrawal') { +function openCreateModal(mode: typeof CashTransactionType.DEPOSIT | typeof CashTransactionType.WITHDRAWAL) { editingTransaction.value = null; formModalMode.value = mode; formModalOpen.value = true; @@ -113,11 +115,11 @@ watch(
- - diff --git a/resources/js/pages/admin/finance/cash/form/CashTransactionFormModal.vue b/resources/js/pages/admin/finance/cash/form/CashTransactionFormModal.vue index 3c10c06..f5903b7 100644 --- a/resources/js/pages/admin/finance/cash/form/CashTransactionFormModal.vue +++ b/resources/js/pages/admin/finance/cash/form/CashTransactionFormModal.vue @@ -22,6 +22,7 @@ import { } from '@/components/ui/field'; import { Textarea } from '@/components/ui/textarea'; import { useFormDialog } from '@/composables/useFormDialog'; +import { CashTransactionType } from '@/constants/cash-transaction-type'; import { FIELD_LIMITS } from '@/lib/field-limits'; import { formErrors } from '@/lib/form'; import { parseRupiah } from '@/lib/rupiah'; @@ -41,9 +42,9 @@ const currentMode = computed(() => { return props.transaction.type; } - return props.mode ?? 'deposit'; + return props.mode ?? CashTransactionType.DEPOSIT; }); -const isWithdrawal = computed(() => currentMode.value === 'withdrawal'); +const isWithdrawal = computed(() => currentMode.value === CashTransactionType.WITHDRAWAL); const existingPhotoId = ref(null); diff --git a/resources/js/pages/admin/finance/employee-advances/Index.vue b/resources/js/pages/admin/finance/employee-advances/Index.vue index b015711..dc81990 100644 --- a/resources/js/pages/admin/finance/employee-advances/Index.vue +++ b/resources/js/pages/admin/finance/employee-advances/Index.vue @@ -9,6 +9,7 @@ import { DataTable } from '@/components/data-table'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { useCan } from '@/composables/useCan'; +import { EmployeeAdvanceStatus } from '@/constants/employee-advance-status'; import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery'; import AdminLayout from '@/layouts/AdminLayout.vue'; import type { DataTableFilterDef, DataTableSort } from '@/types/data-table'; @@ -54,10 +55,10 @@ const filterDefs = computed(() => [ label: 'Status', type: 'select', options: [ - { value: 'pending', label: 'Menunggu' }, - { value: 'approved', label: 'Disetujui' }, - { value: 'rejected', label: 'Ditolak' }, - { value: 'paid', label: 'Lunas' }, + { value: EmployeeAdvanceStatus.PENDING, label: 'Menunggu' }, + { value: EmployeeAdvanceStatus.APPROVED, label: 'Disetujui' }, + { value: EmployeeAdvanceStatus.REJECTED, label: 'Ditolak' }, + { value: EmployeeAdvanceStatus.PAID, label: 'Lunas' }, ], }, ]); diff --git a/resources/js/pages/admin/finance/employee-advances/table/columns.ts b/resources/js/pages/admin/finance/employee-advances/table/columns.ts index 68e3514..07a17f9 100644 --- a/resources/js/pages/admin/finance/employee-advances/table/columns.ts +++ b/resources/js/pages/admin/finance/employee-advances/table/columns.ts @@ -2,16 +2,17 @@ import type { ColumnDef } from '@tanstack/vue-table'; import { h } from 'vue'; import { DataTableColumnHeader } from '@/components/data-table'; import { Badge } from '@/components/ui/badge'; +import { EmployeeAdvanceStatus } from '@/constants/employee-advance-status'; import type { EmployeeAdvanceListItem } from '@/types/employee-advance'; import DataTableActions from './data-table-actions.vue'; function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' { switch (status) { - case 'approved': + case EmployeeAdvanceStatus.APPROVED: return 'default'; - case 'paid': + case EmployeeAdvanceStatus.PAID: return 'secondary'; - case 'rejected': + case EmployeeAdvanceStatus.REJECTED: return 'destructive'; default: return 'outline'; diff --git a/resources/js/pages/admin/finance/payroll/table/columns.ts b/resources/js/pages/admin/finance/payroll/table/columns.ts index ea54698..ad17100 100644 --- a/resources/js/pages/admin/finance/payroll/table/columns.ts +++ b/resources/js/pages/admin/finance/payroll/table/columns.ts @@ -2,11 +2,12 @@ import type { ColumnDef } from '@tanstack/vue-table'; import { h } from 'vue'; import { DataTableColumnHeader } from '@/components/data-table'; import { Badge } from '@/components/ui/badge'; +import { PayrollStatus } from '@/constants/payroll-status'; import type { PayrollListItem } from '@/types/payroll'; import DataTableActions from './data-table-actions.vue'; function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' { - return status === 'paid' ? 'secondary' : 'outline'; + return status === PayrollStatus.PAID ? 'secondary' : 'outline'; } export function createColumns( diff --git a/resources/js/pages/admin/hr/employees/Index.vue b/resources/js/pages/admin/hr/employees/Index.vue index 4009311..70b0f47 100644 --- a/resources/js/pages/admin/hr/employees/Index.vue +++ b/resources/js/pages/admin/hr/employees/Index.vue @@ -7,6 +7,7 @@ import { DataTable } from '@/components/data-table'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { useCan } from '@/composables/useCan'; +import { ActiveStatus } from '@/constants/active-status'; import { useDataTableQuery, useDataTableQuerySync, @@ -66,8 +67,8 @@ const filterDefs = computed(() => [ label: 'Status Akun', type: 'select', options: [ - { value: '1', label: 'Aktif' }, - { value: '0', label: 'Nonaktif' }, + { value: ActiveStatus.ACTIVE, label: 'Aktif' }, + { value: ActiveStatus.INACTIVE, label: 'Nonaktif' }, ], }, ]); diff --git a/resources/js/pages/admin/hr/leave-requests/table/columns.ts b/resources/js/pages/admin/hr/leave-requests/table/columns.ts index 180f1fc..5bc67f0 100644 --- a/resources/js/pages/admin/hr/leave-requests/table/columns.ts +++ b/resources/js/pages/admin/hr/leave-requests/table/columns.ts @@ -2,14 +2,15 @@ import type { ColumnDef } from '@tanstack/vue-table'; import { h } from 'vue'; import { DataTableColumnHeader } from '@/components/data-table'; import { Badge } from '@/components/ui/badge'; +import { LeaveRequestStatus } from '@/constants/leave-request-status'; import type { LeaveRequestListItem } from '@/types/leave-request'; import DataTableActions from './data-table-actions.vue'; function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' { switch (status) { - case 'approved': + case LeaveRequestStatus.APPROVED: return 'default'; - case 'rejected': + case LeaveRequestStatus.REJECTED: return 'destructive'; default: return 'outline'; diff --git a/resources/js/pages/admin/manage/cuttings/table/CuttingGroupedTable.vue b/resources/js/pages/admin/manage/cuttings/table/CuttingGroupedTable.vue index 50354dc..3ef3733 100644 --- a/resources/js/pages/admin/manage/cuttings/table/CuttingGroupedTable.vue +++ b/resources/js/pages/admin/manage/cuttings/table/CuttingGroupedTable.vue @@ -2,6 +2,7 @@ import { Link } from '@inertiajs/vue3'; import { computed } from 'vue'; import DataTableActions from './data-table-actions.vue'; +import { CuttingStatus } from '@/constants/cutting-status'; import DataTableToolbar from '@/components/data-table/DataTableToolbar.vue'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; @@ -59,15 +60,15 @@ function rowNumber(index: number): number { } function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' { - if (status === 'verified') { + if (status === CuttingStatus.VERIFIED) { return 'default'; } - if (status === 'rejected') { + if (status === CuttingStatus.REJECTED) { return 'destructive'; } - if (status === 'completed') { + if (status === CuttingStatus.COMPLETED) { return 'secondary'; } diff --git a/resources/js/pages/admin/manage/cuttings/table/data-table-actions.vue b/resources/js/pages/admin/manage/cuttings/table/data-table-actions.vue index 169de15..de71058 100644 --- a/resources/js/pages/admin/manage/cuttings/table/data-table-actions.vue +++ b/resources/js/pages/admin/manage/cuttings/table/data-table-actions.vue @@ -30,6 +30,7 @@ import { TooltipTrigger, } from '@/components/ui/tooltip'; import { useCan } from '@/composables/useCan'; +import { CuttingStatus } from '@/constants/cutting-status'; import { RupiahInput } from '@/components/form/rupiah-input'; import { parseRupiah } from '@/lib/rupiah'; import { @@ -54,7 +55,7 @@ const allMatches = ref(true); const pendingAction = ref(null); const rejectForm = useForm({ - status: 'rejected', + status: CuttingStatus.REJECTED, reason: '', }); @@ -63,7 +64,7 @@ function buildEmptyPrices(): Record { } const verifyForm = useForm({ - status: 'verified', + status: CuttingStatus.VERIFIED, verification_note: '', results: props.cutting.results.map(res => ({ product_variant_id: res.product_variant?.id || 0, @@ -151,7 +152,7 @@ const canEdit = computed( () => props.cutting.is_editable && can('cuttings.update'), ); const canDelete = computed( - () => can('cuttings.delete') && ['in_progress', 'rejected'].includes(props.cutting.status), + () => can('cuttings.delete') && [CuttingStatus.IN_PROGRESS, CuttingStatus.REJECTED].includes(props.cutting.status), ); function canPerformAction(action: CuttingStatusAction): boolean { @@ -159,15 +160,15 @@ function canPerformAction(action: CuttingStatusAction): boolean { } function statusConfirmDescription(action: CuttingStatusAction): string { - if (action.status === 'completed') { + if (action.status === CuttingStatus.COMPLETED) { return 'Cutting akan ditandai selesai. Stok bahan baku akan dipotong dan sisa dikembalikan. Menunggu verifikasi admin toko.'; } - if (action.status === 'verified') { + if (action.status === CuttingStatus.VERIFIED) { return 'Hasil cutting akan diverifikasi. Stok bagus dan reject akan ditambahkan ke produk.'; } - if (action.status === 'in_progress') { + if (action.status === CuttingStatus.IN_PROGRESS) { return 'Cutting dikembalikan ke proses untuk diperbaiki.'; } @@ -175,13 +176,13 @@ function statusConfirmDescription(action: CuttingStatusAction): string { } function openStatusConfirm(action: CuttingStatusAction) { - if (action.status === 'verified') { + if (action.status === CuttingStatus.VERIFIED) { verifyDialogOpen.value = true; return; } - if (action.status === 'rejected') { + if (action.status === CuttingStatus.REJECTED) { rejectForm.reset(); rejectForm.clearErrors(); rejectDialogOpen.value = true; @@ -299,15 +300,15 @@ function destroyCutting() { } function actionIcon(status: string) { - if (status === 'completed') { + if (status === CuttingStatus.COMPLETED) { return Scissors; } - if (status === 'verified') { + if (status === CuttingStatus.VERIFIED) { return Check; } - if (status === 'in_progress') { + if (status === CuttingStatus.IN_PROGRESS) { return RotateCcw; } diff --git a/resources/js/pages/admin/manage/orders/Show.vue b/resources/js/pages/admin/manage/orders/Show.vue index 6bfa2f6..7845673 100644 --- a/resources/js/pages/admin/manage/orders/Show.vue +++ b/resources/js/pages/admin/manage/orders/Show.vue @@ -32,6 +32,10 @@ import { TableRow, } from '@/components/ui/table'; import { useCan } from '@/composables/useCan'; +import { MarketplaceFeeScope } from '@/constants/marketplace-fee-scope'; +import { MarketplaceFeeValueType } from '@/constants/marketplace-fee-value-type'; +import { OrderStatus } from '@/constants/order-status'; +import { StockQuality } from '@/constants/stock-quality'; import AdminLayout from '@/layouts/AdminLayout.vue'; import type { OrderDetail, OrderStatusAction } from '@/types/order'; @@ -59,15 +63,15 @@ const marketingName = computed(() => { }); function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' { - if (status === 'completed') return 'default'; - if (status === 'cancelled') return 'destructive'; - if (status === 'processing') return 'secondary'; + if (status === OrderStatus.COMPLETED) return 'default'; + if (status === OrderStatus.CANCELLED) return 'destructive'; + if (status === OrderStatus.PROCESSING) return 'secondary'; return 'outline'; } function actionIcon(status: string) { - if (status === 'processing') return Send; - if (status === 'completed') return Check; + if (status === OrderStatus.PROCESSING) return Send; + if (status === OrderStatus.COMPLETED) return Check; return X; } @@ -76,10 +80,10 @@ function canPerformAction(action: OrderStatusAction): boolean { } function statusConfirmDescription(action: OrderStatusAction): string { - if (action.status === 'processing') { + if (action.status === OrderStatus.PROCESSING) { return `Pesanan ${props.order.order_number} akan dikirim dan diproses.`; } - if (action.status === 'completed') { + if (action.status === OrderStatus.COMPLETED) { return `Pesanan ${props.order.order_number} akan ditandai selesai.`; } return `Pesanan ${props.order.order_number} akan dibatalkan. Stok produk akan dikembalikan.`; @@ -182,9 +186,9 @@ function formatRp(amount: number): string { } function formatFeeRule(fee: { scope: string; value_type: string; value: number }): string { - const scope = fee.scope === 'product' ? '/produk' : '/transaksi'; + const scope = fee.scope === MarketplaceFeeScope.PRODUCT ? '/produk' : '/transaksi'; - if (fee.value_type === 'percent') { + if (fee.value_type === MarketplaceFeeValueType.PERCENT) { return `${fee.value}%${scope}`; } @@ -374,7 +378,7 @@ const feeEntries = computed(() => { - {{ item.stock_quality === 'reject' ? 'Reject' : 'Bagus' }} + {{ item.stock_quality === StockQuality.REJECT ? 'Reject' : 'Bagus' }} diff --git a/resources/js/pages/admin/manage/orders/form/OrderPosForm.vue b/resources/js/pages/admin/manage/orders/form/OrderPosForm.vue index 3fa06d1..6fbfe70 100644 --- a/resources/js/pages/admin/manage/orders/form/OrderPosForm.vue +++ b/resources/js/pages/admin/manage/orders/form/OrderPosForm.vue @@ -41,6 +41,9 @@ import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; import { useCan } from '@/composables/useCan'; import { OrderChannel } from '@/constants/order-channel'; +import { OrderPaymentType } from '@/constants/order-payment-type'; +import { PaperSize } from '@/constants/paper-size'; +import { StockQuality } from '@/constants/stock-quality'; import { apiFetch } from '@/lib/api'; import { getFirstCoverImage } from '@/lib/catalog-cover'; import { formErrors } from '@/lib/form'; @@ -95,12 +98,12 @@ const isMarketingUser = computed(() => ); const search = ref(''); -const selectedStockQuality = ref<'good' | 'reject'>('good'); +const selectedStockQuality = ref<'good' | 'reject'>(StockQuality.GOOD); const cart = ref([]); const customerFormOpen = ref(false); const cartDetailOpen = ref(false); const printAfterSave = ref(false); -const selectedPaperSize = ref<'58mm' | '80mm'>('80mm'); +const selectedPaperSize = ref<'58mm' | '80mm'>(PaperSize.MM_80); function onCustomerCreated(customer: { id: number; name: string }) { emit('customer-created', customer); @@ -117,7 +120,7 @@ const form = useForm({ marketing_id: defaultMarketingId.value || 'none', channel: 'store', price_type: 'ecer', - payment_type: 'cash', + payment_type: OrderPaymentType.CASH, is_affiliate: false, tiktok_order_id: '', shopee_order_id: '', @@ -127,7 +130,7 @@ const form = useForm({ }); const isStoreChannel = computed(() => form.channel === 'store'); -const isMarketplaceChannel = computed(() => form.channel === 'shopee' || form.channel === 'tiktok'); +const isMarketplaceChannel = computed(() => form.channel === OrderChannel.SHOPEE || form.channel === OrderChannel.TIKTOK); function populateForm() { if (!props.initialData) { @@ -147,7 +150,7 @@ function populateForm() { form.notes = props.initialData.notes; cart.value = props.initialData.items.map((item) => ({ ...item, - stock_quality: item.stock_quality ?? 'good', + stock_quality: item.stock_quality ?? StockQuality.GOOD, })); } @@ -172,12 +175,12 @@ populateDraftItems(); watch( () => form.channel, (channel) => { - if (channel === 'shopee') { + if (channel === OrderChannel.SHOPEE) { form.price_type = 'shopee'; - form.payment_type = 'marketplace'; - } else if (channel === 'tiktok') { + form.payment_type = OrderPaymentType.MARKETPLACE; + } else if (channel === OrderChannel.TIKTOK) { form.price_type = 'tiktok'; - form.payment_type = 'marketplace'; + form.payment_type = OrderPaymentType.MARKETPLACE; } else if (!props.storePriceTypes.some((type) => type.value === form.price_type)) { form.price_type = 'ecer'; } @@ -228,7 +231,7 @@ function upsertCartItem(item: OrderCartItem) { } function availableStockForVariant(variant: ProductVariantItem, stockQuality: string): number { - return stockQuality === 'reject' ? variant.reject_stock : variant.stock; + return stockQuality === StockQuality.REJECT ? variant.reject_stock : variant.stock; } const filteredCatalog = computed(() => { @@ -309,7 +312,7 @@ async function addToCart(product: OrderCatalogItem, variant: ProductVariantItem) const availableStock = availableStockForVariant(variant, stockQuality); if (availableStock < 1) { - toast.error(`Stok ${stockQuality === 'reject' ? 'reject' : 'bagus'} tidak tersedia.`); + toast.error(`Stok ${stockQuality === StockQuality.REJECT ? 'reject' : 'bagus'} tidak tersedia.`); return; } @@ -322,7 +325,7 @@ async function addToCart(product: OrderCatalogItem, variant: ProductVariantItem) const nextQty = existing ? (Number(existing.quantity) || 0) + 1 : 1; if (nextQty > availableStock) { - toast.error(`Stok ${stockQuality === 'reject' ? 'reject' : 'bagus'} tidak mencukupi.`); + toast.error(`Stok ${stockQuality === StockQuality.REJECT ? 'reject' : 'bagus'} tidak mencukupi.`); return; } @@ -441,15 +444,15 @@ function buildFormData(): FormData { formData.append('price_type', form.price_type); formData.append('payment_type', form.payment_type); - if (form.channel !== 'store') { + if (!isStoreChannel.value) { formData.append('is_affiliate', form.is_affiliate ? '1' : '0'); } - if (form.channel === 'tiktok' && form.tiktok_order_id) { + if (form.channel === OrderChannel.TIKTOK && form.tiktok_order_id) { formData.append('tiktok_order_id', form.tiktok_order_id); } - if (form.channel === 'shopee' && form.shopee_order_id) { + if (form.channel === OrderChannel.SHOPEE && form.shopee_order_id) { formData.append('shopee_order_id', form.shopee_order_id); } @@ -645,7 +648,7 @@ function submit() {
- + ID Pesanan TikTok Shop @@ -654,7 +657,7 @@ function submit() { - + ID Pesanan Shopee @@ -763,7 +766,7 @@ function submit() {

{{ item.variant_name }} · - {{ item.stock_quality_label ?? (item.stock_quality === 'reject' + {{ item.stock_quality_label ?? (item.stock_quality === StockQuality.REJECT ? 'Reject' : 'Bagus') }}

@@ -843,7 +846,7 @@ function submit() {
Ukuran kertas:
-
diff --git a/resources/js/pages/admin/manage/orders/table/OrderGroupedTable.vue b/resources/js/pages/admin/manage/orders/table/OrderGroupedTable.vue index f61700b..5a2b75a 100644 --- a/resources/js/pages/admin/manage/orders/table/OrderGroupedTable.vue +++ b/resources/js/pages/admin/manage/orders/table/OrderGroupedTable.vue @@ -3,6 +3,7 @@ import { Link } from '@inertiajs/vue3'; import { computed } from 'vue'; import DataTableActions from './data-table-actions.vue'; import { formatRupiah } from '@/lib/rupiah'; +import { OrderStatus } from '@/constants/order-status'; import DataTableToolbar from '@/components/data-table/DataTableToolbar.vue'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; @@ -60,15 +61,15 @@ function rowNumber(index: number): number { } function statusVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' { - if (status === 'completed') { + if (status === OrderStatus.COMPLETED) { return 'default'; } - if (status === 'cancelled') { + if (status === OrderStatus.CANCELLED) { return 'destructive'; } - if (status === 'processing') { + if (status === OrderStatus.PROCESSING) { return 'secondary'; } diff --git a/resources/js/pages/admin/manage/orders/table/data-table-actions.vue b/resources/js/pages/admin/manage/orders/table/data-table-actions.vue index ea469b5..c1c37f5 100644 --- a/resources/js/pages/admin/manage/orders/table/data-table-actions.vue +++ b/resources/js/pages/admin/manage/orders/table/data-table-actions.vue @@ -8,6 +8,7 @@ import ConfirmDialog from '@/components/ConfirmDialog.vue'; import { Button } from '@/components/ui/button'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { useCan } from '@/composables/useCan'; +import { OrderStatus } from '@/constants/order-status'; import type { OrderListItem, OrderStatusAction } from '@/types/order'; const props = defineProps<{ @@ -32,11 +33,11 @@ function canPerformAction(action: OrderStatusAction): boolean { } function statusConfirmDescription(action: OrderStatusAction): string { - if (action.status === 'processing') { + if (action.status === OrderStatus.PROCESSING) { return `Pesanan ${props.order.order_number} akan dikirim dan diproses.`; } - if (action.status === 'completed') { + if (action.status === OrderStatus.COMPLETED) { return `Pesanan ${props.order.order_number} akan ditandai selesai.`; } @@ -92,11 +93,11 @@ function destroyOrder() { } function actionIcon(status: string) { - if (status === 'processing') { + if (status === OrderStatus.PROCESSING) { return Send; } - if (status === 'completed') { + if (status === OrderStatus.COMPLETED) { return Check; } diff --git a/resources/js/pages/admin/master/products/Index.vue b/resources/js/pages/admin/master/products/Index.vue index 2b05942..17d0bb0 100644 --- a/resources/js/pages/admin/master/products/Index.vue +++ b/resources/js/pages/admin/master/products/Index.vue @@ -5,6 +5,8 @@ import { computed, ref, watch } from 'vue'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { useCan } from '@/composables/useCan'; +import { ActiveStatus } from '@/constants/active-status'; +import { StockStatus } from '@/constants/stock-status'; import { useDataTableQuery, useDataTableQuerySync, @@ -54,8 +56,8 @@ const filterDefs = computed(() => [ label: 'Status', type: 'select', options: [ - { value: '1', label: 'Aktif' }, - { value: '0', label: 'Nonaktif' }, + { value: ActiveStatus.ACTIVE, label: 'Aktif' }, + { value: ActiveStatus.INACTIVE, label: 'Nonaktif' }, ], }, { @@ -63,8 +65,8 @@ const filterDefs = computed(() => [ label: 'Stok', type: 'select', options: [ - { value: 'out_of_stock', label: 'Stok Habis' }, - { value: 'low_stock', label: 'Stok Menipis' }, + { value: StockStatus.OUT_OF_STOCK, label: 'Stok Habis' }, + { value: StockStatus.LOW_STOCK, label: 'Stok Menipis' }, ], }, ]); diff --git a/resources/js/pages/admin/master/raw-materials/Index.vue b/resources/js/pages/admin/master/raw-materials/Index.vue index 49d2fdd..9ae418f 100644 --- a/resources/js/pages/admin/master/raw-materials/Index.vue +++ b/resources/js/pages/admin/master/raw-materials/Index.vue @@ -5,6 +5,8 @@ import { computed, ref, watch } from 'vue'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { useCan } from '@/composables/useCan'; +import { ActiveStatus } from '@/constants/active-status'; +import { StockStatus } from '@/constants/stock-status'; import { useDataTableQuery, useDataTableQuerySync, @@ -43,8 +45,8 @@ const filterDefs = computed(() => [ label: 'Status', type: 'select', options: [ - { value: '1', label: 'Aktif' }, - { value: '0', label: 'Nonaktif' }, + { value: ActiveStatus.ACTIVE, label: 'Aktif' }, + { value: ActiveStatus.INACTIVE, label: 'Nonaktif' }, ], }, { @@ -52,8 +54,8 @@ const filterDefs = computed(() => [ label: 'Stok', type: 'select', options: [ - { value: 'out_of_stock', label: 'Stok Habis' }, - { value: 'low_stock', label: 'Stok Menipis' }, + { value: StockStatus.OUT_OF_STOCK, label: 'Stok Habis' }, + { value: StockStatus.LOW_STOCK, label: 'Stok Menipis' }, ], }, ]); diff --git a/resources/js/pages/admin/system/setting/Index.vue b/resources/js/pages/admin/system/setting/Index.vue index 42814fe..37b87fa 100644 --- a/resources/js/pages/admin/system/setting/Index.vue +++ b/resources/js/pages/admin/system/setting/Index.vue @@ -6,6 +6,7 @@ import HrSection from './form/HrSection.vue'; import MarketplaceSection from './form/MarketplaceSection.vue'; import SocialMediaSection from './form/SocialMediaSection.vue'; import SystemSection from './form/SystemSection.vue'; +import { SettingSection as SettingSectionConst } from '@/constants/setting-section'; import SettingLayout from '@/layouts/SettingLayout.vue'; import type { HomepageSettingsData, @@ -24,7 +25,7 @@ defineProps<{ homepage: HomepageSettingsData; }>(); -const activeSection = ref('system'); +const activeSection = ref(SettingSectionConst.SYSTEM); diff --git a/resources/js/pages/admin/system/setting/form/MarketplaceFeeField.vue b/resources/js/pages/admin/system/setting/form/MarketplaceFeeField.vue index 04b4bd6..01dccc5 100644 --- a/resources/js/pages/admin/system/setting/form/MarketplaceFeeField.vue +++ b/resources/js/pages/admin/system/setting/form/MarketplaceFeeField.vue @@ -14,6 +14,8 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { MarketplaceFeeScope } from '@/constants/marketplace-fee-scope'; +import { MarketplaceFeeValueType } from '@/constants/marketplace-fee-value-type'; import type { MarketplaceFeeRule } from '@/types/setting'; const props = defineProps<{ @@ -27,7 +29,7 @@ const emit = defineEmits<{ (event: 'update:modelValue', value: MarketplaceFeeRule): void; }>(); -const isPercent = computed(() => props.modelValue.value_type === 'percent'); +const isPercent = computed(() => props.modelValue.value_type === MarketplaceFeeValueType.PERCENT); function update(partial: Partial) { emit('update:modelValue', { @@ -38,7 +40,7 @@ function update(partial: Partial) { function updateValue(value: string | number) { update({ - value: props.modelValue.value_type === 'flat' + value: props.modelValue.value_type === MarketplaceFeeValueType.FLAT ? Number(value) || 0 : Number(value), }); @@ -62,10 +64,10 @@ function updateValue(value: string | number) { - + Per Transaksi - + Per Produk @@ -84,10 +86,10 @@ function updateValue(value: string | number) { - + Persentase (%) - + Flat (Rp) diff --git a/resources/js/pages/admin/system/setting/form/MarketplaceSection.vue b/resources/js/pages/admin/system/setting/form/MarketplaceSection.vue index b2f3510..78091d7 100644 --- a/resources/js/pages/admin/system/setting/form/MarketplaceSection.vue +++ b/resources/js/pages/admin/system/setting/form/MarketplaceSection.vue @@ -13,6 +13,7 @@ import { FieldGroup, FieldSet } from '@/components/ui/field'; +import { MarketplacePlatform as MarketplacePlatformConst } from '@/constants/marketplace-platform'; import { formErrors } from '@/lib/form'; import { cn } from '@/lib/utils'; import type { @@ -24,15 +25,15 @@ const props = defineProps<{ data: MarketplaceSettingsData; }>(); -const activePlatform = ref('tiktok_shop'); +const activePlatform = ref(MarketplacePlatformConst.TIKTOK_SHOP); const platformItems: Array<{ key: MarketplacePlatform; label: string; icon: typeof Store; }> = [ - { key: 'tiktok_shop', label: 'TikTok Shop', icon: Store }, - { key: 'shopee', label: 'Shopee', icon: ShoppingBag }, + { key: MarketplacePlatformConst.TIKTOK_SHOP, label: 'TikTok Shop', icon: Store }, + { key: MarketplacePlatformConst.SHOPEE, label: 'Shopee', icon: ShoppingBag }, ]; const form = useForm({ @@ -88,7 +89,7 @@ function submit() {
- +
@@ -138,7 +139,7 @@ function submit() { - +