Cart stock updation added
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,3 @@
|
||||
# ordering-site/*
|
||||
# ordering_site/
|
||||
ordering-site/*
|
||||
ordering_site/
|
||||
counter-frontend/
|
||||
@@ -34,6 +34,9 @@ public class OrderController {
|
||||
@Autowired
|
||||
private com.rit.canteen.sales.service.OrderArchiverService orderArchiverService;
|
||||
|
||||
// Use ThreadLocal to safely store conflicts for the current request context
|
||||
private static final ThreadLocal<List<Map<String, Object>>> requestConflicts = new ThreadLocal<>();
|
||||
|
||||
@GetMapping("/all")
|
||||
public ResponseEntity<?> getAllOrders(
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate,
|
||||
@@ -113,54 +116,79 @@ public class OrderController {
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Map<String, Object>> placeOrder(@RequestBody Order order) {
|
||||
// Link items to the order for bidirectional relationship
|
||||
if (order.getItems() != null) {
|
||||
for (OrderItem item : order.getItems()) {
|
||||
System.out.println("🛒 RECEIVED ITEM: " + item.getProductName() + " | StallID: " + item.getStallId() + " | StallName: " + item.getStallName());
|
||||
item.setOrder(order);
|
||||
}
|
||||
@org.springframework.transaction.annotation.Transactional
|
||||
public ResponseEntity<?> placeOrder(@RequestBody Order order) {
|
||||
// 1. Pre-validation and linking
|
||||
if (order.getItems() == null || order.getItems().isEmpty()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("success", false, "message", "Order must have items"));
|
||||
}
|
||||
|
||||
// 2. Atomic Stock Check & Update
|
||||
List<Map<String, Object>> stockConflicts = new ArrayList<>();
|
||||
requestConflicts.remove(); // Clear before use
|
||||
|
||||
// Use the actual creation time or current time for counting
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
order.setCreatedAt(now);
|
||||
|
||||
// Calculate start of current day to find how many orders placed today
|
||||
LocalDateTime startOfDay = now.toLocalDate().atStartOfDay();
|
||||
long todaysOrderCount = orderRepository.countByCreatedAtGreaterThanEqual(startOfDay);
|
||||
|
||||
// Generate formatted display ID (#001, #002...) resetting daily
|
||||
String displayId = String.format("%03d", todaysOrderCount + 1);
|
||||
order.setDisplayOrderId(displayId);
|
||||
|
||||
// Save the complete order first
|
||||
Order savedOrder = orderRepository.save(order);
|
||||
|
||||
// --- Reduct Stock Logic ---
|
||||
if (savedOrder.getItems() != null) {
|
||||
for (OrderItem item : savedOrder.getItems()) {
|
||||
Long productId = item.getProductId();
|
||||
if (productId != null) {
|
||||
productRepository.findById(productId).ifPresent(product -> {
|
||||
int currentStock = product.getStock() != null ? product.getStock() : 0;
|
||||
product.setStock(currentStock - item.getQuantity());
|
||||
productRepository.save(product);
|
||||
System.out.println("Updating Stock for " + product.getName() + ": " + currentStock + " -> " + product.getStock());
|
||||
});
|
||||
for (OrderItem item : order.getItems()) {
|
||||
Long productId = item.getProductId();
|
||||
if (productId != null) {
|
||||
int updatedRows = productRepository.decrementStock(productId, item.getQuantity());
|
||||
|
||||
if (updatedRows == 0) {
|
||||
com.rit.canteen.sales.model.Product p = productRepository.findById(productId).orElse(null);
|
||||
int left = (p != null && p.getStock() != null) ? p.getStock() : 0;
|
||||
|
||||
Map<String, Object> conflict = new HashMap<>();
|
||||
conflict.put("productId", productId);
|
||||
conflict.put("productName", item.getProductName());
|
||||
conflict.put("requested", item.getQuantity());
|
||||
conflict.put("available", left);
|
||||
stockConflicts.add(conflict);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!stockConflicts.isEmpty()) {
|
||||
requestConflicts.set(stockConflicts);
|
||||
throw new RuntimeException("CONCURRENCY_STOCK_FAILURE");
|
||||
}
|
||||
|
||||
// 3. Complete Order Details
|
||||
for (OrderItem item : order.getItems()) {
|
||||
item.setOrder(order);
|
||||
}
|
||||
|
||||
System.out.println("Placed Daily Order: " + savedOrder.getId() + " -> Display ID: #" + displayId);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
order.setCreatedAt(now);
|
||||
LocalDateTime startOfDay = now.toLocalDate().atStartOfDay();
|
||||
long todaysOrderCount = orderRepository.countByCreatedAtGreaterThanEqual(startOfDay);
|
||||
String displayId = String.format("%03d", todaysOrderCount + 1);
|
||||
order.setDisplayOrderId(displayId);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("orderNumber", savedOrder.getOrderNumber()); // Secure ID for QR
|
||||
response.put("displayOrderId", savedOrder.getDisplayOrderId()); // Sequential ID (#001)
|
||||
response.put("message", "Order placed successfully");
|
||||
// 4. Final Save
|
||||
Order savedOrder = orderRepository.save(order);
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
System.out.println("Placed Order: " + savedOrder.getId() + " -> Display ID: #" + displayId);
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"success", true,
|
||||
"orderNumber", savedOrder.getOrderNumber(),
|
||||
"displayOrderId", savedOrder.getDisplayOrderId(),
|
||||
"message", "Order placed successfully"
|
||||
));
|
||||
}
|
||||
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<?> handleRuntimeException(RuntimeException e) {
|
||||
if ("CONCURRENCY_STOCK_FAILURE".equals(e.getMessage())) {
|
||||
List<Map<String, Object>> conflicts = requestConflicts.get();
|
||||
requestConflicts.remove();
|
||||
return ResponseEntity.status(400).body(Map.of(
|
||||
"success", false,
|
||||
"errorType", "STOCK_ERROR",
|
||||
"message", "Some items in your cart are no longer available in the requested quantity.",
|
||||
"conflicts", conflicts != null ? conflicts : new ArrayList<>()
|
||||
));
|
||||
}
|
||||
return ResponseEntity.status(500).body(Map.of("success", false, "message", e.getMessage() != null ? e.getMessage() : "Internal Server Error"));
|
||||
}
|
||||
|
||||
@GetMapping("/user/{userId}")
|
||||
|
||||
@@ -24,4 +24,7 @@ public interface ProductRepository extends JpaRepository<Product, Long> {
|
||||
List<String> findDistinctCategories();
|
||||
|
||||
boolean existsByNameAndCategory(String name, String category);
|
||||
@org.springframework.data.jpa.repository.Modifying
|
||||
@org.springframework.data.jpa.repository.Query("UPDATE Product p SET p.stock = p.stock - :quantity WHERE p.id = :id AND p.stock >= :quantity")
|
||||
int decrementStock(@org.springframework.data.repository.query.Param("id") Long id, @org.springframework.data.repository.query.Param("quantity") int quantity);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import LoginScreen from './pages/LoginScreen';
|
||||
import ProfileScreen from './pages/ProfileScreen';
|
||||
import ChangePinScreen from './pages/ChangePinScreen';
|
||||
import StallDetailScreen from './pages/StallDetailScreen';
|
||||
import StockAlert from './components/StockAlert';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
@@ -21,6 +22,7 @@ function App() {
|
||||
<AuthProvider>
|
||||
<FoodProvider>
|
||||
<CartProvider>
|
||||
<StockAlert />
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/login" element={<LoginScreen />} />
|
||||
|
||||
@@ -214,3 +214,24 @@
|
||||
min-width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.limit-badge {
|
||||
background-color: #fef2f2;
|
||||
color: #ef4444;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
padding: 1px 6px;
|
||||
border-radius: 20px;
|
||||
white-space: nowrap;
|
||||
border: 1px solid #fee2e2;
|
||||
}
|
||||
|
||||
.at-limit button:last-child {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
|
||||
.item-card.limit-reached {
|
||||
border-left: 3px solid #ef4444;
|
||||
}
|
||||
|
||||
@@ -13,10 +13,11 @@ const ItemCard: React.FC<ItemCardProps> = ({ item, isLast }) => {
|
||||
const navigate = useNavigate();
|
||||
const { addToCart, updateQuantity, getItemQuantity } = useCart();
|
||||
const quantity = getItemQuantity(item.id);
|
||||
const isLimitReached = item.stock !== undefined && quantity >= item.stock && item.stock > 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`item-card ${isLast ? 'last' : ''} ${item.stock === 0 ? 'out-of-stock' : ''}`}
|
||||
className={`item-card ${isLast ? 'last' : ''} ${item.stock === 0 ? 'out-of-stock' : ''} ${isLimitReached ? 'limit-reached' : ''}`}
|
||||
onClick={() => navigate(`/item/${item.id}`)}
|
||||
>
|
||||
<div className="item-info">
|
||||
@@ -26,6 +27,7 @@ const ItemCard: React.FC<ItemCardProps> = ({ item, isLast }) => {
|
||||
</div>
|
||||
{item.isPopular && <span className="bestseller-badge">Popular</span>}
|
||||
{item.stock === 0 && <span className="out-of-stock-badge">Out of Stock</span>}
|
||||
{isLimitReached && <span className="limit-badge">Only {item.stock} left</span>}
|
||||
</div>
|
||||
|
||||
<h3 className="item-name">{item.name}</h3>
|
||||
@@ -53,10 +55,13 @@ const ItemCard: React.FC<ItemCardProps> = ({ item, isLast }) => {
|
||||
{item.stock === 0 ? 'SOLD OUT' : 'ADD'}
|
||||
</button>
|
||||
) : (
|
||||
<div className="quantity-controls">
|
||||
<div className={`quantity-controls ${isLimitReached ? 'at-limit' : ''}`}>
|
||||
<button onClick={() => updateQuantity(item.id, -1)}>−</button>
|
||||
<span className="quantity">{quantity}</span>
|
||||
<button onClick={() => addToCart(item)}>+</button>
|
||||
<button
|
||||
onClick={() => addToCart(item)}
|
||||
className={isLimitReached ? 'disabled' : ''}
|
||||
>+</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,8 @@ interface CartContextType {
|
||||
getItemQuantity: (itemId: string) => number;
|
||||
totalItems: number;
|
||||
totalPrice: number;
|
||||
stockError: string | null;
|
||||
clearStockError: () => void;
|
||||
}
|
||||
|
||||
const CartContext = createContext<CartContextType | undefined>(undefined);
|
||||
@@ -19,14 +21,25 @@ export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
const savedCart = localStorage.getItem('cart');
|
||||
return savedCart ? JSON.parse(savedCart) : [];
|
||||
});
|
||||
const [stockError, setStockError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('cart', JSON.stringify(cart));
|
||||
}, [cart]);
|
||||
|
||||
const clearStockError = () => setStockError(null);
|
||||
|
||||
const addToCart = (item: FoodItem) => {
|
||||
setCart((prevCart) => {
|
||||
const existingIndex = prevCart.findIndex((i) => i.id === item.id);
|
||||
|
||||
// Stock check
|
||||
const currentQty = existingIndex !== -1 ? prevCart[existingIndex].quantity : 0;
|
||||
if (item.stock !== undefined && currentQty >= item.stock) {
|
||||
setStockError(`Only ${item.stock} left for ${item.name}`);
|
||||
return prevCart;
|
||||
}
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
const newCart = [...prevCart];
|
||||
newCart[existingIndex] = {
|
||||
@@ -47,9 +60,17 @@ export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
setCart((prevCart) => {
|
||||
const index = prevCart.findIndex((item) => item.id === itemId);
|
||||
if (index !== -1) {
|
||||
const newCart = [...prevCart];
|
||||
const newQty = newCart[index].quantity + delta;
|
||||
const item = prevCart[index];
|
||||
const newQty = item.quantity + delta;
|
||||
|
||||
// Stock check for increment
|
||||
if (delta > 0 && item.stock !== undefined && item.quantity >= item.stock) {
|
||||
setStockError(`Only ${item.stock} left for ${item.name}`);
|
||||
return prevCart;
|
||||
}
|
||||
|
||||
if (newQty > 0) {
|
||||
const newCart = [...prevCart];
|
||||
newCart[index] = { ...newCart[index], quantity: newQty };
|
||||
return newCart;
|
||||
} else {
|
||||
@@ -84,6 +105,8 @@ export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
getItemQuantity,
|
||||
totalItems,
|
||||
totalPrice,
|
||||
stockError,
|
||||
clearStockError,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -24,8 +24,8 @@ export const FoodProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchData = async () => {
|
||||
setIsLoading(true);
|
||||
const fetchData = async (silent = false) => {
|
||||
if (!silent) setIsLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const [baseItemsRes, productsRes, stallsRes] = await Promise.all([
|
||||
@@ -119,15 +119,22 @@ export const FoodProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
setFoodItems(mappedFoodItems);
|
||||
setStalls(mappedStalls);
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
if (!silent) setError(err.message);
|
||||
console.error('Error fetching food data:', err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
if (!silent) setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
|
||||
// Silent background polling every 10 seconds to sync stock
|
||||
const pollInterval = setInterval(() => {
|
||||
fetchData(true);
|
||||
}, 10000);
|
||||
|
||||
return () => clearInterval(pollInterval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Smartphone } from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import Header from '../components/Header';
|
||||
import StockConflictModal from '../components/StockConflictModal';
|
||||
import { useCart } from '../contexts/CartContext';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { useFood } from '../contexts/FoodContext';
|
||||
@@ -17,11 +19,15 @@ const UPI_APPS = [
|
||||
|
||||
const CheckoutScreen: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { cart, totalPrice, clearCart } = useCart();
|
||||
const { cart, totalPrice, clearCart, removeFromCart, updateQuantity } = useCart();
|
||||
const { user } = useAuth();
|
||||
const { refreshData } = useFood();
|
||||
const [selectedApp, setSelectedApp] = useState('gpay');
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
|
||||
// Conflict state
|
||||
const [stockConflicts, setStockConflicts] = useState<any[]>([]);
|
||||
const [showConflictModal, setShowConflictModal] = useState(false);
|
||||
|
||||
const handlePlaceOrder = async () => {
|
||||
if (!user) return;
|
||||
@@ -41,10 +47,6 @@ const CheckoutScreen: React.FC = () => {
|
||||
stallName: item.stallName || null
|
||||
}))
|
||||
};
|
||||
|
||||
console.warn('❗ DIAGNOSTIC: PRE-ORDER PAYLOAD CHECK');
|
||||
console.table(orderData.items.map(i => ({ Name: i.productName, StallID: i.stallId, StallName: i.stallName })));
|
||||
console.log('Full Payload:', JSON.stringify(orderData, null, 2));
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://${window.location.hostname}:8080/api/orders`, {
|
||||
@@ -53,26 +55,23 @@ const CheckoutScreen: React.FC = () => {
|
||||
body: JSON.stringify(orderData),
|
||||
});
|
||||
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (contentType && contentType.indexOf("application/json") !== -1) {
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
await refreshData();
|
||||
clearCart();
|
||||
navigate('/success', {
|
||||
state: {
|
||||
orderNumber: data.orderNumber,
|
||||
displayOrderId: data.displayOrderId
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('Order Logic Error:', data.message || data);
|
||||
alert(data.message || 'Failed to place order');
|
||||
}
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
await refreshData();
|
||||
clearCart();
|
||||
navigate('/success', {
|
||||
state: {
|
||||
orderNumber: data.orderNumber,
|
||||
displayOrderId: data.displayOrderId
|
||||
}
|
||||
});
|
||||
} else if (data.errorType === 'STOCK_ERROR') {
|
||||
console.error('Final Step Stock Conflict:', data.conflicts);
|
||||
setStockConflicts(data.conflicts || []);
|
||||
setShowConflictModal(true);
|
||||
await refreshData(true); // Sync background stock
|
||||
} else {
|
||||
const text = await response.text();
|
||||
console.error('Order Server Error (Non-JSON):', text);
|
||||
alert('Server Error. Check console for details.');
|
||||
alert(data.message || 'Failed to place order');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Order error:', error);
|
||||
@@ -82,6 +81,24 @@ const CheckoutScreen: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveConflictItem = (productId: number) => {
|
||||
removeFromCart(productId.toString());
|
||||
const remaining = stockConflicts.filter(c => c.productId !== productId);
|
||||
setStockConflicts(remaining);
|
||||
if (remaining.length === 0) setShowConflictModal(false);
|
||||
};
|
||||
|
||||
const handleAdjustConflictQuantity = (productId: number, newQty: number) => {
|
||||
const item = cart.find(i => i.id === productId.toString());
|
||||
if (item) {
|
||||
const delta = newQty - item.quantity;
|
||||
updateQuantity(productId.toString(), delta);
|
||||
}
|
||||
const remaining = stockConflicts.filter(c => c.productId !== productId);
|
||||
setStockConflicts(remaining);
|
||||
if (remaining.length === 0) setShowConflictModal(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container checkout-page">
|
||||
<Header title="Checkout" showCart={false} />
|
||||
@@ -141,6 +158,17 @@ const CheckoutScreen: React.FC = () => {
|
||||
{isProcessing ? 'Processing...' : `Pay ₹${totalPrice.toFixed(2)} & Place Order`}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{showConflictModal && (
|
||||
<StockConflictModal
|
||||
conflicts={stockConflicts}
|
||||
onRemoveItem={handleRemoveConflictItem}
|
||||
onAdjustQuantity={handleAdjustConflictQuantity}
|
||||
onClose={() => navigate('/cart')}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -259,3 +259,22 @@
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.limit-reached-info {
|
||||
background-color: #fef2f2;
|
||||
border: 1px solid #fee2e2;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #ef4444;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.footer-quantity-controls button:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import Header from '../components/Header';
|
||||
import { useCart } from '../contexts/CartContext';
|
||||
import { useFood } from '../contexts/FoodContext';
|
||||
@@ -13,6 +14,7 @@ const ItemDetailScreen: React.FC = () => {
|
||||
|
||||
const item = foodItems.find((i) => i.id === itemId);
|
||||
const quantity = item ? getItemQuantity(item.id) : 0;
|
||||
const isLimitReached = item && item.stock !== undefined && quantity >= item.stock && item.stock > 0;
|
||||
|
||||
if (isLoading && foodItems.length === 0) {
|
||||
return <div className="container" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>Loading...</div>;
|
||||
@@ -69,10 +71,17 @@ const ItemDetailScreen: React.FC = () => {
|
||||
<div className="info-row">
|
||||
<span className="info-label">Availability</span>
|
||||
<span className={`info-value ${item.stock && item.stock > 0 ? 'green' : 'red'}`}>
|
||||
{item.stock && item.stock > 0 ? 'In Stock' : 'Out of Stock'}
|
||||
{item.stock && item.stock > 0 ? `In Stock (${item.stock} left)` : 'Out of Stock'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isLimitReached && (
|
||||
<div className="limit-reached-info">
|
||||
<AlertCircle size={18} />
|
||||
<span>You have reached the maximum available quantity ({item.stock}) for this item.</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -98,7 +107,7 @@ const ItemDetailScreen: React.FC = () => {
|
||||
<span className="quantity">{quantity}</span>
|
||||
<button
|
||||
onClick={() => addToCart(item)}
|
||||
disabled={item.stock !== undefined && quantity >= item.stock}
|
||||
disabled={isLimitReached}
|
||||
>+</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -65,21 +65,21 @@ const MyOrdersScreen: React.FC = () => {
|
||||
const available: FoodItem[] = [];
|
||||
|
||||
order.items.forEach(orderItem => {
|
||||
// Find the current food item by name (since orders store names)
|
||||
// Robustness: matching by name is fallback if IDs aren't directly available in OrderItem DTO
|
||||
const currentItem = foodItems.find(fi => fi.name === orderItem.productName);
|
||||
|
||||
if (currentItem) {
|
||||
if (currentItem.stock && currentItem.stock > 0) {
|
||||
// Add multiple times based on original quantity
|
||||
for (let i = 0; i < orderItem.quantity; i++) {
|
||||
available.push(currentItem);
|
||||
}
|
||||
} else {
|
||||
missing.push(orderItem.productName);
|
||||
if (currentItem && currentItem.stock !== undefined && currentItem.stock > 0) {
|
||||
// Only add up to available stock
|
||||
const canAdd = Math.min(orderItem.quantity, currentItem.stock);
|
||||
|
||||
for (let i = 0; i < canAdd; i++) {
|
||||
available.push(currentItem);
|
||||
}
|
||||
|
||||
if (canAdd < orderItem.quantity) {
|
||||
missing.push(`${orderItem.productName} (only ${canAdd} of ${orderItem.quantity} available)`);
|
||||
}
|
||||
} else {
|
||||
missing.push(orderItem.productName); // Item no longer exists in catalog
|
||||
missing.push(orderItem.productName); // Item no longer exists or out of stock
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user