feat: add order status management to OrderRequest and OrderService, update frontend forms for status handling

This commit is contained in:
Yoga Pangestu 2026-06-28 00:01:54 +07:00
parent 60c58a9709
commit fe1549e0a5
5 changed files with 44 additions and 1 deletions

View File

@ -3,6 +3,7 @@
namespace App\Http\Requests\Admin\Manage;
use App\Enums\OrderChannel;
use App\Enums\OrderStatus;
use App\Enums\PaymentType;
use App\Enums\Permission;
use App\Enums\PriceType;
@ -39,6 +40,7 @@ public function rules(): array
'shopee_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::SHOPEE->value), 'string', 'max:100'],
'discount' => ['nullable', 'integer', 'min:0'],
'notes' => ['nullable', 'string'],
'status' => ['nullable', Rule::enum(OrderStatus::class)],
];
if ($this->isMethod('PUT') || $this->isMethod('PATCH')) {
@ -71,6 +73,7 @@ public function attributes(): array
'shopee_order_id' => 'ID pesanan Shopee',
'discount' => 'diskon',
'notes' => 'keterangan',
'status' => 'status pesanan',
'items' => 'produk',
'items.*.product_variant_id' => 'varian produk',
'items.*.quantity' => 'jumlah',
@ -85,6 +88,12 @@ public function withValidator(Validator $validator): void
$validator->errors()->add('customer_id', 'Pelanggan wajib diisi jika marketing dipilih.');
}
if ($this->filled('status') && $this->status === OrderStatus::COMPLETED->value) {
if (! $this->user()?->can(Permission::ORDERS_COMPLETE->value)) {
$validator->errors()->add('status', 'Anda tidak memiliki izin untuk menandai pesanan sebagai selesai.');
}
}
if ($this->isMethod('PUT') || $this->isMethod('PATCH')) {
/** @var Order $order */
$order = $this->route('order');

View File

@ -421,6 +421,10 @@ public function create(array $validated, User $user): Order
$totalAmount = max($subtotal - $discount, 0);
$channel = OrderChannel::from($validated['channel']);
$status = isset($validated['status'])
? OrderStatus::from($validated['status'])
: OrderStatus::PENDING;
$order = Order::create([
'customer_id' => $validated['customer_id'] ?? null,
'marketing_id' => $validated['marketing_id'] ?? null,
@ -428,7 +432,7 @@ public function create(array $validated, User $user): Order
'price_type' => $priceType,
'payment_type' => PaymentType::from($validated['payment_type']),
'is_affiliate' => $validated['is_affiliate'] ?? false,
'status' => OrderStatus::PENDING,
'status' => $status,
'tiktok_order_id' => $validated['tiktok_order_id'] ?? null,
'shopee_order_id' => $validated['shopee_order_id'] ?? null,
'created_by_id' => $user->id,
@ -547,6 +551,15 @@ public function update(Order $order, array $validated): void
);
$order->total_amount = $totalAmount;
$order->notes = $validated['notes'] ?? null;
if (isset($validated['status'])) {
$newStatus = OrderStatus::from($validated['status']);
if ($order->status->canTransitionTo($newStatus) || $newStatus === $order->status) {
$order->status = $newStatus;
}
}
$order->save();
foreach ($lineItems as $itemData) {

View File

@ -1,4 +1,5 @@
export const OrderStatus = {
PENDING: 'pending',
PROCESSING: 'processing',
COMPLETED: 'completed',
CANCELLED: 'cancelled',

View File

@ -37,6 +37,7 @@ const initialData = computed(() => ({
shopee_order_id: props.order.shopee_order_id ?? '',
discount: String(props.order.discount),
notes: props.order.notes ?? '',
status: props.order.status,
items: props.order.items.map((item) => ({
product_variant_id: item.product_variant_id,
product_name: item.product_name,

View File

@ -42,6 +42,7 @@ 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 { OrderStatus } from '@/constants/order-status';
import { PaperSize } from '@/constants/paper-size';
import { StockQuality } from '@/constants/stock-quality';
import { apiFetch } from '@/lib/api';
@ -75,6 +76,7 @@ const props = defineProps<{
shopee_order_id: string;
discount: string;
notes: string;
status: string;
items: OrderCartItem[];
};
draftItems?: OrderCartItem[];
@ -126,6 +128,7 @@ const form = useForm({
shopee_order_id: '',
discount: '',
notes: '',
status: OrderStatus.PENDING,
});
const isStoreChannel = computed(() => form.channel === 'store');
@ -146,6 +149,7 @@ function populateForm() {
form.shopee_order_id = props.initialData.shopee_order_id;
form.discount = props.initialData.discount;
form.notes = props.initialData.notes;
form.status = props.initialData.status || OrderStatus.PENDING;
cart.value = props.initialData.items.map((item) => ({
...item,
stock_quality: item.stock_quality ?? StockQuality.GOOD,
@ -457,6 +461,7 @@ function buildFormData(): FormData {
formData.append('discount', parseRupiah(form.discount));
formData.append('notes', form.notes);
formData.append('status', form.status);
if (isCreateMode.value && printAfterSave.value) {
formData.append('print', '1');
@ -823,6 +828,20 @@ function submit() {
</div>
</div>
<Field v-if="can('orders.complete')">
<div class="flex items-center gap-3">
<Switch id="status-completed" :model-value="form.status === OrderStatus.COMPLETED"
@update:model-value="form.status = $event ? OrderStatus.COMPLETED : OrderStatus.PENDING" />
<Label for="status-completed" class="cursor-pointer text-sm">
Pesanan selesai
</Label>
</div>
<p class="text-xs text-muted-foreground">
Aktifkan jika pesanan sudah selesai diproses.
</p>
<FieldError :errors="formErrors(form, 'status')" />
</Field>
<Field>
<FieldLabel for="notes">Keterangan</FieldLabel>
<Textarea id="notes" v-model="form.notes" placeholder="Contoh: Pesanan walk-in"