backend updated for counter login
This commit is contained in:
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user