Rating added

This commit is contained in:
Sidharth Prabhu
2026-04-17 14:44:00 +05:30
parent ab18786b41
commit 8a41d650a2
17 changed files with 1219 additions and 26 deletions

View File

@@ -0,0 +1,151 @@
package com.rit.canteen.sales.controller;
import com.rit.canteen.sales.model.Feedback;
import com.rit.canteen.sales.model.ItemRating;
import com.rit.canteen.sales.model.Order;
import com.rit.canteen.sales.repository.FeedbackRepository;
import com.rit.canteen.sales.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/feedback")
public class FeedbackController {
@Autowired
private FeedbackRepository feedbackRepository;
@Autowired
private OrderRepository orderRepository;
@GetMapping
public Page<Feedback> getAllFeedback(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return feedbackRepository.findAllByOrderByCreatedAtDesc(PageRequest.of(page, size));
}
@GetMapping("/item-details")
public Page<Map<String, Object>> getItemDetails(
@RequestParam String productName,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Page<ItemRating> ratings = feedbackRepository.findByProductName(productName, PageRequest.of(page, size));
return ratings.map(r -> {
Map<String, Object> map = new HashMap<>();
map.put("rating", r.getRating());
// Priority: Item-specific comment -> parent Feedback comment -> empty
String comment = r.getComment();
if (comment == null || comment.isBlank()) {
if (r.getFeedback() != null) {
comment = r.getFeedback().getComment();
}
}
map.put("comment", comment != null ? comment : "");
map.put("date", r.getFeedback() != null ? r.getFeedback().getCreatedAt() : java.time.LocalDateTime.now());
return map;
});
}
@GetMapping("/stats")
public Map<String, Object> getFeedbackStats() {
Map<String, Object> stats = new HashMap<>();
Double avg = feedbackRepository.getAverageRating();
stats.put("averageRating", avg != null ? avg : 0.0);
List<Object[]> distribution = feedbackRepository.getRatingDistribution();
List<Map<String, Object>> distList = new ArrayList<>();
for (Object[] row : distribution) {
Map<String, Object> item = new HashMap<>();
item.put("rating", row[0]);
item.put("count", row[1]);
distList.add(item);
}
stats.put("distribution", distList);
List<Object[]> topRated = feedbackRepository.getTopRatedItems();
List<Map<String, Object>> topList = new ArrayList<>();
for (Object[] row : topRated) {
Map<String, Object> item = new HashMap<>();
item.put("name", row[0]);
item.put("average", row[1]);
item.put("count", row[2]);
topList.add(item);
}
stats.put("ratedItems", topList);
return stats;
}
@GetMapping("/latest-unrated/{userId}")
public ResponseEntity<Order> getLatestUnratedOrder(@PathVariable Long userId) {
// Look for any order that is either PAID or COMPLETED
Optional<Order> latestOrderOpt = orderRepository.findFirstByUserIdOrderByCreatedAtDesc(userId);
if (latestOrderOpt.isPresent()) {
Order order = latestOrderOpt.get();
String status = order.getStatus().toUpperCase();
if ((status.equals("PAID") || status.equals("COMPLETED")) && !order.isHasFeedback()) {
return ResponseEntity.ok(order);
}
}
return ResponseEntity.noContent().build();
}
@PostMapping("/skip/{orderId}")
@Transactional
public ResponseEntity<Void> skipFeedback(@PathVariable Long orderId) {
Optional<Order> orderOpt = orderRepository.findById(orderId);
if (orderOpt.isPresent()) {
Order order = orderOpt.get();
order.setHasFeedback(true);
orderRepository.save(order);
return ResponseEntity.ok().build();
}
return ResponseEntity.notFound().build();
}
@PostMapping("/submit")
@Transactional
public ResponseEntity<Feedback> submitFeedback(@RequestBody Feedback feedback) {
if (feedback.getOrder() == null || feedback.getOrder().getId() == null) {
return ResponseEntity.badRequest().build();
}
Optional<Order> orderOpt = orderRepository.findById(feedback.getOrder().getId());
if (orderOpt.isEmpty()) {
return ResponseEntity.badRequest().build();
}
Order order = orderOpt.get();
feedback.setOrder(order);
// Link item ratings to feedback and propagate comment
if (feedback.getItemRatings() != null) {
for (ItemRating rating : feedback.getItemRatings()) {
rating.setFeedback(feedback);
// Propagate the general comment to each item for better visibility in item-wise dashboard
if (rating.getComment() == null || rating.getComment().isBlank()) {
rating.setComment(feedback.getComment());
}
}
}
Feedback savedFeedback = feedbackRepository.save(feedback);
// Mark order as rated
order.setHasFeedback(true);
orderRepository.save(order);
return ResponseEntity.ok(savedFeedback);
}
}

View File

@@ -0,0 +1,62 @@
package com.rit.canteen.sales.model;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "canteen_feedbacks")
public class Feedback {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "order_id", nullable = false)
private Order order;
private Long userId;
private String userName;
@OneToMany(mappedBy = "feedback", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ItemRating> itemRatings = new ArrayList<>();
private String comment;
private LocalDateTime createdAt;
public Feedback() {
this.createdAt = LocalDateTime.now();
}
@PrePersist
protected void onCreate() {
if (createdAt == null) createdAt = LocalDateTime.now();
if (itemRatings != null) {
for (ItemRating rating : itemRatings) {
rating.setFeedback(this);
}
}
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Order getOrder() { return order; }
public void setOrder(Order order) { this.order = order; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public List<ItemRating> getItemRatings() { return itemRatings; }
public void setItemRatings(List<ItemRating> itemRatings) { this.itemRatings = itemRatings; }
public String getComment() { return comment; }
public void setComment(String comment) { this.comment = comment; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

View File

@@ -0,0 +1,44 @@
package com.rit.canteen.sales.model;
import jakarta.persistence.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = "canteen_item_ratings")
public class ItemRating {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "feedback_id")
@JsonIgnore
private Feedback feedback;
private Long productId;
private String productName;
private int rating; // 1 to 5
@Column(length = 500)
private String comment;
public ItemRating() {}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Feedback getFeedback() { return feedback; }
public void setFeedback(Feedback feedback) { this.feedback = feedback; }
public Long getProductId() { return productId; }
public void setProductId(Long productId) { this.productId = productId; }
public String getProductName() { return productName; }
public void setProductName(String productName) { this.productName = productName; }
public int getRating() { return rating; }
public void setRating(int rating) { this.rating = rating; }
public String getComment() { return comment; }
public void setComment(String comment) { this.comment = comment; }
}

View File

@@ -47,6 +47,9 @@ public class Order {
@Column(name = "is_archived", nullable = false)
private boolean isArchived = false;
@Column(name = "has_feedback", nullable = false)
private boolean hasFeedback = false;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
@@ -100,6 +103,9 @@ public class Order {
public boolean isArchived() { return isArchived; }
public void setArchived(boolean archived) { isArchived = archived; }
public boolean isHasFeedback() { return hasFeedback; }
public void setHasFeedback(boolean hasFeedback) { this.hasFeedback = hasFeedback; }
public String getOrderType() { return orderType; }
public void setOrderType(String orderType) { this.orderType = orderType; }

View File

@@ -0,0 +1,30 @@
package com.rit.canteen.sales.repository;
import com.rit.canteen.sales.model.Feedback;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface FeedbackRepository extends JpaRepository<Feedback, Long> {
Optional<Feedback> findByOrderId(Long orderId);
boolean existsByOrderId(Long orderId);
Page<Feedback> findAllByOrderByCreatedAtDesc(Pageable pageable);
@Query("SELECT AVG(ir.rating) FROM ItemRating ir")
Double getAverageRating();
@Query("SELECT ir.rating, COUNT(ir) FROM ItemRating ir GROUP BY ir.rating")
List<Object[]> getRatingDistribution();
@Query("SELECT ir.productName, AVG(ir.rating), COUNT(ir) FROM ItemRating ir GROUP BY ir.productName ORDER BY AVG(ir.rating) DESC")
List<Object[]> getTopRatedItems();
@Query("SELECT ir FROM ItemRating ir WHERE TRIM(LOWER(ir.productName)) = TRIM(LOWER(:productName)) ORDER BY ir.feedback.createdAt DESC")
Page<com.rit.canteen.sales.model.ItemRating> findByProductName(@org.springframework.data.repository.query.Param("productName") String productName, Pageable pageable);
}

View File

@@ -13,6 +13,9 @@ import java.util.Optional;
public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> {
List<Order> findByUserIdOrderByCreatedAtDesc(Long userId);
Optional<Order> findFirstByUserIdOrderByCreatedAtDesc(Long userId);
Optional<Order> findFirstByUserIdAndStatusAndHasFeedbackFalseOrderByCreatedAtDesc(Long userId, String status);
Optional<Order> findFirstByUserIdAndStatusOrderByCreatedAtDesc(Long userId, String status);
long countByCreatedAtGreaterThanEqual(LocalDateTime startOfDay);
long countByCreatedAtBetween(LocalDateTime start, LocalDateTime end);
List<Order> findByIsArchivedFalseAndCreatedAtBefore(LocalDateTime timestamp);

View File

@@ -27,11 +27,33 @@ public class DatabaseSeeder implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
repairStallsSchema();
repairOrdersSchema();
repairFeedbackSchema();
repairLobColumns();
seedCategories();
seedProducts();
}
private void repairFeedbackSchema() {
System.out.println("Checking schema consistency for 'canteen_item_ratings' table...");
try {
jdbcTemplate.execute("ALTER TABLE canteen_item_ratings ADD COLUMN IF NOT EXISTS comment VARCHAR(500)");
System.out.println("Feedback schema consistency confirmed.");
} catch (Exception e) {
System.err.println("Feedback schema repair notice: " + e.getMessage());
}
}
private void repairOrdersSchema() {
System.out.println("Checking schema consistency for 'canteen_orders' table...");
try {
jdbcTemplate.execute("ALTER TABLE canteen_orders ADD COLUMN IF NOT EXISTS has_feedback BOOLEAN DEFAULT FALSE");
System.out.println("Orders schema consistency confirmed.");
} catch (Exception e) {
System.err.println("Orders schema repair notice: " + e.getMessage());
}
}
private void repairStallsSchema() {
System.out.println("Checking schema consistency for 'stalls' table...");
try {