backend updated for counter login

This commit is contained in:
Sidharth Prabhu
2026-04-17 08:25:17 +05:30
parent 5d247a09e9
commit 2d24b4d71b
4 changed files with 129 additions and 4 deletions

View File

@@ -19,6 +19,9 @@ public class ProductController {
@Autowired @Autowired
private com.rit.canteen.sales.repository.StallRepository stallRepository; private com.rit.canteen.sales.repository.StallRepository stallRepository;
@Autowired
private StockUpdateController stockUpdateController;
@GetMapping @GetMapping
public org.springframework.data.domain.Page<Product> getAllProducts( public org.springframework.data.domain.Page<Product> getAllProducts(
@RequestParam(required = false) String search, @RequestParam(required = false) String search,
@@ -32,9 +35,12 @@ public class ProductController {
@GetMapping("/category/{categoryName}") @GetMapping("/category/{categoryName}")
public List<Product> getProductsByCategory(@PathVariable String categoryName) { public List<Product> getProductsByCategory(@PathVariable String categoryName) {
return productRepository.findAll().stream() return productRepository.findByCategory(categoryName);
.filter(p -> categoryName.equals(p.getCategory())) }
.toList();
@GetMapping("/categories")
public List<String> getAllCategories() {
return productRepository.findDistinctCategories();
} }
@PostMapping @PostMapping
@@ -81,6 +87,9 @@ public class ProductController {
Product updated = productRepository.save(product); Product updated = productRepository.save(product);
updateStallAssociations(updated, productDetails.getStalls()); updateStallAssociations(updated, productDetails.getStalls());
// Broadcast update
stockUpdateController.broadcastStockUpdate(updated.getId(), updated.getStock());
return ResponseEntity.ok(productRepository.findById(updated.getId()).orElse(updated)); return ResponseEntity.ok(productRepository.findById(updated.getId()).orElse(updated));
}) })
.orElse(ResponseEntity.notFound().build()); .orElse(ResponseEntity.notFound().build());
@@ -127,8 +136,40 @@ public class ProductController {
.map(product -> { .map(product -> {
Integer currentStock = product.getStock() != null ? product.getStock() : 0; Integer currentStock = product.getStock() != null ? product.getStock() : 0;
product.setStock(currentStock > 0 ? 0 : 99); // Toggle between Out of Stock and In Stock product.setStock(currentStock > 0 ? 0 : 99); // Toggle between Out of Stock and In Stock
return ResponseEntity.ok(productRepository.save(product)); Product saved = productRepository.save(product);
stockUpdateController.broadcastStockUpdate(saved.getId(), saved.getStock());
return ResponseEntity.ok(saved);
}) })
.orElse(ResponseEntity.notFound().build()); .orElse(ResponseEntity.notFound().build());
} }
@PatchMapping("/{id}/stock-adjust")
public ResponseEntity<Product> adjustStock(@PathVariable Long id, @RequestParam int delta) {
return productRepository.findById(id)
.map(product -> {
int currentStock = product.getStock() != null ? product.getStock() : 0;
product.setStock(Math.max(0, currentStock + delta));
Product saved = productRepository.save(product);
stockUpdateController.broadcastStockUpdate(saved.getId(), saved.getStock());
return ResponseEntity.ok(saved);
})
.orElse(ResponseEntity.notFound().build());
}
@PatchMapping("/batch-assign-category")
@org.springframework.transaction.annotation.Transactional
public ResponseEntity<Void> batchAssignCategory(@RequestBody java.util.Map<String, Object> payload) {
String categoryName = (String) payload.get("categoryName");
List<Integer> productIds = (List<Integer>) payload.get("productIds");
if (categoryName == null || productIds == null) return ResponseEntity.badRequest().build();
for (Integer id : productIds) {
productRepository.findById(id.longValue()).ifPresent(p -> {
p.setCategory(categoryName);
productRepository.save(p);
});
}
return ResponseEntity.ok().build();
}
} }

View File

@@ -0,0 +1,60 @@
package com.rit.canteen.sales.controller;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
@RestController
@RequestMapping("/api/stock")
public class StockUpdateController {
private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamStockUpdates() {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
this.emitters.add(emitter);
emitter.onCompletion(() -> this.emitters.remove(emitter));
emitter.onTimeout(() -> this.emitters.remove(emitter));
emitter.onError((e) -> this.emitters.remove(emitter));
// Send initial heartbeat to keep connection alive
try {
emitter.send(SseEmitter.event()
.name("init")
.data("Connection Established"));
} catch (IOException e) {
this.emitters.remove(emitter);
}
return emitter;
}
public void broadcastStockUpdate(Long productId, Integer stock) {
List<SseEmitter> deadEmitters = new CopyOnWriteArrayList<>();
Map<String, Object> payload = Map.of(
"productId", productId,
"stock", stock
);
this.emitters.forEach(emitter -> {
try {
emitter.send(SseEmitter.event()
.name("stockUpdate")
.data(payload));
} catch (Exception e) {
deadEmitters.add(emitter);
}
});
this.emitters.removeAll(deadEmitters);
}
}

View File

@@ -20,5 +20,8 @@ public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByCategory(String category); List<Product> findByCategory(String category);
@Query("SELECT DISTINCT p.category FROM Product p WHERE p.category IS NOT NULL")
List<String> findDistinctCategories();
boolean existsByNameAndCategory(String name, String category); boolean existsByNameAndCategory(String name, String category);
} }

View File

@@ -114,6 +114,27 @@ const Products = () => {
useEffect(() => { useEffect(() => {
fetchProducts(); fetchProducts();
// SSE Real-time stock updates
const host = window.location.hostname;
const eventSource = new EventSource(`http://${host}:8080/api/stock/stream`);
eventSource.addEventListener('stockUpdate', (event: any) => {
try {
const update = JSON.parse(event.data);
setProducts(prevProducts =>
prevProducts.map(p =>
p.id === update.productId ? { ...p, stock: update.stock } : p
)
);
} catch (err) {
console.error('Error processing stock update:', err);
}
});
return () => {
eventSource.close();
};
}, [currentPage, pageSize, debouncedSearchTerm]); }, [currentPage, pageSize, debouncedSearchTerm]);
useEffect(() => { useEffect(() => {