feat: implement stock validation on purchase removal and add global toast error handling for flash messages
This commit is contained in:
parent
2d5d0b8eb1
commit
23cc07596c
@ -79,7 +79,11 @@ public function store(OrderRequest $request): RedirectResponse
|
||||
'price_type' => $item['price_type'],
|
||||
]);
|
||||
|
||||
Product::find($item['product_id'])->decrement('stock', $item['qty']);
|
||||
$product = Product::find($item['product_id']);
|
||||
if ($product->stock < $item['qty']) {
|
||||
throw new \InvalidArgumentException("Stok produk {$product->name} tidak mencukupi. Sisa stok: {$product->stock}");
|
||||
}
|
||||
$product->decrement('stock', $item['qty']);
|
||||
}
|
||||
|
||||
OrderItem::where('user_id', auth()->id())
|
||||
@ -88,6 +92,8 @@ public function store(OrderRequest $request): RedirectResponse
|
||||
});
|
||||
|
||||
return redirect()->route('order.index')->with('success', 'Pesanan berhasil disimpan');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return redirect()->back()->withInput()->with('error', $e->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
LogHelper::logException($e, 'Failed to store order', [
|
||||
'customer_name' => $validated['customer_name'],
|
||||
@ -166,11 +172,17 @@ public function update(OrderRequest $request, Order $order): RedirectResponse
|
||||
'price_type' => $item['price_type'],
|
||||
]);
|
||||
|
||||
Product::find($item['product_id'])->decrement('stock', $item['qty']);
|
||||
$product = Product::find($item['product_id']);
|
||||
if ($product->stock < $item['qty']) {
|
||||
throw new \InvalidArgumentException("Stok produk {$product->name} tidak mencukupi. Sisa stok: {$product->stock}");
|
||||
}
|
||||
$product->decrement('stock', $item['qty']);
|
||||
}
|
||||
});
|
||||
|
||||
return redirect()->route('order.index')->with('success', 'Pesanan berhasil diperbarui');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return redirect()->back()->withInput()->with('error', $e->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
LogHelper::logException($e, 'Failed to update order', [
|
||||
'order_id' => $order->id,
|
||||
|
||||
@ -106,6 +106,9 @@ public function update(PurchaseRequest $request, Purchase $purchase): RedirectRe
|
||||
]);
|
||||
|
||||
foreach ($purchase->items as $item) {
|
||||
if ($item->product->stock < $item->quantity) {
|
||||
throw new \InvalidArgumentException("Stok produk {$item->product->name} tidak mencukupi untuk dibatalkan. Sisa stok: {$item->product->stock}");
|
||||
}
|
||||
$item->product->decrement('stock', $item->quantity);
|
||||
}
|
||||
|
||||
@ -125,6 +128,8 @@ public function update(PurchaseRequest $request, Purchase $purchase): RedirectRe
|
||||
});
|
||||
|
||||
return redirect()->route('purchase.index')->with('success', 'Data berhasil diperbarui');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return redirect()->back()->withInput()->with('error', $e->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
LogHelper::logException($e, 'Failed to update purchase', [
|
||||
'purchase_id' => $purchase->id,
|
||||
@ -143,12 +148,17 @@ public function destroy(Purchase $purchase): RedirectResponse
|
||||
try {
|
||||
DB::transaction(function () use ($purchase) {
|
||||
foreach ($purchase->items as $item) {
|
||||
if ($item->product->stock < $item->quantity) {
|
||||
throw new \InvalidArgumentException("Stok produk {$item->product->name} tidak mencukupi untuk dihapus. Sisa stok: {$item->product->stock}");
|
||||
}
|
||||
$item->product->decrement('stock', $item->quantity);
|
||||
}
|
||||
$purchase->delete();
|
||||
});
|
||||
|
||||
return redirect()->back()->with('success', 'Data berhasil dihapus');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
LogHelper::logException($e, 'Failed to delete purchase', [
|
||||
'purchase_id' => $purchase->id,
|
||||
@ -164,9 +174,12 @@ public function bulkDestroy(Request $request): RedirectResponse
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($ids) {
|
||||
$purchases = Purchase::with('items')->whereIn('id', $ids)->get();
|
||||
$purchases = Purchase::with('items.product')->whereIn('id', $ids)->get();
|
||||
foreach ($purchases as $purchase) {
|
||||
foreach ($purchase->items as $item) {
|
||||
if ($item->product->stock < $item->quantity) {
|
||||
throw new \InvalidArgumentException("Stok produk {$item->product->name} tidak mencukupi untuk dihapus. Sisa stok: {$item->product->stock}");
|
||||
}
|
||||
$item->product->decrement('stock', $item->quantity);
|
||||
}
|
||||
$purchase->delete();
|
||||
@ -174,6 +187,8 @@ public function bulkDestroy(Request $request): RedirectResponse
|
||||
});
|
||||
|
||||
return redirect()->back()->with('success', 'Data terpilih berhasil dihapus');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return redirect()->back()->with('error', $e->getMessage());
|
||||
} catch (\Throwable $e) {
|
||||
LogHelper::logException($e, 'Failed to bulk delete purchases', [
|
||||
'ids' => $ids,
|
||||
|
||||
@ -16,7 +16,7 @@ public function up(): void
|
||||
$table->string('name', 100);
|
||||
$table->string('slug', 100);
|
||||
$table->text('description')->nullable();
|
||||
$table->integer('stock')->default(0);
|
||||
$table->unsignedInteger('stock')->default(0);
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
|
||||
@ -180,7 +180,12 @@ return;
|
||||
|
||||
post(orderRoutes.store().url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -195,7 +195,12 @@ return;
|
||||
|
||||
patch(orderRoutes.update(order.id).url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -165,7 +165,12 @@ return;
|
||||
|
||||
post(purchaseRoutes.store().url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -170,7 +170,12 @@ export default function PurchaseEdit({ purchase, products }: { purchase: Purchas
|
||||
|
||||
patch(purchaseRoutes.update(purchase.id).url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -20,7 +20,12 @@ export function usePurchaseIndex() {
|
||||
if (purchaseToDelete) {
|
||||
router.delete(purchaseRoutes.destroy(purchaseToDelete.id).url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
setIsDeleteDialogOpen(false);
|
||||
setPurchaseToDelete(null);
|
||||
setRowSelection({});
|
||||
@ -35,7 +40,12 @@ export function usePurchaseIndex() {
|
||||
_method: 'DELETE'
|
||||
}, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
setIsBulkDeleteDialogOpen(false);
|
||||
setRowsToDelete([]);
|
||||
setRowSelection({});
|
||||
|
||||
@ -113,7 +113,12 @@ export default function ProductCreate({ categories }: { categories: Category[] }
|
||||
post(productRoutes.store().url, {
|
||||
forceFormData: true,
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -165,7 +165,12 @@ export default function ProductEdit({ product, categories }: { product: Product,
|
||||
post(productRoutes.update(product.id).url, {
|
||||
forceFormData: true,
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -21,7 +21,12 @@ export function useProductIndex() {
|
||||
if (productToDelete) {
|
||||
router.delete(productRoutes.destroy(productToDelete.id).url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
setIsDeleteDialogOpen(false);
|
||||
setProductToDelete(null);
|
||||
setRowSelection({});
|
||||
@ -36,7 +41,12 @@ export function useProductIndex() {
|
||||
_method: 'DELETE'
|
||||
}, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
setIsBulkDeleteDialogOpen(false);
|
||||
setRowsToDelete([]);
|
||||
setRowSelection({});
|
||||
@ -48,7 +58,14 @@ export function useProductIndex() {
|
||||
router.patch(productRoutes.toggleStatus(id).url, {}, {
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
onSuccess: (response: any) => toast.success(response.props.flash.success),
|
||||
onSuccess: (response: any) => {
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@ -50,7 +50,12 @@ export default function UserCreate({ roles }: { roles: any[] }) {
|
||||
e.preventDefault();
|
||||
post(userRoutes.store().url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -51,7 +51,12 @@ export default function UserEdit({ user, roles }: { user: any, roles: any[] }) {
|
||||
e.preventDefault();
|
||||
post(userRoutes.update(user.id).url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -22,7 +22,12 @@ export function useUserIndex() {
|
||||
if (userToReset) {
|
||||
router.patch(userRoutes.resetPassword(userToReset.id).url, {}, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
setIsResetPasswordOpen(false);
|
||||
setUserToReset(null);
|
||||
},
|
||||
@ -39,7 +44,12 @@ export function useUserIndex() {
|
||||
if (userToDelete) {
|
||||
router.delete(userRoutes.destroy(userToDelete.id).url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
setIsDeleteDialogOpen(false);
|
||||
setUserToDelete(null);
|
||||
setRowSelection({});
|
||||
@ -54,7 +64,12 @@ export function useUserIndex() {
|
||||
_method: 'DELETE'
|
||||
}, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
setIsBulkDeleteDialogOpen(false);
|
||||
setRowsToDelete([]);
|
||||
setRowSelection({});
|
||||
@ -67,7 +82,12 @@ export function useUserIndex() {
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -30,7 +30,12 @@ export default function RoleCreate({
|
||||
e.preventDefault();
|
||||
post(roleRoutes.store().url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -32,7 +32,12 @@ export default function RoleEdit({
|
||||
e.preventDefault();
|
||||
put(roleRoutes.update(role.id).url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -20,7 +20,12 @@ export function useRoleIndex() {
|
||||
if (roleToDelete) {
|
||||
router.delete(roleRoutes.destroy(roleToDelete.id).url, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
setIsDeleteDialogOpen(false);
|
||||
setRoleToDelete(null);
|
||||
setRowSelection({});
|
||||
@ -35,7 +40,12 @@ export function useRoleIndex() {
|
||||
_method: 'DELETE'
|
||||
}, {
|
||||
onSuccess: (response: any) => {
|
||||
toast.success(response.props.flash.success);
|
||||
const flash = response.props.flash;
|
||||
if (flash?.error) {
|
||||
toast.error(flash.error);
|
||||
} else if (flash?.success) {
|
||||
toast.success(flash.success);
|
||||
}
|
||||
setIsBulkDeleteDialogOpen(false);
|
||||
setRowsToDelete([]);
|
||||
setRowSelection({});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user