feat: add tiktok_order_id and shopee_order_id fields to order management, including validation, display, and search functionality

This commit is contained in:
Yoga Pangestu 2026-06-17 23:18:06 +07:00
parent b176854bc3
commit c86af4ab91
8 changed files with 69 additions and 8 deletions

View File

@ -31,6 +31,8 @@ public function rules(): array
'marketing_id' => ['nullable', 'integer', Rule::exists('users', 'id')->whereNull('deleted_at')],
'channel' => ['required', Rule::enum(OrderChannel::class)],
'price_type' => ['required', Rule::enum(PriceType::class)],
'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'],
'notes' => ['nullable', 'string'],
];
@ -58,6 +60,8 @@ public function attributes(): array
'marketing_id' => 'marketing',
'channel' => 'channel',
'price_type' => 'tipe harga',
'tiktok_order_id' => 'ID pesanan TikTok Shop',
'shopee_order_id' => 'ID pesanan Shopee',
'discount' => 'diskon',
'notes' => 'keterangan',
'items' => 'produk',

View File

@ -46,6 +46,8 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
$search = $tableQuery['search'];
$query->where(function (Builder $query) use ($search): void {
$query->where('order_number', 'like', "%{$search}%")
->orWhere('tiktok_order_id', 'like', "%{$search}%")
->orWhere('shopee_order_id', 'like', "%{$search}%")
->orWhere('notes', 'like', "%{$search}%")
->orWhereHas('customer', fn (Builder $query) => $query->where('name', 'like', "%{$search}%"))
->orWhereHas('items.productVariant', function (Builder $query) use ($search): void {
@ -324,6 +326,8 @@ public function create(array $validated, User $user): Order
'channel' => $channel,
'price_type' => $priceType,
'status' => OrderStatus::PENDING,
'tiktok_order_id' => $validated['tiktok_order_id'] ?? null,
'shopee_order_id' => $validated['shopee_order_id'] ?? null,
'created_by_id' => $user->id,
'subtotal' => $subtotal,
'discount' => $discount,
@ -397,6 +401,8 @@ public function update(Order $order, array $validated): void
$order->marketing_id = $validated['marketing_id'] ?? null;
$order->channel = $channel;
$order->price_type = $priceType;
$order->tiktok_order_id = $validated['tiktok_order_id'] ?? null;
$order->shopee_order_id = $validated['shopee_order_id'] ?? null;
$order->subtotal = $subtotal;
$order->discount = $discount;
$order->marketplace_settings_snapshot = $this->marketplaceService->buildOrderSnapshot(

View File

@ -21,6 +21,8 @@ public function up(): void
$table->enum('channel', array_column(OrderChannel::cases(), 'value'));
$table->enum('price_type', array_column(PriceType::cases(), 'value'));
$table->enum('status', array_column(OrderStatus::cases(), 'value'))->default(OrderStatus::PENDING->value);
$table->string('tiktok_order_id', 100)->nullable();
$table->string('shopee_order_id', 100)->nullable();
$table->unsignedBigInteger('subtotal');
$table->unsignedBigInteger('discount')->default(0);
$table->json('marketplace_settings_snapshot')->nullable();

View File

@ -98,6 +98,12 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
<Badge variant="secondary">
{{ order.channel_label }}
</Badge>
<Badge v-if="order.tiktok_order_id" variant="outline">
ID Pesanan: {{ order.tiktok_order_id }}
</Badge>
<Badge v-if="order.shopee_order_id" variant="outline">
ID Pesanan: {{ order.shopee_order_id }}
</Badge>
</div>
<div class="text-muted-foreground space-y-1 text-sm">
<p v-if="order.customer">
@ -108,13 +114,13 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
</div>
<div class="flex flex-wrap gap-x-4 gap-y-1 text-sm">
<span>Tipe Harga <strong class="text-foreground">{{ order.price_type_label
}}</strong></span>
}}</strong></span>
<span>Subtotal <strong class="text-foreground">{{ order.subtotal_formatted
}}</strong></span>
}}</strong></span>
<span>Diskon <strong class="text-foreground">{{ order.discount_formatted
}}</strong></span>
}}</strong></span>
<span>Total <strong class="text-primary">{{ order.total_amount_formatted
}}</strong></span>
}}</strong></span>
</div>
<p v-if="order.notes" class="text-muted-foreground text-sm">
{{ order.notes }}

View File

@ -3,7 +3,6 @@ import { useForm, usePage } from '@inertiajs/vue3';
import { Minus, Plus, Save, Search, ShoppingCart, Trash2 } from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import { toast } from 'vue-sonner';
import type { Auth } from '@/types/auth';
import PosCatalogCard from '@/components/admin/manage/PosCatalogCard.vue';
import PosCatalogVariantThumb from '@/components/admin/manage/PosCatalogVariantThumb.vue';
import { NumberInput } from '@/components/form/number-input';
@ -34,10 +33,12 @@ import {
} from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
import { Textarea } from '@/components/ui/textarea';
import { OrderChannel } from '@/constants/order-channel';
import { apiFetch } from '@/lib/api';
import { getFirstCoverImage } from '@/lib/catalog-cover';
import { formErrors } from '@/lib/form';
import { formatRupiah, parseRupiah } from '@/lib/rupiah';
import type { Auth } from '@/types/auth';
import type { EnumOption, OrderCartItem, OrderCatalogItem, SelectOption } from '@/types/order';
import type { ProductPriceItem, ProductVariantItem } from '@/types/product';
@ -52,6 +53,8 @@ const props = defineProps<{
marketing_id: string;
channel: string;
price_type: string;
tiktok_order_id: string;
shopee_order_id: string;
discount: string;
notes: string;
items: OrderCartItem[];
@ -83,6 +86,8 @@ const form = useForm({
marketing_id: defaultMarketingId.value || 'none',
channel: 'store',
price_type: 'ecer',
tiktok_order_id: '',
shopee_order_id: '',
discount: '',
notes: '',
});
@ -98,6 +103,8 @@ function populateForm() {
form.marketing_id = props.initialData.marketing_id || 'none';
form.channel = props.initialData.channel;
form.price_type = props.initialData.price_type;
form.tiktok_order_id = props.initialData.tiktok_order_id;
form.shopee_order_id = props.initialData.shopee_order_id;
form.discount = props.initialData.discount;
form.notes = props.initialData.notes;
cart.value = props.initialData.items.map((item) => ({ ...item }));
@ -347,6 +354,15 @@ function buildFormData(): FormData {
formData.append('channel', form.channel);
formData.append('price_type', form.price_type);
if (form.channel === 'tiktok' && form.tiktok_order_id) {
formData.append('tiktok_order_id', form.tiktok_order_id);
}
if (form.channel === 'shopee' && form.shopee_order_id) {
formData.append('shopee_order_id', form.shopee_order_id);
}
formData.append('discount', parseRupiah(form.discount));
formData.append('notes', form.notes);
@ -409,8 +425,7 @@ function submit() {
Belum ada varian
</p>
<div v-for="variant in product.variants" :key="variant.id"
class="flex items-center gap-2.5 px-3 py-2.5 transition-all duration-200"
:class="[
class="flex items-center gap-2.5 px-3 py-2.5 transition-all duration-200" :class="[
getVariantPrice(variant) ? 'cursor-pointer hover:bg-muted/30' : 'opacity-60',
getCartItem(variant.id) ? 'border-2 border-primary bg-primary/5 rounded-md mx-1 my-0.5' : ''
]"
@ -502,10 +517,28 @@ function submit() {
<Field v-else>
<FieldLabel>Tipe Harga</FieldLabel>
<div class="flex h-9 items-center rounded-md border bg-muted/40 px-3 text-sm">
{{ form.channel === 'shopee' ? 'Shopee' : 'TikTok' }}
{{ form.channel === OrderChannel.SHOPEE ? 'Shopee' : 'TikTok' }}
</div>
</Field>
<Field v-if="form.channel === 'tiktok'">
<FieldLabel for="tiktok_order_id" :required="form.channel === OrderChannel.TIKTOK">ID
Pesanan
TikTok Shop</FieldLabel>
<Input id="tiktok_order_id" v-model="form.tiktok_order_id"
placeholder="Masukkan ID pesanan TikTok Shop" />
<FieldError :errors="formErrors(form, 'tiktok_order_id')" />
</Field>
<Field v-if="form.channel === 'shopee'">
<FieldLabel for="shopee_order_id" :required="form.channel === OrderChannel.SHOPEE">ID
Pesanan
Shopee</FieldLabel>
<Input id="shopee_order_id" v-model="form.shopee_order_id"
placeholder="Masukkan ID pesanan Shopee" />
<FieldError :errors="formErrors(form, 'shopee_order_id')" />
</Field>
<Field>
<FieldLabel for="customer">Pelanggan</FieldLabel>
<Select v-model="form.customer_id">

View File

@ -0,0 +1,4 @@
export const OrderChannel = {
TIKTOK: 'tiktok',
SHOPEE: 'shopee',
} as const;

View File

@ -21,6 +21,8 @@ const initialData = computed(() => ({
marketing_id: props.order.marketing_id ? String(props.order.marketing_id) : '',
channel: props.order.channel,
price_type: props.order.price_type,
tiktok_order_id: props.order.tiktok_order_id ?? '',
shopee_order_id: props.order.shopee_order_id ?? '',
discount: String(props.order.discount),
notes: props.order.notes ?? '',
items: props.order.items.map((item) => ({

View File

@ -45,6 +45,8 @@ export type OrderListItem = {
status_label: string;
is_editable: boolean;
available_actions: OrderStatusAction[];
tiktok_order_id: string | null;
shopee_order_id: string | null;
subtotal_formatted: string;
discount_formatted: string;
total_amount_formatted: string;
@ -80,6 +82,8 @@ export type OrderEditItem = {
channel: string;
price_type: string;
status: string;
tiktok_order_id: string | null;
shopee_order_id: string | null;
discount: number;
notes: string | null;
items: Array<{