| [09e02d7] | 1 | package mk.finki.roomreservation.service;
|
|---|
| 2 |
|
|---|
| 3 | import mk.finki.roomreservation.model.CreateReservationRequest;
|
|---|
| 4 | import mk.finki.roomreservation.model.EquipmentRequest;
|
|---|
| 5 | import mk.finki.roomreservation.model.ReservationDetails;
|
|---|
| 6 | import org.springframework.jdbc.core.JdbcTemplate;
|
|---|
| 7 | import org.springframework.stereotype.Service;
|
|---|
| 8 | import org.springframework.transaction.annotation.Transactional;
|
|---|
| 9 |
|
|---|
| 10 | import java.sql.Date;
|
|---|
| 11 | import java.sql.Time;
|
|---|
| 12 | import java.time.LocalDate;
|
|---|
| 13 | import java.time.LocalTime;
|
|---|
| 14 | import java.util.List;
|
|---|
| 15 | import java.util.Objects;
|
|---|
| 16 |
|
|---|
| 17 | @Service
|
|---|
| 18 | public class ReservationService {
|
|---|
| 19 |
|
|---|
| 20 | private final JdbcTemplate jdbcTemplate;
|
|---|
| 21 |
|
|---|
| 22 | public ReservationService(JdbcTemplate jdbcTemplate) {
|
|---|
| 23 | this.jdbcTemplate = jdbcTemplate;
|
|---|
| 24 | }
|
|---|
| 25 |
|
|---|
| 26 | @Transactional
|
|---|
| 27 | public ReservationDetails createReservation(CreateReservationRequest request) {
|
|---|
| 28 | validateCreateRequest(request);
|
|---|
| 29 |
|
|---|
| 30 | LocalDate date = LocalDate.parse(request.reservationDate());
|
|---|
| 31 | LocalTime start = LocalTime.parse(request.startTime());
|
|---|
| 32 | LocalTime end = LocalTime.parse(request.endTime());
|
|---|
| 33 |
|
|---|
| 34 | List<EquipmentRequest> equipment =
|
|---|
| 35 | request.equipment() == null ? List.of() : request.equipment();
|
|---|
| 36 |
|
|---|
| 37 | String insertReservationSql = """
|
|---|
| 38 | INSERT INTO project.reservations (
|
|---|
| 39 | room_id,
|
|---|
| 40 | user_id,
|
|---|
| 41 | reservation_date,
|
|---|
| 42 | start_time,
|
|---|
| 43 | end_time,
|
|---|
| 44 | status
|
|---|
| 45 | )
|
|---|
| 46 | VALUES (?, ?, ?, ?, ?, 'pending')
|
|---|
| 47 | RETURNING reservation_id
|
|---|
| 48 | """;
|
|---|
| 49 |
|
|---|
| 50 | Integer reservationId = jdbcTemplate.queryForObject(
|
|---|
| 51 | insertReservationSql,
|
|---|
| 52 | Integer.class,
|
|---|
| 53 | request.roomId(),
|
|---|
| 54 | request.userId(),
|
|---|
| 55 | Date.valueOf(date),
|
|---|
| 56 | Time.valueOf(start),
|
|---|
| 57 | Time.valueOf(end)
|
|---|
| 58 | );
|
|---|
| 59 |
|
|---|
| 60 | if (reservationId == null) {
|
|---|
| 61 | throw new RuntimeException("Reservation was not created.");
|
|---|
| 62 | }
|
|---|
| 63 |
|
|---|
| 64 | for (EquipmentRequest item : equipment) {
|
|---|
| 65 | jdbcTemplate.update(
|
|---|
| 66 | """
|
|---|
| 67 | INSERT INTO project.reservation_equipment (
|
|---|
| 68 | reservation_id,
|
|---|
| 69 | equipment_id,
|
|---|
| 70 | requested_quantity
|
|---|
| 71 | )
|
|---|
| 72 | VALUES (?, ?, ?)
|
|---|
| 73 | """,
|
|---|
| 74 | reservationId,
|
|---|
| 75 | item.equipmentId(),
|
|---|
| 76 | item.requestedQuantity()
|
|---|
| 77 | );
|
|---|
| 78 | }
|
|---|
| 79 |
|
|---|
| 80 | return findReservationDetails(reservationId);
|
|---|
| 81 | }
|
|---|
| 82 |
|
|---|
| 83 | public ReservationDetails findReservationDetails(int reservationId) {
|
|---|
| 84 | String sql = """
|
|---|
| 85 | SELECT
|
|---|
| 86 | res.reservation_id,
|
|---|
| 87 | u.full_name AS requester_name,
|
|---|
| 88 | COALESCE(r.room_code, 'No room') AS room_code,
|
|---|
| 89 | res.reservation_date::text AS reservation_date,
|
|---|
| 90 | res.start_time::text AS start_time,
|
|---|
| 91 | res.end_time::text AS end_time,
|
|---|
| 92 | res.status,
|
|---|
| 93 | COALESCE(eq.requested_equipment, 'No equipment') AS equipment
|
|---|
| 94 | FROM project.reservations res
|
|---|
| 95 | JOIN project.users u
|
|---|
| 96 | ON res.user_id = u.user_id
|
|---|
| 97 | LEFT JOIN project.rooms r
|
|---|
| 98 | ON res.room_id = r.room_id
|
|---|
| 99 | LEFT JOIN (
|
|---|
| 100 | SELECT
|
|---|
| 101 | req.reservation_id,
|
|---|
| 102 | string_agg(e.name || ' x' || req.requested_quantity, ', ' ORDER BY e.name) AS requested_equipment
|
|---|
| 103 | FROM project.reservation_equipment req
|
|---|
| 104 | JOIN project.equipment e
|
|---|
| 105 | ON req.equipment_id = e.equipment_id
|
|---|
| 106 | GROUP BY req.reservation_id
|
|---|
| 107 | ) eq
|
|---|
| 108 | ON res.reservation_id = eq.reservation_id
|
|---|
| 109 | WHERE res.reservation_id = ?
|
|---|
| 110 | """;
|
|---|
| 111 |
|
|---|
| 112 | ReservationDetails details = jdbcTemplate.queryForObject(
|
|---|
| 113 | sql,
|
|---|
| 114 | (rs, rowNum) -> new ReservationDetails(
|
|---|
| 115 | rs.getInt("reservation_id"),
|
|---|
| 116 | rs.getString("requester_name"),
|
|---|
| 117 | rs.getString("room_code"),
|
|---|
| 118 | rs.getString("reservation_date"),
|
|---|
| 119 | rs.getString("start_time"),
|
|---|
| 120 | rs.getString("end_time"),
|
|---|
| 121 | rs.getString("status"),
|
|---|
| 122 | rs.getString("equipment")
|
|---|
| 123 | ),
|
|---|
| 124 | reservationId
|
|---|
| 125 | );
|
|---|
| 126 |
|
|---|
| 127 | if (details == null) {
|
|---|
| 128 | throw new RuntimeException("Reservation not found.");
|
|---|
| 129 | }
|
|---|
| 130 |
|
|---|
| 131 | return details;
|
|---|
| 132 | }
|
|---|
| 133 |
|
|---|
| 134 | private void validateCreateRequest(CreateReservationRequest request) {
|
|---|
| 135 | if (request.userId() == null) {
|
|---|
| 136 | throw new IllegalArgumentException("Requester user is required.");
|
|---|
| 137 | }
|
|---|
| 138 |
|
|---|
| 139 | if (request.reservationDate() == null || request.reservationDate().isBlank()) {
|
|---|
| 140 | throw new IllegalArgumentException("Reservation date is required.");
|
|---|
| 141 | }
|
|---|
| 142 |
|
|---|
| 143 | if (request.startTime() == null || request.startTime().isBlank()) {
|
|---|
| 144 | throw new IllegalArgumentException("Start time is required.");
|
|---|
| 145 | }
|
|---|
| 146 |
|
|---|
| 147 | if (request.endTime() == null || request.endTime().isBlank()) {
|
|---|
| 148 | throw new IllegalArgumentException("End time is required.");
|
|---|
| 149 | }
|
|---|
| 150 |
|
|---|
| 151 | LocalTime start = LocalTime.parse(request.startTime());
|
|---|
| 152 | LocalTime end = LocalTime.parse(request.endTime());
|
|---|
| 153 |
|
|---|
| 154 | if (!end.isAfter(start)) {
|
|---|
| 155 | throw new IllegalArgumentException("End time must be after start time.");
|
|---|
| 156 | }
|
|---|
| 157 |
|
|---|
| 158 | boolean hasRoom = request.roomId() != null;
|
|---|
| 159 | boolean hasEquipment = request.equipment() != null && !request.equipment().isEmpty();
|
|---|
| 160 |
|
|---|
| 161 | if (!hasRoom && !hasEquipment) {
|
|---|
| 162 | throw new IllegalArgumentException("Reservation must include at least one resource: room or equipment.");
|
|---|
| 163 | }
|
|---|
| 164 |
|
|---|
| 165 | if (hasEquipment) {
|
|---|
| 166 | for (EquipmentRequest item : request.equipment()) {
|
|---|
| 167 | Objects.requireNonNull(item, "Equipment request cannot be null.");
|
|---|
| 168 |
|
|---|
| 169 | if (item.equipmentId() <= 0) {
|
|---|
| 170 | throw new IllegalArgumentException("Equipment ID must be valid.");
|
|---|
| 171 | }
|
|---|
| 172 |
|
|---|
| 173 | if (item.requestedQuantity() <= 0) {
|
|---|
| 174 | throw new IllegalArgumentException("Requested equipment quantity must be greater than 0.");
|
|---|
| 175 | }
|
|---|
| 176 | }
|
|---|
| 177 | }
|
|---|
| 178 | }
|
|---|
| 179 | } |
|---|