diff --git a/app/Enums/Role.php b/app/Enums/Role.php index ccf8fb7..69e15ce 100644 --- a/app/Enums/Role.php +++ b/app/Enums/Role.php @@ -195,6 +195,9 @@ public function permissions(): array Permission::PAYROLL_VIEW, Permission::PAYROLL_ADJUST, + + Permission::SETTINGS_VIEW, + Permission::SETTINGS_UPDATE, ], self::CASHIER => [ diff --git a/app/Http/Controllers/Admin/System/SettingController.php b/app/Http/Controllers/Admin/System/SettingController.php index 722127c..0c22b9c 100644 --- a/app/Http/Controllers/Admin/System/SettingController.php +++ b/app/Http/Controllers/Admin/System/SettingController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Admin\System; +use App\Enums\Permission; use App\Http\Controllers\Concerns\FlashesEntityMessage; use App\Http\Controllers\Controller; use App\Http\Requests\Admin\System\Setting\HomepageSettingRequest; @@ -9,11 +10,13 @@ use App\Http\Requests\Admin\System\Setting\MarketplaceRequest; use App\Http\Requests\Admin\System\Setting\SocialMediaRequest; use App\Http\Requests\Admin\System\Setting\SystemRequest; +use App\Models\OwnerVerificationRequest; use App\Services\System\Setting\HomepageSettingService; use App\Services\System\Setting\HrSettingService; use App\Services\System\Setting\MarketplaceService; use App\Services\System\Setting\SocialMediaService; use App\Services\System\Setting\SystemService; +use App\Settings\MarketplaceSettings; use Illuminate\Http\RedirectResponse; use Inertia\Inertia; use Inertia\Response; @@ -38,11 +41,19 @@ public function index(): Response 'marketplace' => $this->marketplaceService->marketplaceData(), 'hr' => $this->hrSettingService->hrData(), 'homepage' => $this->homepageSettingService->homepageData(), + 'hasPendingMarketplaceVerification' => OwnerVerificationRequest::query() + ->where('subject_type', MarketplaceSettings::class) + ->pending() + ->exists(), ]); } public function updateSystem(SystemRequest $request): RedirectResponse { + if (! $request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) { + abort(403, 'Aksi ini tidak diizinkan.'); + } + $this->systemService->updateSystem($request->validated()); $this->flashSuccess('Pengaturan sistem berhasil disimpan.'); @@ -52,6 +63,10 @@ public function updateSystem(SystemRequest $request): RedirectResponse public function updateSocialMedia(SocialMediaRequest $request): RedirectResponse { + if (! $request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) { + abort(403, 'Aksi ini tidak diizinkan.'); + } + $this->socialMediaService->updateSocialMedia($request->validated()); $this->flashSuccess('Pengaturan media sosial berhasil disimpan.'); @@ -61,15 +76,28 @@ public function updateSocialMedia(SocialMediaRequest $request): RedirectResponse public function updateMarketplace(MarketplaceRequest $request): RedirectResponse { - $this->marketplaceService->updateMarketplace($request->validated()); + $this->marketplaceService->updateMarketplace($request->validated(), $request->user()); - $this->flashSuccess('Pengaturan marketplace berhasil disimpan.'); + $hasPending = OwnerVerificationRequest::query() + ->where('subject_type', MarketplaceSettings::class) + ->pending() + ->exists(); + + if ($hasPending) { + $this->flashSuccess('Perubahan pengaturan marketplace berhasil diajukan dan menunggu verifikasi owner.'); + } else { + $this->flashSuccess('Pengaturan marketplace berhasil disimpan.'); + } return redirect()->route('admin.system.settings.index'); } public function updateHr(HrSettingRequest $request): RedirectResponse { + if (! $request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) { + abort(403, 'Aksi ini tidak diizinkan.'); + } + $this->hrSettingService->updateHr($request->validated()); $this->flashSuccess('Pengaturan HR berhasil disimpan.'); @@ -79,6 +107,10 @@ public function updateHr(HrSettingRequest $request): RedirectResponse public function updateHomepage(HomepageSettingRequest $request): RedirectResponse { + if (! $request->user()->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) { + abort(403, 'Aksi ini tidak diizinkan.'); + } + $this->homepageSettingService->updateHomepage($request->validated()); $this->flashSuccess('Pengaturan homepage berhasil disimpan.'); diff --git a/app/Services/Manage/OwnerVerificationService.php b/app/Services/Manage/OwnerVerificationService.php index 9c90d04..f3bd9f3 100644 --- a/app/Services/Manage/OwnerVerificationService.php +++ b/app/Services/Manage/OwnerVerificationService.php @@ -16,6 +16,8 @@ use App\Services\System\PushNotificationService; use App\Support\ActivityLog\ModelLabel; use App\Support\OwnerVerification\VerificationChangeFormatter; +use App\Services\System\Setting\MarketplaceService; +use App\Settings\MarketplaceSettings; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; use Illuminate\Pagination\LengthAwarePaginator as Paginator; @@ -32,6 +34,7 @@ public function __construct( private readonly RawMaterialService $rawMaterialService, private readonly PurchaseService $purchaseService, private readonly PushNotificationService $pushNotificationService, + private readonly MarketplaceService $marketplaceService, ) {} /** @@ -278,6 +281,7 @@ private function rejectVerificationRequest(OwnerVerificationRequest $request): v Product::class => $this->productService->rejectVerificationRequest($request), RawMaterial::class => $this->rawMaterialService->rejectVerificationRequest($request), Purchase::class => $this->purchaseService->rejectVerificationRequest($request), + MarketplaceSettings::class => null, default => throw ValidationException::withMessages([ 'subject_type' => 'Tipe data verifikasi tidak didukung.', ]), @@ -290,6 +294,7 @@ private function applyVerificationRequest(OwnerVerificationRequest $request): vo Product::class => $this->productService->applyVerificationRequest($request), RawMaterial::class => $this->rawMaterialService->applyVerificationRequest($request), Purchase::class => $this->purchaseService->applyVerificationRequest($request), + MarketplaceSettings::class => $this->marketplaceService->applyVerificationRequest($request), default => throw ValidationException::withMessages([ 'subject_type' => 'Tipe data verifikasi tidak didukung.', ]), @@ -469,6 +474,10 @@ private function makePaginator( private function requestTitle(OwnerVerificationRequest $request): string { + if ($request->subject_type === MarketplaceSettings::class) { + return 'Pengaturan Marketplace'; + } + if ($request->subject instanceof Product || $request->subject instanceof RawMaterial) { return $request->subject->name; } @@ -517,6 +526,7 @@ private function notifyRequestSubmitter( Product::class => route('admin.master.products.index'), RawMaterial::class => route('admin.master.raw_materials.index'), Purchase::class => route('admin.manage.purchases.index'), + MarketplaceSettings::class => route('admin.system.settings.index'), default => route('admin.manage.owner_verifications.index'), }; diff --git a/app/Services/System/Setting/MarketplaceService.php b/app/Services/System/Setting/MarketplaceService.php index 10bb2c5..8eccf46 100644 --- a/app/Services/System/Setting/MarketplaceService.php +++ b/app/Services/System/Setting/MarketplaceService.php @@ -3,12 +3,23 @@ namespace App\Services\System\Setting; use App\Enums\OrderChannel; +use App\Enums\OwnerVerificationAction; +use App\Enums\OwnerVerificationStatus; +use App\Enums\Permission; +use App\Models\OwnerVerificationRequest; +use App\Models\User; +use App\Services\System\PushNotificationService; use App\Settings\MarketplaceSettings; use App\Support\Marketplace\MarketplaceFeeCalculator; use App\Support\Marketplace\MarketplaceFeeRule; +use Illuminate\Validation\ValidationException; class MarketplaceService { + public function __construct( + private readonly PushNotificationService $pushNotificationService, + ) {} + /** * @return list */ @@ -65,7 +76,67 @@ public function marketplaceData(): array ]; } - public function updateMarketplace(array $validated): void + public function updateMarketplace(array $validated, User $user): void + { + if ($user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value)) { + $this->saveSettings($validated); + + return; + } + + $hasPending = OwnerVerificationRequest::query() + ->where('subject_type', MarketplaceSettings::class) + ->pending() + ->exists(); + + if ($hasPending) { + throw ValidationException::withMessages([ + 'system' => 'Perubahan pengaturan marketplace sedang menunggu verifikasi owner.', + ]); + } + + $settings = app(MarketplaceSettings::class); + $oldPayload = []; + $newPayload = []; + + foreach ($this->tiktokFeeKeys() as $key) { + $oldPayload[$key] = $this->presentFeeRule($settings->{$key}); + $newPayload[$key] = $this->normalizeFeeRule($validated[$key]); + } + + foreach ($this->shopeeFeeKeys() as $key) { + $oldPayload[$key] = $this->presentFeeRule($settings->{$key}); + $newPayload[$key] = $this->normalizeFeeRule($validated[$key]); + } + + OwnerVerificationRequest::create([ + 'action' => OwnerVerificationAction::UPDATE, + 'status' => OwnerVerificationStatus::PENDING, + 'subject_type' => MarketplaceSettings::class, + 'subject_id' => null, + 'submitted_by_id' => $user->id, + 'payload' => [ + 'old' => $oldPayload, + 'new' => $newPayload, + ], + ]); + + $this->pushNotificationService->sendToRoles( + '⚙️ Pengaturan Marketplace Menunggu Persetujuan Owner', + "Pengajuan ubah pengaturan marketplace oleh '{$user->username}' menunggu verifikasi owner.", + ['owner', 'developer'], + route('admin.manage.owner_verifications.index'), + ); + + $this->pushNotificationService->sendToUser( + '📤 Pengajuan Terkirim', + 'Pengajuan ubah pengaturan marketplace menunggu verifikasi owner.', + $user->id, + route('admin.system.settings.index'), + ); + } + + public function saveSettings(array $validated): void { $settings = app(MarketplaceSettings::class); @@ -80,6 +151,12 @@ public function updateMarketplace(array $validated): void $settings->save(); } + public function applyVerificationRequest(OwnerVerificationRequest $request): void + { + $newPayload = $request->payload['new'] ?? []; + $this->saveSettings($newPayload); + } + /** * @param list $lineItems * @return array|null diff --git a/app/Support/ActivityLog/ModelLabel.php b/app/Support/ActivityLog/ModelLabel.php index 2f9cf58..4456cc2 100644 --- a/app/Support/ActivityLog/ModelLabel.php +++ b/app/Support/ActivityLog/ModelLabel.php @@ -67,6 +67,7 @@ class ModelLabel SystemConfiguration::class => 'Konfigurasi Sistem', User::class => 'Pengguna', UserProfile::class => 'Profil Pengguna', + \App\Settings\MarketplaceSettings::class => 'Pengaturan Marketplace', ]; public static function for(?string $modelClass): string diff --git a/app/Support/OwnerVerification/VerificationChangeFormatter.php b/app/Support/OwnerVerification/VerificationChangeFormatter.php index 343dae2..b9a09ea 100644 --- a/app/Support/OwnerVerification/VerificationChangeFormatter.php +++ b/app/Support/OwnerVerification/VerificationChangeFormatter.php @@ -74,6 +74,21 @@ private static function label(string $field): string 'is_active' => 'Status Aktif', 'variants' => 'Varian', 'prices' => 'Varian Harga', + 'tiktok_shop_platform_commission' => 'TikTok Shop - Komisi Platform', + 'tiktok_shop_logistics_service_fee' => 'TikTok Shop - Biaya Logistik', + 'tiktok_shop_dynamic_commission' => 'TikTok Shop - Komisi Dinamis', + 'tiktok_shop_order_processing_fee' => 'TikTok Shop - Biaya Proses Pesanan', + 'tiktok_shop_affiliate' => 'TikTok Shop - Komisi Affiliate', + 'tiktok_shop_pre_order_service_fee' => 'TikTok Shop - Biaya Pre Order', + 'shopee_admin_fee' => 'Shopee - Biaya Admin', + 'shopee_program_fee' => 'Shopee - Biaya Program', + 'shopee_shipping_savings' => 'Shopee - Hemat Biaya Kirim', + 'shopee_premium' => 'Shopee - Premi', + 'shopee_service_fee' => 'Shopee - Biaya Layanan', + 'shopee_order_processing_fee' => 'Shopee - Biaya Proses Pesanan', + 'shopee_ams_commission_fee' => 'Shopee - Komisi AMS', + 'shopee_pre_order' => 'Shopee - Pre Order', + 'shopee_live_extra' => 'Shopee - Live Extra', default => ucfirst(str_replace('_', ' ', $field)), }; } @@ -88,6 +103,39 @@ private static function presentValue(string $field, mixed $value): mixed return (bool) $value; } + $marketplaceKeys = [ + 'tiktok_shop_platform_commission', + 'tiktok_shop_logistics_service_fee', + 'tiktok_shop_dynamic_commission', + 'tiktok_shop_order_processing_fee', + 'tiktok_shop_affiliate', + 'tiktok_shop_pre_order_service_fee', + 'shopee_admin_fee', + 'shopee_program_fee', + 'shopee_shipping_savings', + 'shopee_premium', + 'shopee_service_fee', + 'shopee_order_processing_fee', + 'shopee_ams_commission_fee', + 'shopee_pre_order', + 'shopee_live_extra', + ]; + + if (in_array($field, $marketplaceKeys, true)) { + if (! is_array($value) || ! isset($value['scope'], $value['value_type'], $value['value'])) { + return is_array($value) ? json_encode($value) : (string) $value; + } + + $val = $value['value']; + $formattedValue = $value['value_type'] === 'percent' + ? rtrim(rtrim(number_format($val, 2, ',', '.'), '0'), ',') . '%' + : 'Rp ' . number_format($val, 0, ',', '.'); + + $scopeStr = $value['scope'] === 'item' ? 'per Item' : 'per Transaksi'; + + return "{$formattedValue} ({$scopeStr})"; + } + if ($field === 'variants' && is_array($value)) { return collect($value) ->map(function (array $variant): string { diff --git a/resources/js/layouts/SettingLayout.vue b/resources/js/layouts/SettingLayout.vue index a10a68f..a10bbfb 100644 --- a/resources/js/layouts/SettingLayout.vue +++ b/resources/js/layouts/SettingLayout.vue @@ -1,23 +1,30 @@ diff --git a/resources/js/pages/admin/system/setting/form/MarketplaceFeeField.vue b/resources/js/pages/admin/system/setting/form/MarketplaceFeeField.vue index 32f2886..35c06d2 100644 --- a/resources/js/pages/admin/system/setting/form/MarketplaceFeeField.vue +++ b/resources/js/pages/admin/system/setting/form/MarketplaceFeeField.vue @@ -23,6 +23,7 @@ const props = defineProps<{ label: string; modelValue: MarketplaceFeeRule; errors?: string[]; + disabled?: boolean; }>(); const emit = defineEmits<{ @@ -57,6 +58,7 @@ function updateValue(value: string | number) { Dasar @@ -97,9 +100,10 @@ function updateValue(value: string | number) { Nilai + max="100" :disabled="disabled" @update:model-value="updateValue" /> diff --git a/resources/js/pages/admin/system/setting/form/MarketplaceSection.vue b/resources/js/pages/admin/system/setting/form/MarketplaceSection.vue index 1107b14..9c6fa41 100644 --- a/resources/js/pages/admin/system/setting/form/MarketplaceSection.vue +++ b/resources/js/pages/admin/system/setting/form/MarketplaceSection.vue @@ -1,6 +1,6 @@