|
|
|
@ -0,0 +1,273 @@ |
|
|
|
package com.example.VictoriaPublicHall.service; |
|
|
|
|
|
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
|
import org.springframework.dao.DataAccessException; |
|
|
|
import org.springframework.http.HttpStatus; |
|
|
|
import org.springframework.jdbc.core.BatchPreparedStatementSetter; |
|
|
|
import org.springframework.jdbc.core.JdbcTemplate; |
|
|
|
import org.springframework.jdbc.support.GeneratedKeyHolder; |
|
|
|
import org.springframework.jdbc.support.KeyHolder; |
|
|
|
import org.springframework.stereotype.Service; |
|
|
|
import org.springframework.transaction.annotation.Transactional; |
|
|
|
import org.springframework.web.server.ResponseStatusException; |
|
|
|
|
|
|
|
import java.sql.PreparedStatement; |
|
|
|
import java.sql.Statement; |
|
|
|
import java.time.LocalDate; |
|
|
|
import java.util.*; |
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
@Service |
|
|
|
public class BookingService { |
|
|
|
|
|
|
|
private final JdbcTemplate jdbc; |
|
|
|
|
|
|
|
public BookingService(JdbcTemplate jdbc) { |
|
|
|
this.jdbc = jdbc; |
|
|
|
} |
|
|
|
|
|
|
|
@Value("${victoriahall.slot.capacity}") |
|
|
|
private int slotCapacity; |
|
|
|
|
|
|
|
|
|
|
|
@Transactional |
|
|
|
public Map<String, Object> create(Map<String, Object> body) { |
|
|
|
String name = getStr(body, "name", true); |
|
|
|
String mobileno = getStr(body, "mobileno", false); |
|
|
|
String address = getStr(body, "address", false); |
|
|
|
String aadhaarNo = getStr(body, "aadhaarNo", false); |
|
|
|
Integer age = getInt(body, "age", false); |
|
|
|
|
|
|
|
String bookingType = getStr(body, "bookingType", true); // "individual"/"group" |
|
|
|
Integer persons = getInt(body, "noofpersons", true); |
|
|
|
LocalDate bookDate = parseDate(getStr(body, "bookingDate", true)); |
|
|
|
Long slotId = getLong(body, "slotId", true); |
|
|
|
|
|
|
|
List<Integer> seats = getIntList(body, "selectedSlots", true); |
|
|
|
|
|
|
|
// Check number of seats matches persons |
|
|
|
if (!Objects.equals(persons, seats.size())) { |
|
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "noofpersons must equal selectedSlots size"); |
|
|
|
} |
|
|
|
|
|
|
|
// seat limits |
|
|
|
if ("individual".equalsIgnoreCase(bookingType) && persons > 10) { |
|
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Individual bookings cannot exceed 10 seats"); |
|
|
|
} |
|
|
|
if ("group".equalsIgnoreCase(bookingType) && persons > 50) { |
|
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Group bookings cannot exceed 50 seats"); |
|
|
|
} |
|
|
|
|
|
|
|
// Conflict check |
|
|
|
if (countConflicts(bookDate, slotId, seats) > 0) { |
|
|
|
throw new ResponseStatusException(HttpStatus.CONFLICT, "Some seats already booked for this date/slot"); |
|
|
|
} |
|
|
|
|
|
|
|
String refId = UUID.randomUUID().toString().replace("-", ""); |
|
|
|
Long groupId = resolveGroupIdForSeat(seats.get(0)); |
|
|
|
|
|
|
|
Long bookingId = insertBooking( |
|
|
|
name, mobileno, address, aadhaarNo, age, |
|
|
|
bookingType, persons, bookDate, slotId, groupId, refId |
|
|
|
); |
|
|
|
|
|
|
|
insertBookingSeats(bookingId, slotId, seats); |
|
|
|
|
|
|
|
Map<String, Object> resp = getSummary(refId); |
|
|
|
resp.put("bookingId", bookingId); |
|
|
|
resp.put("refId", refId); |
|
|
|
return resp; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Long insertBooking(String name, String mobileno, String address, String aadhaarNo, |
|
|
|
Integer age, String bookingType, Integer persons, |
|
|
|
LocalDate bookingDate, Long slotId, Long groupId, String refId) { |
|
|
|
String sql = " INSERT INTO registration_details " + |
|
|
|
"(name, mobileno, address, aadhaar_no, age, booking_type, " + |
|
|
|
"noofpersons, booking_date, slot_id, group_id, ref_id, is_active, is_delete) " + |
|
|
|
"VALUES (?,?,?,?,?, ?,?,?,?,?, ?,1,0) "; |
|
|
|
|
|
|
|
KeyHolder kh = new GeneratedKeyHolder(); |
|
|
|
jdbc.update(conn -> { |
|
|
|
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); |
|
|
|
int i = 1; |
|
|
|
ps.setString(i++, name); |
|
|
|
ps.setString(i++, mobileno); |
|
|
|
ps.setString(i++, address); |
|
|
|
ps.setString(i++, aadhaarNo); |
|
|
|
if (age == null) ps.setObject(i++, null); else ps.setInt(i++, age); |
|
|
|
ps.setString(i++, bookingType); |
|
|
|
ps.setInt(i++, persons); |
|
|
|
ps.setObject(i++, bookingDate); |
|
|
|
ps.setLong(i++, slotId); |
|
|
|
ps.setLong(i++, groupId); |
|
|
|
ps.setString(i++, refId); |
|
|
|
return ps; |
|
|
|
}, kh); |
|
|
|
|
|
|
|
Number key = kh.getKey(); |
|
|
|
if (key == null) throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to get booking id"); |
|
|
|
return key.longValue(); |
|
|
|
} |
|
|
|
|
|
|
|
private void insertBookingSeats(Long bookingId, Long slotId, List<Integer> seats) { |
|
|
|
String sql = "INSERT INTO booking_slots (booking_id, seat_no, group_id, slot_id) VALUES (?, ?, ?, ?)"; |
|
|
|
|
|
|
|
jdbc.batchUpdate(sql, new BatchPreparedStatementSetter() { |
|
|
|
@Override |
|
|
|
public void setValues(PreparedStatement ps, int i) throws java.sql.SQLException { |
|
|
|
int seatNo = seats.get(i); |
|
|
|
ps.setLong(1, bookingId); |
|
|
|
ps.setInt(2, seatNo); |
|
|
|
ps.setLong(3, resolveGroupIdForSeat(seatNo)); |
|
|
|
ps.setLong(4, slotId); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public int getBatchSize() { |
|
|
|
return seats.size(); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private long resolveGroupIdForSeat(int seatNo) { |
|
|
|
int groupNumber = ((seatNo - 1) / 10) + 1; |
|
|
|
try { |
|
|
|
return jdbc.queryForObject( |
|
|
|
"SELECT id FROM group_master WHERE group_name = ? AND is_active=1 AND is_delete=0 LIMIT 1", |
|
|
|
Long.class, "Group " + groupNumber |
|
|
|
); |
|
|
|
} catch (DataAccessException e) { |
|
|
|
return groupNumber; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private int countConflicts(LocalDate bookingDate, Long slotId, List<Integer> seats) { |
|
|
|
String condition = seats.stream() |
|
|
|
.map(s -> "FIND_IN_SET(?, bs.seat_no) > 0") |
|
|
|
.collect(Collectors.joining(" OR ")); |
|
|
|
|
|
|
|
String sql = "SELECT COUNT(*) FROM booking_slots bs JOIN registration_details r " + |
|
|
|
"ON r.id = bs.booking_id WHERE r.booking_date = ? AND r.slot_id = ? AND r.is_delete = 0 " + |
|
|
|
"AND (%s)" .formatted(condition); |
|
|
|
|
|
|
|
List<Object> params = new ArrayList<>(); |
|
|
|
params.add(bookingDate); |
|
|
|
params.add(slotId); |
|
|
|
params.addAll(seats); |
|
|
|
|
|
|
|
Integer cnt = jdbc.queryForObject(sql, params.toArray(), Integer.class); |
|
|
|
return cnt == null ? 0 : cnt; |
|
|
|
} |
|
|
|
|
|
|
|
private String getStr(Map<String, Object> m, String key, boolean required) { |
|
|
|
Object v = m.get(key); |
|
|
|
if (v == null) { |
|
|
|
if (required) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Missing: " + key); |
|
|
|
return null; |
|
|
|
} |
|
|
|
String s = String.valueOf(v).trim(); |
|
|
|
if (required && s.isEmpty()) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Empty: " + key); |
|
|
|
return s; |
|
|
|
} |
|
|
|
|
|
|
|
private Integer getInt(Map<String, Object> m, String key, boolean required) { |
|
|
|
Object v = m.get(key); |
|
|
|
if (v == null) { |
|
|
|
if (required) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Missing: " + key); |
|
|
|
return null; |
|
|
|
} |
|
|
|
if (v instanceof Number n) return n.intValue(); |
|
|
|
try { return Integer.parseInt(String.valueOf(v)); } |
|
|
|
catch (Exception e) { |
|
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid int: " + key); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private Long getLong(Map<String, Object> m, String key, boolean required) { |
|
|
|
Object v = m.get(key); |
|
|
|
if (v == null) { |
|
|
|
if (required) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Missing: " + key); |
|
|
|
return null; |
|
|
|
} |
|
|
|
if (v instanceof Number n) return n.longValue(); |
|
|
|
try { return Long.parseLong(String.valueOf(v)); } |
|
|
|
catch (Exception e) { |
|
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid long: " + key); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
private List<Integer> getIntList(Map<String, Object> m, String key, boolean required) { |
|
|
|
Object v = m.get(key); |
|
|
|
if (v == null) { |
|
|
|
if (required) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Missing: " + key); |
|
|
|
return List.of(); |
|
|
|
} |
|
|
|
if (v instanceof List<?> list) { |
|
|
|
try { |
|
|
|
List<Integer> out = new ArrayList<>(list.size()); |
|
|
|
for (Object o : list) { |
|
|
|
if (o instanceof Number n) out.add(n.intValue()); |
|
|
|
else out.add(Integer.parseInt(String.valueOf(o))); |
|
|
|
} |
|
|
|
return out; |
|
|
|
} catch (Exception e) { |
|
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid int list: " + key); |
|
|
|
} |
|
|
|
} |
|
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Expected array for: " + key); |
|
|
|
} |
|
|
|
|
|
|
|
private LocalDate parseDate(String s) { |
|
|
|
try { return LocalDate.parse(s); } |
|
|
|
catch (Exception e) { |
|
|
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid date (yyyy-MM-dd) for bookingDate"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public Map<String, Object> getSummary(String refId) { |
|
|
|
String sql = " SELECT r.id AS bookingId, r.ref_id, r.name, r.mobileno, r.address, " + |
|
|
|
"r.booking_type, r.noofpersons, r.booking_date, sm.slot_timing, gm.group_name, " + |
|
|
|
"GROUP_CONCAT(bs.seat_no ORDER BY bs.seat_no) AS seats FROM registration_details r " + |
|
|
|
"JOIN slots_master sm ON sm.id = r.slot_id " + |
|
|
|
"JOIN group_master gm ON gm.id = r.group_id " + |
|
|
|
"JOIN booking_slots bs ON bs.booking_id = r.id " + |
|
|
|
"WHERE r.ref_id = ? AND r.is_delete = 0 " + |
|
|
|
"GROUP BY r.id, r.ref_id, r.name, r.mobileno, r.address, " + |
|
|
|
"r.booking_type, r.noofpersons, r.booking_date, sm.slot_timing, gm.group_name "; |
|
|
|
|
|
|
|
return jdbc.queryForMap(sql, refId); |
|
|
|
} |
|
|
|
|
|
|
|
public Map<String, Object> getOccupiedSeats(String bookingDate, Long slotId) { |
|
|
|
String sql = " SELECT bs.seat_no FROM booking_slots bs " + |
|
|
|
"JOIN registration_details r ON r.id = bs.booking_id WHERE r.booking_date = ? " + |
|
|
|
"AND r.slot_id = ? AND r.is_delete = 0 "; |
|
|
|
|
|
|
|
List<String> seatStrings = jdbc.queryForList( |
|
|
|
sql, |
|
|
|
new Object[]{LocalDate.parse(bookingDate), slotId}, |
|
|
|
String.class |
|
|
|
); |
|
|
|
|
|
|
|
List<Integer> occupiedSeats = seatStrings.stream() |
|
|
|
.flatMap(s -> Arrays.stream(s.split(","))) |
|
|
|
.map(Integer::parseInt) |
|
|
|
.toList(); |
|
|
|
|
|
|
|
int availableCount = slotCapacity - occupiedSeats.size(); |
|
|
|
|
|
|
|
Map<String, Object> result = new HashMap<>(); |
|
|
|
result.put("occupiedSeats", occupiedSeats); |
|
|
|
result.put("availableCount", availableCount); |
|
|
|
result.put("capacity", slotCapacity); |
|
|
|
|
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|