diff --git a/app/Http/Controllers/Admin/Manage/OrderController.php b/app/Http/Controllers/Admin/Manage/OrderController.php index b777a10..522d697 100644 --- a/app/Http/Controllers/Admin/Manage/OrderController.php +++ b/app/Http/Controllers/Admin/Manage/OrderController.php @@ -35,13 +35,16 @@ public function index(Request $request): Response ]); } - public function create(): Response + public function create(Request $request): Response { + $user = $request->user(); + return Inertia::render('admin/manage/orders/Create', [ 'customers' => $this->orderService->customerOptions(), - 'catalog' => $this->orderService->catalogItems(), + 'catalog' => $this->orderService->catalogItems(user: $user), 'channels' => OrderChannel::selectOptions(), 'storePriceTypes' => $this->orderService->storePriceTypeOptions(), + 'draftItems' => $this->orderService->draftItemsForUser($user), ]); } diff --git a/app/Http/Controllers/Admin/Manage/OrderDraftItemController.php b/app/Http/Controllers/Admin/Manage/OrderDraftItemController.php new file mode 100644 index 0000000..fb8b6af --- /dev/null +++ b/app/Http/Controllers/Admin/Manage/OrderDraftItemController.php @@ -0,0 +1,42 @@ +orderService->syncDraftItem($request->validated(), $request->user()); + + return response()->json(['item' => $item]); + } + + public function destroy(Request $request, ProductVariant $productVariant): JsonResponse + { + $this->orderService->removeDraftItem($request->user(), $productVariant); + + return response()->json(['ok' => true]); + } + + public function resyncPrices(OrderDraftResyncPricesRequest $request): JsonResponse + { + $items = $this->orderService->resyncDraftPrices( + $request->user(), + $request->validated('price_type'), + ); + + return response()->json(['items' => $items]); + } +} diff --git a/app/Http/Controllers/Admin/Manage/PurchaseController.php b/app/Http/Controllers/Admin/Manage/PurchaseController.php index 8b8fa76..d42ea67 100644 --- a/app/Http/Controllers/Admin/Manage/PurchaseController.php +++ b/app/Http/Controllers/Admin/Manage/PurchaseController.php @@ -32,11 +32,14 @@ public function index(Request $request): Response ]); } - public function create(): Response + public function create(Request $request): Response { + $user = $request->user(); + return Inertia::render('admin/manage/purchases/Create', [ 'suppliers' => $this->purchaseService->supplierOptions(), - 'catalog' => $this->purchaseService->catalogItems(), + 'catalog' => $this->purchaseService->catalogItems(user: $user), + 'draftItems' => $this->purchaseService->draftItemsForUser($user), ]); } diff --git a/app/Http/Controllers/Admin/Manage/PurchaseDraftItemController.php b/app/Http/Controllers/Admin/Manage/PurchaseDraftItemController.php new file mode 100644 index 0000000..a392114 --- /dev/null +++ b/app/Http/Controllers/Admin/Manage/PurchaseDraftItemController.php @@ -0,0 +1,31 @@ +purchaseService->syncDraftItem($request->validated(), $request->user()); + + return response()->json(['item' => $item]); + } + + public function destroy(Request $request, RawMaterialPrice $rawMaterialPrice): JsonResponse + { + $this->purchaseService->removeDraftItem($request->user(), $rawMaterialPrice); + + return response()->json(['ok' => true]); + } +} diff --git a/app/Http/Requests/Admin/Manage/OrderDraftItemRequest.php b/app/Http/Requests/Admin/Manage/OrderDraftItemRequest.php new file mode 100644 index 0000000..015d51a --- /dev/null +++ b/app/Http/Requests/Admin/Manage/OrderDraftItemRequest.php @@ -0,0 +1,32 @@ +user()?->can(Permission::ORDERS_CREATE->value) ?? false; + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'product_variant_id' => [ + 'required', + 'integer', + Rule::exists('product_variants', 'id')->whereNull('deleted_at'), + ], + 'quantity' => ['required', 'integer', 'min:1'], + 'price_type' => ['required', Rule::enum(PriceType::class)], + ]; + } +} diff --git a/app/Http/Requests/Admin/Manage/OrderDraftResyncPricesRequest.php b/app/Http/Requests/Admin/Manage/OrderDraftResyncPricesRequest.php new file mode 100644 index 0000000..ab9e422 --- /dev/null +++ b/app/Http/Requests/Admin/Manage/OrderDraftResyncPricesRequest.php @@ -0,0 +1,26 @@ +user()?->can(Permission::ORDERS_CREATE->value) ?? false; + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'price_type' => ['required', Rule::enum(PriceType::class)], + ]; + } +} diff --git a/app/Http/Requests/Admin/Manage/OrderRequest.php b/app/Http/Requests/Admin/Manage/OrderRequest.php index f7e0f81..b99b406 100644 --- a/app/Http/Requests/Admin/Manage/OrderRequest.php +++ b/app/Http/Requests/Admin/Manage/OrderRequest.php @@ -33,15 +33,17 @@ public function rules(): array 'discount' => ['nullable', 'integer', 'min:0'], 'marketplace_fee' => ['nullable', 'integer', 'min:0'], 'notes' => ['nullable', 'string'], + ]; - 'items' => ['required', 'array', 'min:1'], - 'items.*.product_variant_id' => [ + if ($this->isMethod('PUT') || $this->isMethod('PATCH')) { + $rules['items'] = ['required', 'array', 'min:1']; + $rules['items.*.product_variant_id'] = [ 'required', 'integer', Rule::exists('product_variants', 'id')->whereNull('deleted_at'), - ], - 'items.*.quantity' => ['required', 'integer', 'min:1'], - ]; + ]; + $rules['items.*.quantity'] = ['required', 'integer', 'min:1']; + } return $rules; } diff --git a/app/Http/Requests/Admin/Manage/PurchaseDraftItemRequest.php b/app/Http/Requests/Admin/Manage/PurchaseDraftItemRequest.php new file mode 100644 index 0000000..1837c29 --- /dev/null +++ b/app/Http/Requests/Admin/Manage/PurchaseDraftItemRequest.php @@ -0,0 +1,30 @@ +user()?->can(Permission::PURCHASES_CREATE->value) ?? false; + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'raw_material_price_id' => [ + 'required', + 'integer', + Rule::exists('raw_material_prices', 'id')->whereNull('deleted_at'), + ], + 'quantity' => ['required', 'numeric', 'decimal:0,4', 'gt:0'], + ]; + } +} diff --git a/app/Http/Requests/Admin/Manage/PurchaseRequest.php b/app/Http/Requests/Admin/Manage/PurchaseRequest.php index beac7bb..dffb99f 100644 --- a/app/Http/Requests/Admin/Manage/PurchaseRequest.php +++ b/app/Http/Requests/Admin/Manage/PurchaseRequest.php @@ -25,20 +25,24 @@ public function authorize(): bool */ public function rules(): array { - return [ + $rules = [ 'supplier_id' => ['required', 'integer', Rule::exists('suppliers', 'id')->whereNull('deleted_at')], 'discount' => ['nullable', 'integer', 'min:0'], 'notes' => ['nullable', 'string', 'max:100'], + ...$this->photoRules(), + ]; - 'items' => ['required', 'array', 'min:1'], - 'items.*.raw_material_price_id' => [ + if ($this->isMethod('PUT') || $this->isMethod('PATCH')) { + $rules['items'] = ['required', 'array', 'min:1']; + $rules['items.*.raw_material_price_id'] = [ 'required', 'integer', Rule::exists('raw_material_prices', 'id')->whereNull('deleted_at'), - ], - 'items.*.quantity' => ['required', 'numeric', 'decimal:0,4', 'gt:0'], - ...$this->photoRules(), - ]; + ]; + $rules['items.*.quantity'] = ['required', 'numeric', 'decimal:0,4', 'gt:0']; + } + + return $rules; } /** diff --git a/app/Models/OrderItem.php b/app/Models/OrderItem.php index 9a362ec..cc9a5d2 100644 --- a/app/Models/OrderItem.php +++ b/app/Models/OrderItem.php @@ -33,6 +33,11 @@ protected function casts(): array ]; } + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + public function order(): BelongsTo { return $this->belongsTo(Order::class); diff --git a/app/Models/PurchaseItem.php b/app/Models/PurchaseItem.php index cc21683..0ea4320 100644 --- a/app/Models/PurchaseItem.php +++ b/app/Models/PurchaseItem.php @@ -34,6 +34,11 @@ protected function casts(): array ]; } + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + public function purchase(): BelongsTo { return $this->belongsTo(Purchase::class); diff --git a/app/Services/Manage/OrderService.php b/app/Services/Manage/OrderService.php index e3acfda..615dbd7 100644 --- a/app/Services/Manage/OrderService.php +++ b/app/Services/Manage/OrderService.php @@ -15,6 +15,7 @@ use App\Support\Media\MediaPresenter; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Validation\ValidationException; @@ -97,11 +98,11 @@ public function storePriceTypeOptions(): array /** * @return Collection */ - public function catalogItems(?Order $order = null): Collection + public function catalogItems(?Order $order = null, ?User $user = null): Collection { $orderVariantIds = $order ? $order->items()->pluck('product_variant_id')->all() - : []; + : ($user ? $this->draftItemsQuery($user)->pluck('product_variant_id')->all() : []); return Product::query() ->with([ @@ -155,6 +156,110 @@ public function findForEdit(Order $order): Order return $order; } + /** + * @return list> + */ + public function draftItemsForUser(User $user): array + { + return $this->draftItemsQuery($user) + ->with([ + 'productVariant.product:id,name', + 'productVariant.media', + ]) + ->get() + ->map(fn (OrderItem $item) => $this->presentDraftItem($item)) + ->values() + ->all(); + } + + /** + * @param array $validated + * @return array + */ + public function syncDraftItem(array $validated, User $user): array + { + $priceType = PriceType::from($validated['price_type']); + $variant = ProductVariant::query()->findOrFail($validated['product_variant_id']); + $price = ProductPrice::query() + ->where('variant_id', $variant->id) + ->where('type', $priceType) + ->first(); + + if ($price === null) { + throw ValidationException::withMessages([ + 'product_variant_id' => 'Harga untuk tipe harga ini belum diatur.', + ]); + } + + $quantity = (int) $validated['quantity']; + $unitPrice = (int) $price->price; + $subtotal = $unitPrice * $quantity; + + $item = OrderItem::query()->updateOrCreate( + [ + 'user_id' => $user->id, + 'product_variant_id' => $variant->id, + 'order_id' => null, + ], + [ + 'quantity' => $quantity, + 'unit_price' => $unitPrice, + 'subtotal' => $subtotal, + ], + ); + + $item->load([ + 'productVariant.product:id,name', + 'productVariant.media', + ]); + + return $this->presentDraftItem($item); + } + + public function removeDraftItem(User $user, ProductVariant $productVariant): void + { + OrderItem::query() + ->whereNull('order_id') + ->where('user_id', $user->id) + ->where('product_variant_id', $productVariant->id) + ->delete(); + } + + /** + * @return list> + */ + public function resyncDraftPrices(User $user, string $priceTypeValue): array + { + $priceType = PriceType::from($priceTypeValue); + + $items = $this->draftItemsQuery($user) + ->with([ + 'productVariant.product:id,name', + 'productVariant.media', + ]) + ->get(); + + foreach ($items as $item) { + $price = ProductPrice::query() + ->where('variant_id', $item->product_variant_id) + ->where('type', $priceType) + ->first(); + + if ($price === null) { + $item->delete(); + + continue; + } + + $unitPrice = (int) $price->price; + $item->unit_price = $unitPrice; + $item->subtotal = $unitPrice * $item->quantity; + $item->save(); + } + + return $this->draftItemsForUser($user); + } + /** * @param array $validated */ @@ -162,8 +267,21 @@ public function create(array $validated, User $user): Order { return DB::transaction(function () use ($validated, $user): Order { $priceType = $this->resolvePriceType($validated['channel'], $validated['price_type']); - $lineItems = $this->buildLineItems($validated['items'], $priceType); - $subtotal = array_sum(array_column($lineItems, 'subtotal')); + + /** @var EloquentCollection $draftItems */ + $draftItems = $this->draftItemsQuery($user) + ->lockForUpdate() + ->get(); + + if ($draftItems->isEmpty()) { + throw ValidationException::withMessages([ + 'items' => 'Tambahkan minimal satu produk ke keranjang.', + ]); + } + + $this->applyDraftPrices($draftItems, $priceType); + + $subtotal = $draftItems->sum('subtotal'); $discount = (int) ($validated['discount'] ?? 0); $marketplaceFee = (int) ($validated['marketplace_fee'] ?? 0); $netAmount = max($subtotal - $discount - $marketplaceFee, 0); @@ -181,9 +299,10 @@ public function create(array $validated, User $user): Order 'notes' => $validated['notes'] ?? null, ]); - foreach ($lineItems as $itemData) { - $orderItem = $order->items()->create($itemData); - $this->decrementStock($orderItem); + foreach ($draftItems as $item) { + $item->order_id = $order->id; + $item->save(); + $this->decrementStock($item); } return $order; @@ -342,6 +461,57 @@ private function resolvePriceType(string $channel, string $priceType): PriceType return $priceTypeEnum; } + /** + * @return Builder + */ + private function draftItemsQuery(User $user): Builder + { + return OrderItem::query() + ->whereNull('order_id') + ->where('user_id', $user->id); + } + + /** + * @return array + */ + private function presentDraftItem(OrderItem $item): array + { + $variant = $item->productVariant; + + return [ + 'product_variant_id' => $item->product_variant_id, + 'product_name' => $variant?->product?->name ?? '', + 'variant_name' => $variant?->name ?? '', + 'quantity' => (string) $item->quantity, + 'unit_price' => $item->unit_price, + 'images' => $variant ? MediaPresenter::collection($variant, 'images') : [], + ]; + } + + /** + * @param EloquentCollection $items + */ + private function applyDraftPrices(EloquentCollection $items, PriceType $priceType): void + { + foreach ($items as $index => $item) { + $price = ProductPrice::query() + ->where('variant_id', $item->product_variant_id) + ->where('type', $priceType) + ->first(); + + if ($price === null) { + throw ValidationException::withMessages([ + "items.{$index}.product_variant_id" => 'Harga untuk tipe harga ini belum diatur.', + ]); + } + + $unitPrice = (int) $price->price; + $item->unit_price = $unitPrice; + $item->subtotal = $unitPrice * $item->quantity; + $item->save(); + } + } + private function decrementStock(OrderItem $item): void { ProductVariant::query() diff --git a/app/Services/Manage/PurchaseService.php b/app/Services/Manage/PurchaseService.php index bcb6b93..38cf835 100644 --- a/app/Services/Manage/PurchaseService.php +++ b/app/Services/Manage/PurchaseService.php @@ -12,6 +12,7 @@ use App\Support\Media\MediaPresenter; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Validation\ValidationException; @@ -81,11 +82,11 @@ public function supplierOptions(): array /** * @return Collection */ - public function catalogItems(?Purchase $purchase = null): Collection + public function catalogItems(?Purchase $purchase = null, ?User $user = null): Collection { $purchasePriceIds = $purchase ? $purchase->items()->pluck('raw_material_price_id')->all() - : []; + : ($user ? $this->draftItemsQuery($user)->pluck('raw_material_price_id')->all() : []); return RawMaterial::query() ->with([ @@ -141,14 +142,84 @@ public function findForEdit(Purchase $purchase): Purchase return $purchase; } + /** + * @return list> + */ + public function draftItemsForUser(User $user): array + { + return $this->draftItemsQuery($user) + ->with([ + 'rawMaterialPrice.rawMaterial:id,name,unit', + 'rawMaterialPrice.media', + ]) + ->get() + ->map(fn (PurchaseItem $item) => $this->presentDraftItem($item)) + ->values() + ->all(); + } + + /** + * @param array $validated + * @return array + */ + public function syncDraftItem(array $validated, User $user): array + { + $price = RawMaterialPrice::query() + ->with('rawMaterial:id,name,unit') + ->findOrFail($validated['raw_material_price_id']); + + $quantity = (float) $validated['quantity']; + $unitPrice = (int) $price->price; + $subtotal = (int) round($quantity * $unitPrice); + + $item = PurchaseItem::query()->updateOrCreate( + [ + 'user_id' => $user->id, + 'raw_material_price_id' => $price->id, + 'purchase_id' => null, + ], + [ + 'quantity' => $quantity, + 'unit_price' => $unitPrice, + 'subtotal' => $subtotal, + ], + ); + + $item->load([ + 'rawMaterialPrice.rawMaterial:id,name,unit', + 'rawMaterialPrice.media', + ]); + + return $this->presentDraftItem($item); + } + + public function removeDraftItem(User $user, RawMaterialPrice $rawMaterialPrice): void + { + PurchaseItem::query() + ->whereNull('purchase_id') + ->where('user_id', $user->id) + ->where('raw_material_price_id', $rawMaterialPrice->id) + ->delete(); + } + /** * @param array $validated */ public function create(array $validated, User $user): Purchase { return DB::transaction(function () use ($validated, $user): Purchase { - $lineItems = $this->buildLineItems($validated['items']); - $subtotal = array_sum(array_column($lineItems, 'subtotal')); + /** @var EloquentCollection $draftItems */ + $draftItems = $this->draftItemsQuery($user) + ->lockForUpdate() + ->get(); + + if ($draftItems->isEmpty()) { + throw ValidationException::withMessages([ + 'items' => 'Tambahkan minimal satu bahan baku ke keranjang.', + ]); + } + + $subtotal = $draftItems->sum('subtotal'); $discount = (int) ($validated['discount'] ?? 0); $total = max($subtotal - $discount, 0); @@ -161,9 +232,10 @@ public function create(array $validated, User $user): Purchase 'notes' => $validated['notes'] ?? null, ]); - foreach ($lineItems as $itemData) { - $purchaseItem = $purchase->items()->create($itemData); - $this->incrementStock($purchaseItem); + foreach ($draftItems as $item) { + $item->purchase_id = $purchase->id; + $item->save(); + $this->incrementStock($item); } $this->syncPhotos($purchase, $validated); @@ -268,6 +340,35 @@ private function syncPhotos(Purchase $purchase, array $validated): void ); } + /** + * @return Builder + */ + private function draftItemsQuery(User $user): Builder + { + return PurchaseItem::query() + ->whereNull('purchase_id') + ->where('user_id', $user->id); + } + + /** + * @return array + */ + private function presentDraftItem(PurchaseItem $item): array + { + $price = $item->rawMaterialPrice; + $rawMaterial = $price?->rawMaterial; + + return [ + 'raw_material_price_id' => $item->raw_material_price_id, + 'raw_material_name' => $rawMaterial?->name ?? '', + 'variant' => $price?->variant ?? '', + 'unit_abbreviation' => $rawMaterial?->unit?->abbreviation() ?? '', + 'quantity' => $item->quantity_input, + 'unit_price' => $item->unit_price, + 'images' => $price ? MediaPresenter::collection($price, 'images') : [], + ]; + } + private function incrementStock(PurchaseItem $item): void { RawMaterialPrice::query() diff --git a/database/migrations/2026_06_11_120002_create_purchase_items_table.php b/database/migrations/2026_06_11_120002_create_purchase_items_table.php index 2c9b09e..6282625 100644 --- a/database/migrations/2026_06_11_120002_create_purchase_items_table.php +++ b/database/migrations/2026_06_11_120002_create_purchase_items_table.php @@ -11,7 +11,8 @@ public function up(): void Schema::create('purchase_items', function (Blueprint $table) { $table->id(); - $table->foreignId('purchase_id')->constrained()->cascadeOnDelete(); + $table->foreignId('purchase_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('user_id')->nullable()->constrained()->cascadeOnDelete(); $table->foreignId('raw_material_price_id')->constrained()->cascadeOnDelete(); $table->decimal('quantity', 18, 4); diff --git a/database/migrations/2026_06_12_100002_create_order_items_table.php b/database/migrations/2026_06_12_100002_create_order_items_table.php index 17e9a4a..217af1b 100644 --- a/database/migrations/2026_06_12_100002_create_order_items_table.php +++ b/database/migrations/2026_06_12_100002_create_order_items_table.php @@ -11,7 +11,8 @@ public function up(): void Schema::create('order_items', function (Blueprint $table) { $table->id(); - $table->foreignId('order_id')->constrained()->cascadeOnDelete(); + $table->foreignId('order_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('user_id')->nullable()->constrained()->cascadeOnDelete(); $table->foreignId('product_variant_id')->constrained()->restrictOnDelete(); $table->unsignedInteger('quantity'); diff --git a/resources/js/components/admin/manage/orders/OrderPosForm.vue b/resources/js/components/admin/manage/orders/OrderPosForm.vue index 068af94..34dc97e 100644 --- a/resources/js/components/admin/manage/orders/OrderPosForm.vue +++ b/resources/js/components/admin/manage/orders/OrderPosForm.vue @@ -32,6 +32,7 @@ import { } from '@/components/ui/select'; import { Separator } from '@/components/ui/separator'; import { Textarea } from '@/components/ui/textarea'; +import { apiFetch } from '@/lib/api'; import { getFirstCoverImage } from '@/lib/catalog-cover'; import { formErrors } from '@/lib/form'; import { formatRupiah, parseRupiah } from '@/lib/rupiah'; @@ -52,11 +53,14 @@ const props = defineProps<{ notes: string; items: OrderCartItem[]; }; + draftItems?: OrderCartItem[]; submitUrl: string; method: 'post' | 'put'; submitLabel: string; }>(); +const isCreateMode = computed(() => props.method === 'post'); + const search = ref(''); const cart = ref([]); @@ -94,6 +98,17 @@ watch( { immediate: true }, ); +function populateDraftItems() { + if (!isCreateMode.value || !props.draftItems?.length) { + return; + } + + cart.value = props.draftItems.map((item) => ({ ...item })); + toast.info('Keranjang dipulihkan dari data tersimpan.'); +} + +populateDraftItems(); + watch( () => form.channel, (channel) => { @@ -107,6 +122,43 @@ watch( }, ); +watch( + () => form.price_type, + async (priceType) => { + if (!isCreateMode.value || cart.value.length === 0) { + return; + } + + try { + const { items } = await apiFetch<{ items: OrderCartItem[] }>( + '/admin/manage/orders/draft-items/resync-prices', + { + method: 'PUT', + body: JSON.stringify({ price_type: priceType }), + }, + ); + + cart.value = items.map((item) => ({ ...item })); + } catch (error) { + toast.error(error instanceof Error ? error.message : 'Gagal memperbarui harga keranjang.'); + } + }, +); + +function upsertCartItem(item: OrderCartItem) { + const index = cart.value.findIndex( + (cartItem) => cartItem.product_variant_id === item.product_variant_id, + ); + + if (index === -1) { + cart.value.push({ ...item }); + + return; + } + + cart.value[index] = { ...item }; +} + const filteredCatalog = computed(() => { const keyword = search.value.trim().toLowerCase(); @@ -138,7 +190,20 @@ function lineSubtotal(item: OrderCartItem): number { return quantity * item.unit_price; } -function addToCart(product: OrderCatalogItem, variant: ProductVariantItem) { +async function syncDraftItem(variantId: number, quantity: number) { + const { item } = await apiFetch<{ item: OrderCartItem }>('/admin/manage/orders/draft-items', { + method: 'POST', + body: JSON.stringify({ + product_variant_id: variantId, + quantity, + price_type: form.price_type, + }), + }); + + upsertCartItem(item); +} + +async function addToCart(product: OrderCatalogItem, variant: ProductVariantItem) { const price = getVariantPrice(variant); if (!price) { @@ -150,10 +215,20 @@ function addToCart(product: OrderCatalogItem, variant: ProductVariantItem) { const existing = cart.value.find( (item) => item.product_variant_id === variant.id, ); + const nextQty = existing ? (Number(existing.quantity) || 0) + 1 : 1; + + if (isCreateMode.value) { + try { + await syncDraftItem(variant.id, nextQty); + } catch (error) { + toast.error(error instanceof Error ? error.message : 'Gagal menyimpan item ke keranjang.'); + } + + return; + } if (existing) { - const currentQty = Number(existing.quantity) || 0; - existing.quantity = String(currentQty + 1); + existing.quantity = String(nextQty); return; } @@ -168,16 +243,40 @@ function addToCart(product: OrderCatalogItem, variant: ProductVariantItem) { }); } -function removeFromCart(index: number) { +async function removeFromCart(index: number) { + const item = cart.value[index]; + + if (isCreateMode.value) { + try { + await apiFetch(`/admin/manage/orders/draft-items/${item.product_variant_id}`, { + method: 'DELETE', + }); + } catch (error) { + toast.error(error instanceof Error ? error.message : 'Gagal menghapus item dari keranjang.'); + + return; + } + } + cart.value.splice(index, 1); } -function adjustQuantity(index: number, delta: number) { +async function adjustQuantity(index: number, delta: number) { const item = cart.value[index]; const nextQty = (Number(item.quantity) || 0) + delta; if (nextQty < 1) { - removeFromCart(index); + await removeFromCart(index); + + return; + } + + if (isCreateMode.value) { + try { + await syncDraftItem(item.product_variant_id, nextQty); + } catch (error) { + toast.error(error instanceof Error ? error.message : 'Gagal memperbarui jumlah item.'); + } return; } @@ -185,6 +284,27 @@ function adjustQuantity(index: number, delta: number) { item.quantity = String(nextQty); } +async function syncCartItemQuantity(index: number) { + const item = cart.value[index]; + const nextQty = Number(item.quantity) || 0; + + if (nextQty < 1) { + await removeFromCart(index); + + return; + } + + if (!isCreateMode.value) { + return; + } + + try { + await syncDraftItem(item.product_variant_id, nextQty); + } catch (error) { + toast.error(error instanceof Error ? error.message : 'Gagal memperbarui jumlah item.'); + } +} + function buildFormData(): FormData { const formData = new FormData(); @@ -202,10 +322,12 @@ function buildFormData(): FormData { formData.append('marketplace_fee', parseRupiah(form.marketplace_fee)); formData.append('notes', form.notes); - cart.value.forEach((item, index) => { - formData.append(`items[${index}][product_variant_id]`, String(item.product_variant_id)); - formData.append(`items[${index}][quantity]`, item.quantity); - }); + if (props.method === 'put') { + cart.value.forEach((item, index) => { + formData.append(`items[${index}][product_variant_id]`, String(item.product_variant_id)); + formData.append(`items[${index}][quantity]`, item.quantity); + }); + } return formData; } @@ -398,7 +520,7 @@ function submit() { + class="h-8 text-center" @change="syncCartItemQuantity(index)" /> + step="0.0001" class="h-8 text-center" + @change="syncCartItemQuantity(index)" />