Rating added
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user