feat: add order status management to OrderRequest and OrderService, update frontend forms for status handling
This commit is contained in:
parent
60c58a9709
commit
fe1549e0a5
@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Requests\Admin\Manage;
|
namespace App\Http\Requests\Admin\Manage;
|
||||||
|
|
||||||
use App\Enums\OrderChannel;
|
use App\Enums\OrderChannel;
|
||||||
|
use App\Enums\OrderStatus;
|
||||||
use App\Enums\PaymentType;
|
use App\Enums\PaymentType;
|
||||||
use App\Enums\Permission;
|
use App\Enums\Permission;
|
||||||
use App\Enums\PriceType;
|
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'],
|
'shopee_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::SHOPEE->value), 'string', 'max:100'],
|
||||||
'discount' => ['nullable', 'integer', 'min:0'],
|
'discount' => ['nullable', 'integer', 'min:0'],
|
||||||
'notes' => ['nullable', 'string'],
|
'notes' => ['nullable', 'string'],
|
||||||
|
'status' => ['nullable', Rule::enum(OrderStatus::class)],
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($this->isMethod('PUT') || $this->isMethod('PATCH')) {
|
if ($this->isMethod('PUT') || $this->isMethod('PATCH')) {
|
||||||
@ -71,6 +73,7 @@ public function attributes(): array
|
|||||||
'shopee_order_id' => 'ID pesanan Shopee',
|
'shopee_order_id' => 'ID pesanan Shopee',
|
||||||
'discount' => 'diskon',
|
'discount' => 'diskon',
|
||||||
'notes' => 'keterangan',
|
'notes' => 'keterangan',
|
||||||
|
'status' => 'status pesanan',
|
||||||
'items' => 'produk',
|
'items' => 'produk',
|
||||||
'items.*.product_variant_id' => 'varian produk',
|
'items.*.product_variant_id' => 'varian produk',
|
||||||
'items.*.quantity' => 'jumlah',
|
'items.*.quantity' => 'jumlah',
|
||||||
@ -85,6 +88,12 @@ public function withValidator(Validator $validator): void
|
|||||||
$validator->errors()->add('customer_id', 'Pelanggan wajib diisi jika marketing dipilih.');
|
$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')) {
|
if ($this->isMethod('PUT') || $this->isMethod('PATCH')) {
|
||||||
/** @var Order $order */
|
/** @var Order $order */
|
||||||
$order = $this->route('order');
|
$order = $this->route('order');
|
||||||
|
|||||||
@ -421,6 +421,10 @@ public function create(array $validated, User $user): Order
|
|||||||
$totalAmount = max($subtotal - $discount, 0);
|
$totalAmount = max($subtotal - $discount, 0);
|
||||||
$channel = OrderChannel::from($validated['channel']);
|
$channel = OrderChannel::from($validated['channel']);
|
||||||
|
|
||||||
|
$status = isset($validated['status'])
|
||||||
|
? OrderStatus::from($validated['status'])
|
||||||
|
: OrderStatus::PENDING;
|
||||||
|
|
||||||
$order = Order::create([
|
$order = Order::create([
|
||||||
'customer_id' => $validated['customer_id'] ?? null,
|
'customer_id' => $validated['customer_id'] ?? null,
|
||||||
'marketing_id' => $validated['marketing_id'] ?? null,
|
'marketing_id' => $validated['marketing_id'] ?? null,
|
||||||
@ -428,7 +432,7 @@ public function create(array $validated, User $user): Order
|
|||||||
'price_type' => $priceType,
|
'price_type' => $priceType,
|
||||||
'payment_type' => PaymentType::from($validated['payment_type']),
|
'payment_type' => PaymentType::from($validated['payment_type']),
|
||||||
'is_affiliate' => $validated['is_affiliate'] ?? false,
|
'is_affiliate' => $validated['is_affiliate'] ?? false,
|
||||||
'status' => OrderStatus::PENDING,
|
'status' => $status,
|
||||||
'tiktok_order_id' => $validated['tiktok_order_id'] ?? null,
|
'tiktok_order_id' => $validated['tiktok_order_id'] ?? null,
|
||||||
'shopee_order_id' => $validated['shopee_order_id'] ?? null,
|
'shopee_order_id' => $validated['shopee_order_id'] ?? null,
|
||||||
'created_by_id' => $user->id,
|
'created_by_id' => $user->id,
|
||||||
@ -547,6 +551,15 @@ public function update(Order $order, array $validated): void
|
|||||||
);
|
);
|
||||||
$order->total_amount = $totalAmount;
|
$order->total_amount = $totalAmount;
|
||||||
$order->notes = $validated['notes'] ?? null;
|
$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();
|
$order->save();
|
||||||
|
|
||||||
foreach ($lineItems as $itemData) {
|
foreach ($lineItems as $itemData) {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export const OrderStatus = {
|
export const OrderStatus = {
|
||||||
|
PENDING: 'pending',
|
||||||
PROCESSING: 'processing',
|
PROCESSING: 'processing',
|
||||||
COMPLETED: 'completed',
|
COMPLETED: 'completed',
|
||||||
CANCELLED: 'cancelled',
|
CANCELLED: 'cancelled',
|
||||||
|
|||||||
@ -37,6 +37,7 @@ const initialData = computed(() => ({
|
|||||||
shopee_order_id: props.order.shopee_order_id ?? '',
|
shopee_order_id: props.order.shopee_order_id ?? '',
|
||||||
discount: String(props.order.discount),
|
discount: String(props.order.discount),
|
||||||
notes: props.order.notes ?? '',
|
notes: props.order.notes ?? '',
|
||||||
|
status: props.order.status,
|
||||||
items: props.order.items.map((item) => ({
|
items: props.order.items.map((item) => ({
|
||||||
product_variant_id: item.product_variant_id,
|
product_variant_id: item.product_variant_id,
|
||||||
product_name: item.product_name,
|
product_name: item.product_name,
|
||||||
|
|||||||
@ -42,6 +42,7 @@ import { Textarea } from '@/components/ui/textarea';
|
|||||||
import { useCan } from '@/composables/useCan';
|
import { useCan } from '@/composables/useCan';
|
||||||
import { OrderChannel } from '@/constants/order-channel';
|
import { OrderChannel } from '@/constants/order-channel';
|
||||||
import { OrderPaymentType } from '@/constants/order-payment-type';
|
import { OrderPaymentType } from '@/constants/order-payment-type';
|
||||||
|
import { OrderStatus } from '@/constants/order-status';
|
||||||
import { PaperSize } from '@/constants/paper-size';
|
import { PaperSize } from '@/constants/paper-size';
|
||||||
import { StockQuality } from '@/constants/stock-quality';
|
import { StockQuality } from '@/constants/stock-quality';
|
||||||
import { apiFetch } from '@/lib/api';
|
import { apiFetch } from '@/lib/api';
|
||||||
@ -75,6 +76,7 @@ const props = defineProps<{
|
|||||||
shopee_order_id: string;
|
shopee_order_id: string;
|
||||||
discount: string;
|
discount: string;
|
||||||
notes: string;
|
notes: string;
|
||||||
|
status: string;
|
||||||
items: OrderCartItem[];
|
items: OrderCartItem[];
|
||||||
};
|
};
|
||||||
draftItems?: OrderCartItem[];
|
draftItems?: OrderCartItem[];
|
||||||
@ -126,6 +128,7 @@ const form = useForm({
|
|||||||
shopee_order_id: '',
|
shopee_order_id: '',
|
||||||
discount: '',
|
discount: '',
|
||||||
notes: '',
|
notes: '',
|
||||||
|
status: OrderStatus.PENDING,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isStoreChannel = computed(() => form.channel === 'store');
|
const isStoreChannel = computed(() => form.channel === 'store');
|
||||||
@ -146,6 +149,7 @@ function populateForm() {
|
|||||||
form.shopee_order_id = props.initialData.shopee_order_id;
|
form.shopee_order_id = props.initialData.shopee_order_id;
|
||||||
form.discount = props.initialData.discount;
|
form.discount = props.initialData.discount;
|
||||||
form.notes = props.initialData.notes;
|
form.notes = props.initialData.notes;
|
||||||
|
form.status = props.initialData.status || OrderStatus.PENDING;
|
||||||
cart.value = props.initialData.items.map((item) => ({
|
cart.value = props.initialData.items.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
stock_quality: item.stock_quality ?? StockQuality.GOOD,
|
stock_quality: item.stock_quality ?? StockQuality.GOOD,
|
||||||
@ -457,6 +461,7 @@ function buildFormData(): FormData {
|
|||||||
|
|
||||||
formData.append('discount', parseRupiah(form.discount));
|
formData.append('discount', parseRupiah(form.discount));
|
||||||
formData.append('notes', form.notes);
|
formData.append('notes', form.notes);
|
||||||
|
formData.append('status', form.status);
|
||||||
|
|
||||||
if (isCreateMode.value && printAfterSave.value) {
|
if (isCreateMode.value && printAfterSave.value) {
|
||||||
formData.append('print', '1');
|
formData.append('print', '1');
|
||||||
@ -823,6 +828,20 @@ function submit() {
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
<Field>
|
||||||
<FieldLabel for="notes">Keterangan</FieldLabel>
|
<FieldLabel for="notes">Keterangan</FieldLabel>
|
||||||
<Textarea id="notes" v-model="form.notes" placeholder="Contoh: Pesanan walk-in"
|
<Textarea id="notes" v-model="form.notes" placeholder="Contoh: Pesanan walk-in"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user