From 1af4af70fa51ec99d4c34038eae6154975d3dae6 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 18 Jun 2026 00:57:47 +0700 Subject: [PATCH] feat: add is_affiliate field to order management, including validation, database migration, and UI updates --- .../Requests/Admin/Manage/OrderRequest.php | 2 ++ app/Models/Order.php | 1 + app/Services/Manage/OrderService.php | 5 ++++ .../System/Setting/MarketplaceService.php | 30 ++++++++++++++++++- .../2026_06_12_100001_create_orders_table.php | 1 + .../admin/manage/orders/OrderGroupedTable.vue | 13 ++++---- .../admin/manage/orders/OrderPosForm.vue | 29 +++++++++++++++++- .../js/pages/admin/manage/orders/Edit.vue | 1 + resources/js/types/order.ts | 2 ++ 9 files changed, 77 insertions(+), 7 deletions(-) diff --git a/app/Http/Requests/Admin/Manage/OrderRequest.php b/app/Http/Requests/Admin/Manage/OrderRequest.php index 6c6eb24..c6fd907 100644 --- a/app/Http/Requests/Admin/Manage/OrderRequest.php +++ b/app/Http/Requests/Admin/Manage/OrderRequest.php @@ -33,6 +33,7 @@ public function rules(): array 'channel' => ['required', Rule::enum(OrderChannel::class)], 'price_type' => ['required', Rule::enum(PriceType::class)], 'payment_type' => ['required', Rule::enum(PaymentType::class)], + 'is_affiliate' => ['nullable', 'boolean'], 'tiktok_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::TIKTOK->value), 'string', 'max:100'], 'shopee_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::SHOPEE->value), 'string', 'max:100'], 'discount' => ['nullable', 'integer', 'min:0'], @@ -63,6 +64,7 @@ public function attributes(): array 'channel' => 'channel', 'price_type' => 'tipe harga', 'payment_type' => 'tipe pembayaran', + 'is_affiliate' => 'afiliasi', 'tiktok_order_id' => 'ID pesanan TikTok Shop', 'shopee_order_id' => 'ID pesanan Shopee', 'discount' => 'diskon', diff --git a/app/Models/Order.php b/app/Models/Order.php index f904c72..731cc1c 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -39,6 +39,7 @@ protected function casts(): array 'channel' => OrderChannel::class, 'price_type' => PriceType::class, 'payment_type' => PaymentType::class, + 'is_affiliate' => 'boolean', 'status' => OrderStatus::class, 'subtotal' => 'integer', 'discount' => 'integer', diff --git a/app/Services/Manage/OrderService.php b/app/Services/Manage/OrderService.php index 8590efd..24f9771 100644 --- a/app/Services/Manage/OrderService.php +++ b/app/Services/Manage/OrderService.php @@ -329,6 +329,7 @@ public function create(array $validated, User $user): Order 'channel' => $channel, 'price_type' => $priceType, 'payment_type' => PaymentType::from($validated['payment_type']), + 'is_affiliate' => $validated['is_affiliate'] ?? false, 'status' => OrderStatus::PENDING, 'tiktok_order_id' => $validated['tiktok_order_id'] ?? null, 'shopee_order_id' => $validated['shopee_order_id'] ?? null, @@ -339,6 +340,7 @@ public function create(array $validated, User $user): Order $channel, $totalAmount, $this->lineItemsForSnapshot($draftItems), + $validated['is_affiliate'] ?? false, ), 'total_amount' => $totalAmount, 'notes' => $validated['notes'] ?? null, @@ -417,6 +419,8 @@ public function update(Order $order, array $validated): void $order->marketing_id = $validated['marketing_id'] ?? null; $order->channel = $channel; $order->price_type = $priceType; + $order->payment_type = PaymentType::from($validated['payment_type']); + $order->is_affiliate = $validated['is_affiliate'] ?? false; $order->tiktok_order_id = $validated['tiktok_order_id'] ?? null; $order->shopee_order_id = $validated['shopee_order_id'] ?? null; $order->subtotal = $subtotal; @@ -425,6 +429,7 @@ public function update(Order $order, array $validated): void $channel, $totalAmount, $this->lineItemsForSnapshot($lineItems), + $validated['is_affiliate'] ?? false, ); $order->total_amount = $totalAmount; $order->notes = $validated['notes'] ?? null; diff --git a/app/Services/System/Setting/MarketplaceService.php b/app/Services/System/Setting/MarketplaceService.php index a05f1f9..10bb2c5 100644 --- a/app/Services/System/Setting/MarketplaceService.php +++ b/app/Services/System/Setting/MarketplaceService.php @@ -84,7 +84,7 @@ public function updateMarketplace(array $validated): void * @param list $lineItems * @return array|null */ - public function buildOrderSnapshot(OrderChannel $channel, int $totalAmount, array $lineItems): ?array + public function buildOrderSnapshot(OrderChannel $channel, int $totalAmount, array $lineItems, bool $isAffiliate = false): ?array { $feeSnapshot = $this->feeRulesForChannel($channel); @@ -92,6 +92,10 @@ public function buildOrderSnapshot(OrderChannel $channel, int $totalAmount, arra return null; } + if (! $isAffiliate) { + $feeSnapshot['fees'] = $this->excludeAffiliateFee($feeSnapshot['fees'], $channel); + } + $calculation = app(MarketplaceFeeCalculator::class)->calculate( $feeSnapshot['fees'], $totalAmount, @@ -100,6 +104,7 @@ public function buildOrderSnapshot(OrderChannel $channel, int $totalAmount, arra return [ 'platform' => $feeSnapshot['platform'], + 'is_affiliate' => $isAffiliate, 'fees' => $feeSnapshot['fees'], 'base_amount' => $totalAmount, 'results' => $calculation['results'], @@ -108,6 +113,29 @@ public function buildOrderSnapshot(OrderChannel $channel, int $totalAmount, arra ]; } + /** + * @param array $fees + * @return array + */ + private function excludeAffiliateFee(array $fees, OrderChannel $channel): array + { + $affiliateKey = match ($channel) { + OrderChannel::TIKTOK => 'tiktok_shop_affiliate', + OrderChannel::SHOPEE => 'ams_commission_fee', + default => null, + }; + + if ($affiliateKey !== null && isset($fees[$affiliateKey])) { + $fees[$affiliateKey] = [ + 'scope' => $fees[$affiliateKey]['scope'], + 'value_type' => $fees[$affiliateKey]['value_type'], + 'value' => 0, + ]; + } + + return $fees; + } + /** * @return array{platform: string, fees: array}|null */ diff --git a/database/migrations/2026_06_12_100001_create_orders_table.php b/database/migrations/2026_06_12_100001_create_orders_table.php index f9975a5..b865e3c 100644 --- a/database/migrations/2026_06_12_100001_create_orders_table.php +++ b/database/migrations/2026_06_12_100001_create_orders_table.php @@ -23,6 +23,7 @@ public function up(): void $table->enum('price_type', array_column(PriceType::cases(), 'value')); $table->enum('status', array_column(OrderStatus::cases(), 'value'))->default(OrderStatus::PENDING->value); $table->enum('payment_type', array_column(PaymentType::cases(), 'value'))->default(PaymentType::CASH->value); + $table->boolean('is_affiliate')->default(false); $table->string('tiktok_order_id', 100)->nullable(); $table->string('shopee_order_id', 100)->nullable(); $table->unsignedBigInteger('subtotal'); diff --git a/resources/js/components/admin/manage/orders/OrderGroupedTable.vue b/resources/js/components/admin/manage/orders/OrderGroupedTable.vue index 0b7d395..7752ed6 100644 --- a/resources/js/components/admin/manage/orders/OrderGroupedTable.vue +++ b/resources/js/components/admin/manage/orders/OrderGroupedTable.vue @@ -104,6 +104,9 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive' ID Pesanan: {{ order.shopee_order_id }} + + Afiliasi +

@@ -114,15 +117,15 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'

Tipe Harga {{ order.price_type_label - }} + }} Pembayaran {{ order.payment_type_label - }} + }} Subtotal {{ order.subtotal_formatted - }} + }} Diskon {{ order.discount_formatted - }} + }} Total {{ order.total_amount_formatted - }} + }}

{{ order.notes }} diff --git a/resources/js/components/admin/manage/orders/OrderPosForm.vue b/resources/js/components/admin/manage/orders/OrderPosForm.vue index e477cad..8e3eab4 100644 --- a/resources/js/components/admin/manage/orders/OrderPosForm.vue +++ b/resources/js/components/admin/manage/orders/OrderPosForm.vue @@ -32,6 +32,7 @@ import { SelectValue, } from '@/components/ui/select'; import { Separator } from '@/components/ui/separator'; +import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; import { OrderChannel } from '@/constants/order-channel'; import { apiFetch } from '@/lib/api'; @@ -55,6 +56,7 @@ const props = defineProps<{ channel: string; price_type: string; payment_type: string; + is_affiliate: boolean; tiktok_order_id: string; shopee_order_id: string; discount: string; @@ -89,6 +91,7 @@ const form = useForm({ channel: 'store', price_type: 'ecer', payment_type: 'cash', + is_affiliate: false, tiktok_order_id: '', shopee_order_id: '', discount: '', @@ -96,6 +99,7 @@ const form = useForm({ }); const isStoreChannel = computed(() => form.channel === 'store'); +const isMarketplaceChannel = computed(() => form.channel === 'shopee' || form.channel === 'tiktok'); function populateForm() { if (!props.initialData) { @@ -107,6 +111,7 @@ function populateForm() { form.channel = props.initialData.channel; form.price_type = props.initialData.price_type; form.payment_type = props.initialData.payment_type; + form.is_affiliate = props.initialData.is_affiliate; form.tiktok_order_id = props.initialData.tiktok_order_id; form.shopee_order_id = props.initialData.shopee_order_id; form.discount = props.initialData.discount; @@ -137,11 +142,17 @@ watch( (channel) => { if (channel === 'shopee') { form.price_type = 'shopee'; + form.payment_type = 'marketplace'; } else if (channel === 'tiktok') { form.price_type = 'tiktok'; + form.payment_type = 'marketplace'; } else if (!props.storePriceTypes.some((type) => type.value === form.price_type)) { form.price_type = 'ecer'; } + + if (channel === 'store') { + form.is_affiliate = false; + } }, ); @@ -360,6 +371,10 @@ function buildFormData(): FormData { formData.append('price_type', form.price_type); formData.append('payment_type', form.payment_type); + if (form.channel !== 'store') { + formData.append('is_affiliate', form.is_affiliate ? '1' : '0'); + } + if (form.channel === 'tiktok' && form.tiktok_order_id) { formData.append('tiktok_order_id', form.tiktok_order_id); } @@ -546,7 +561,7 @@ function submit() { Tipe Pembayaran - @@ -562,6 +577,18 @@ function submit() { + +

+ + Pesanan Afiliasi +
+

+ Aktifkan jika pesanan ini melalui afiliasi. Biaya afiliasi akan dikenakan sesuai + pengaturan marketplace. +

+ + Pelanggan