feat: add tiktok_order_id and shopee_order_id fields to order management, including validation, display, and search functionality
This commit is contained in:
parent
b176854bc3
commit
c86af4ab91
@ -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',
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 }}
|
||||
|
||||
@ -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">
|
||||
|
||||
4
resources/js/constants/order-channel.ts
Normal file
4
resources/js/constants/order-channel.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const OrderChannel = {
|
||||
TIKTOK: 'tiktok',
|
||||
SHOPEE: 'shopee',
|
||||
} as const;
|
||||
@ -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) => ({
|
||||
|
||||
@ -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<{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user