From cec15f2767cfd5b21c354ca090005223e9257731 Mon Sep 17 00:00:00 2001
From: migudel <miguel.moras@estudiantes.uva.es>
Date: Sat, 28 Dec 2024 23:30:13 +0100
Subject: [PATCH] =?UTF-8?q?Estado=20de=20cliente=20administrado=20por=20re?=
 =?UTF-8?q?servas,=20programaci=C3=B3n=20de=20actualizaci=C3=B3n=20peri?=
 =?UTF-8?q?=C3=B3dica?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 java/services/bookings/pom.xml                |  3 +-
 .../uva/api/bookings/BookingsApplication.java |  2 +
 .../com/uva/api/bookings/api/UserApi.java     | 24 +++++-
 .../api/bookings/config/MyScheduledTasks.java | 25 +++++++
 .../controllers/BookingController.java        |  6 +-
 .../models/external/users/ClientDTO.java      | 14 ++++
 .../models/external/users/ClientStatus.java   |  5 ++
 .../repositories/BookingRepository.java       | 11 +++
 .../api/bookings/services/BookingService.java | 74 ++++++++++++++++---
 .../users/controllers/ClientController.java   |  8 +-
 .../java/com/uva/api/users/models/Client.java | 12 +--
 11 files changed, 154 insertions(+), 30 deletions(-)
 create mode 100644 java/services/bookings/src/main/java/com/uva/api/bookings/config/MyScheduledTasks.java
 create mode 100644 java/services/bookings/src/main/java/com/uva/api/bookings/models/external/users/ClientDTO.java
 create mode 100644 java/services/bookings/src/main/java/com/uva/api/bookings/models/external/users/ClientStatus.java

diff --git a/java/services/bookings/pom.xml b/java/services/bookings/pom.xml
index 51c840b..2fa958d 100644
--- a/java/services/bookings/pom.xml
+++ b/java/services/bookings/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>org.springframework.boot</groupId>
 		<artifactId>spring-boot-starter-parent</artifactId>
-		<version>3.3.4</version>
+		<version>3.3.7</version>
 		<relativePath/> <!-- lookup parent from repository -->
 	</parent>
 	<groupId>com.uva</groupId>
@@ -61,7 +61,6 @@
 		<dependency>
 			<groupId>org.projectlombok</groupId>
 			<artifactId>lombok</artifactId>
-			<version>1.18.36</version>
 			<scope>provided</scope>
 		</dependency>
 	</dependencies>
diff --git a/java/services/bookings/src/main/java/com/uva/api/bookings/BookingsApplication.java b/java/services/bookings/src/main/java/com/uva/api/bookings/BookingsApplication.java
index 41ff330..6d21798 100644
--- a/java/services/bookings/src/main/java/com/uva/api/bookings/BookingsApplication.java
+++ b/java/services/bookings/src/main/java/com/uva/api/bookings/BookingsApplication.java
@@ -2,8 +2,10 @@ package com.uva.api.bookings;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 @SpringBootApplication
+@EnableScheduling
 public class BookingsApplication {
 
 	public static void main(String[] args) {
diff --git a/java/services/bookings/src/main/java/com/uva/api/bookings/api/UserApi.java b/java/services/bookings/src/main/java/com/uva/api/bookings/api/UserApi.java
index a17bf87..dc68715 100644
--- a/java/services/bookings/src/main/java/com/uva/api/bookings/api/UserApi.java
+++ b/java/services/bookings/src/main/java/com/uva/api/bookings/api/UserApi.java
@@ -1,5 +1,8 @@
 package com.uva.api.bookings.api;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpStatus;
@@ -8,6 +11,9 @@ import org.springframework.stereotype.Component;
 import org.springframework.web.client.HttpClientErrorException;
 import org.springframework.web.client.RestTemplate;
 
+import com.uva.api.bookings.models.external.users.ClientDTO;
+import com.uva.api.bookings.models.external.users.ClientStatus;
+
 @Component
 public class UserApi {
 
@@ -20,14 +26,14 @@ public class UserApi {
   @Value("${services.external.managers.url}")
   private String MANAGERS_API_URL;
 
-  public boolean existsClientById(int id) {
+  public ClientDTO findClientById(int id) {
     try {
       String url = CLIENTS_API_URL + "/{id}";
-      ResponseEntity<Void> response = restTemplate.getForEntity(url, Void.class, id);
-      return response.getStatusCode() == HttpStatus.OK;
+      ClientDTO client = restTemplate.getForObject(url, ClientDTO.class, id);
+      return client;
     } catch (HttpClientErrorException ex) {
       if (ex.getStatusCode() == HttpStatus.NOT_FOUND) {
-        return false;
+        return null;
       }
       throw ex;
     }
@@ -47,4 +53,14 @@ public class UserApi {
     }
   }
 
+  public void updateClientState(int id, ClientStatus state) {
+    String url = CLIENTS_API_URL + "/{id}";
+
+    Map<String, Object> body = new HashMap<>();
+    System.out.println(state);
+    body.put("status", state);
+
+    restTemplate.put(url, body, id);
+  }
+
 }
diff --git a/java/services/bookings/src/main/java/com/uva/api/bookings/config/MyScheduledTasks.java b/java/services/bookings/src/main/java/com/uva/api/bookings/config/MyScheduledTasks.java
new file mode 100644
index 0000000..d289884
--- /dev/null
+++ b/java/services/bookings/src/main/java/com/uva/api/bookings/config/MyScheduledTasks.java
@@ -0,0 +1,25 @@
+package com.uva.api.bookings.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import com.uva.api.bookings.services.BookingService;
+
+@Component
+public class MyScheduledTasks {
+
+  @Autowired
+  private BookingService bookingService;
+
+  @Scheduled(cron = "0 30 0 * * *") // Se ejecuta cada día a medianoche
+  public void updateInactiveBookings() {
+    System.out.println(
+        "Iniciando proceso de actualizar comunicación de cambio de estado para usuarios cuyas reservas finalizaron el dia de hoy");
+    long start = System.currentTimeMillis();
+    long updatedUsers = bookingService.performDailyClientsStateUpdate();
+    long time = System.currentTimeMillis() - start;
+
+    System.out.println(updatedUsers + " clients updated in " + time + " ml");
+  }
+}
\ No newline at end of file
diff --git a/java/services/bookings/src/main/java/com/uva/api/bookings/controllers/BookingController.java b/java/services/bookings/src/main/java/com/uva/api/bookings/controllers/BookingController.java
index 2a8967a..d0d965a 100644
--- a/java/services/bookings/src/main/java/com/uva/api/bookings/controllers/BookingController.java
+++ b/java/services/bookings/src/main/java/com/uva/api/bookings/controllers/BookingController.java
@@ -8,6 +8,8 @@ import com.uva.api.bookings.models.Booking;
 import com.uva.api.bookings.services.BookingService;
 
 import java.time.LocalDate;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 
 @RestController
 @RequestMapping("/bookings")
@@ -33,7 +35,7 @@ public class BookingController {
         return bookingService.createBooking(booking);
     }
 
-    @GetMapping("/{id}")
+    @GetMapping("/{id:\\d+}")
     public ResponseEntity<?> getBookingById(@PathVariable Integer id) {
         return bookingService.getBookingById(id);
     }
@@ -46,7 +48,7 @@ public class BookingController {
         return bookingService.deleteBookings(hotelId, managerId, userId);
     }
 
-    @DeleteMapping("/{id}")
+    @DeleteMapping("/{id:\\d+}")
     public ResponseEntity<?> deleteBooking(@PathVariable Integer id) {
         return bookingService.deleteBooking(id);
     }
diff --git a/java/services/bookings/src/main/java/com/uva/api/bookings/models/external/users/ClientDTO.java b/java/services/bookings/src/main/java/com/uva/api/bookings/models/external/users/ClientDTO.java
new file mode 100644
index 0000000..4a5ea6f
--- /dev/null
+++ b/java/services/bookings/src/main/java/com/uva/api/bookings/models/external/users/ClientDTO.java
@@ -0,0 +1,14 @@
+package com.uva.api.bookings.models.external.users;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class ClientDTO {
+  private ClientStatus status;
+}
\ No newline at end of file
diff --git a/java/services/bookings/src/main/java/com/uva/api/bookings/models/external/users/ClientStatus.java b/java/services/bookings/src/main/java/com/uva/api/bookings/models/external/users/ClientStatus.java
new file mode 100644
index 0000000..3df8494
--- /dev/null
+++ b/java/services/bookings/src/main/java/com/uva/api/bookings/models/external/users/ClientStatus.java
@@ -0,0 +1,5 @@
+package com.uva.api.bookings.models.external.users;
+
+public enum ClientStatus {
+  NO_BOOKINGS, WITH_ACTIVE_BOOKINGS, WITH_INACTIVE_BOOKINGS;
+}
diff --git a/java/services/bookings/src/main/java/com/uva/api/bookings/repositories/BookingRepository.java b/java/services/bookings/src/main/java/com/uva/api/bookings/repositories/BookingRepository.java
index 111ede8..c55c0d6 100644
--- a/java/services/bookings/src/main/java/com/uva/api/bookings/repositories/BookingRepository.java
+++ b/java/services/bookings/src/main/java/com/uva/api/bookings/repositories/BookingRepository.java
@@ -41,9 +41,20 @@ public interface BookingRepository extends JpaRepository<Booking, Integer> {
 
         List<Booking> findAllByManagerId(int managerId);
 
+        @Query("SELECT EXISTS (Select b from Booking b where b.userId = ?1 AND b.end >= ?2)")
+        boolean existsActiveByUserIdAndDate(int userId, LocalDate date);
+
+        @Query("SELECT EXISTS (Select b from Booking b where b.userId = ?1 AND b.end < ?2)")
+        boolean existsInactiveByUserIdAndDate(int userId, LocalDate date);
+
         @Transactional
         void deleteAllByUserId(int userId);
 
         @Transactional
         void deleteAllByManagerId(int managerId);
+
+        @Query("SELECT b from Booking b where b.end = ?1")
+        List<Booking> findAllPassed(LocalDate yesterday);
+        // List<Booking> findAllByEnd(LocalDate end);// también puede ser
+
 }
diff --git a/java/services/bookings/src/main/java/com/uva/api/bookings/services/BookingService.java b/java/services/bookings/src/main/java/com/uva/api/bookings/services/BookingService.java
index 7a3f8a4..7576034 100644
--- a/java/services/bookings/src/main/java/com/uva/api/bookings/services/BookingService.java
+++ b/java/services/bookings/src/main/java/com/uva/api/bookings/services/BookingService.java
@@ -11,11 +11,14 @@ import com.uva.api.bookings.api.UserApi;
 import com.uva.api.bookings.exceptions.BookingNotFoundException;
 import com.uva.api.bookings.exceptions.InvalidDateRangeException;
 import com.uva.api.bookings.models.Booking;
+import com.uva.api.bookings.models.external.users.ClientDTO;
+import com.uva.api.bookings.models.external.users.ClientStatus;
 import com.uva.api.bookings.repositories.BookingRepository;
 
 import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Stream;
 
 @Service
 public class BookingService {
@@ -101,29 +104,37 @@ public class BookingService {
     public ResponseEntity<Booking> createBooking(Booking booking) {
         if (booking.getId() != null)
             booking.setId(null);
+
+        if (booking.getStart().isAfter(booking.getEnd()))
+            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST,
+                    "La reserva no puede acabar antes de que empiece");
+
         int userId = booking.getUserId();
         int roomId = booking.getRoomId();
         int hotelId = booking.getHotelId();
         int managerId = booking.getManagerId();
 
-        // Check if the customer and rooms exists
+        List<Booking> existingBookings = bookingRepository.findAllByRoomIdInDateRange(roomId, booking.getStart(),
+                booking.getEnd());
+
+        // Local checks first
+        if (!existingBookings.isEmpty())
+            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST,
+                    "Room is not available for the selected dates");
+
         if (!userApi.existsManagerById(managerId))
             throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Manager not found");
 
-        if (!userApi.existsClientById(userId))
-            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "User not found");
-
         if (!hotelApi.existsById(hotelId, roomId))
             throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Hotel or room not found");
 
-        // Check availability
-        List<Booking> existingBookings = bookingRepository.findAllByRoomIdInDateRange(roomId, booking.getStart(),
-                booking.getEnd());
+        ClientDTO client = userApi.findClientById(userId);
 
-        if (!existingBookings.isEmpty()) {
-            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST,
-                    "Room is not available for the selected dates");
-        }
+        if (client == null)
+            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "User not found");
+
+        if (client.getStatus() != ClientStatus.WITH_ACTIVE_BOOKINGS)
+            userApi.updateClientState(userId, ClientStatus.WITH_ACTIVE_BOOKINGS);
 
         booking = bookingRepository.save(booking);
 
@@ -141,9 +152,36 @@ public class BookingService {
         return ResponseEntity.ok(booking);
     }
 
+    private ClientStatus calculateClientStatus(int id) {
+        return calculateClientStatus(id, null);
+    }
+
+    private ClientStatus calculateClientStatus(int id, LocalDate date) {
+
+        date = date != null ? date : LocalDate.now();
+
+        boolean hasActiveBookings = bookingRepository.existsActiveByUserIdAndDate(id, date);
+        boolean hasInactiveBookings = bookingRepository.existsInactiveByUserIdAndDate(id, date);
+
+        ClientStatus status;
+        if (hasActiveBookings) {
+            status = ClientStatus.WITH_ACTIVE_BOOKINGS;
+        } else if (hasInactiveBookings) {
+            status = ClientStatus.WITH_INACTIVE_BOOKINGS;
+        } else {
+            status = ClientStatus.NO_BOOKINGS;
+        }
+        return status;
+    }
+
     public ResponseEntity<?> deleteBooking(Integer id) {
         Booking booking = findById(id);
         bookingRepository.deleteById(id);
+
+        ClientStatus status = calculateClientStatus(id);
+        // In this case, the check if the client has already de state is expensive
+        userApi.updateClientState(id, status);
+
         return ResponseEntity.ok(booking);
     }
 
@@ -196,4 +234,18 @@ public class BookingService {
         }
         return ResponseEntity.ok(bookings);
     }
+
+    public long performDailyClientsStateUpdate() {
+        LocalDate yesterday = LocalDate.now().minusDays(1);
+
+        List<Booking> passedBookings = bookingRepository.findAllPassed(yesterday);
+
+        Stream<Integer> userIds = passedBookings.stream().map(b -> b.getUserId()).distinct();
+        userIds.forEach(userId -> {
+            ClientStatus status = calculateClientStatus(userId);
+            userApi.updateClientState(userId, status);
+        });
+
+        return userIds.count();
+    }
 }
diff --git a/java/services/users/src/main/java/com/uva/api/users/controllers/ClientController.java b/java/services/users/src/main/java/com/uva/api/users/controllers/ClientController.java
index 8342e85..b9ee383 100644
--- a/java/services/users/src/main/java/com/uva/api/users/controllers/ClientController.java
+++ b/java/services/users/src/main/java/com/uva/api/users/controllers/ClientController.java
@@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PatchMapping;
 import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestHeader;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -42,8 +43,13 @@ public class ClientController {
   }
 
   @PatchMapping("/{id:\\d+}")
-  public ResponseEntity<?> updateClientState(@PathVariable int id, @RequestBody Map<String, String> json) {
+  public ResponseEntity<?> updateClientStateWrapper(@PathVariable int id, @RequestBody Map<String, String> json) {
+    return updateClientState(id, json);
+  }
 
+  @PutMapping("/{id:\\d+}")
+  public ResponseEntity<?> updateClientState(@PathVariable int id, @RequestBody Map<String, String> json) {
+    json.entrySet().forEach(t -> System.out.println(t));
     String strStatus = json.get("status");
     if (strStatus == null)
       throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Missing required fields");
diff --git a/java/services/users/src/main/java/com/uva/api/users/models/Client.java b/java/services/users/src/main/java/com/uva/api/users/models/Client.java
index e28cca3..12ab1ed 100644
--- a/java/services/users/src/main/java/com/uva/api/users/models/Client.java
+++ b/java/services/users/src/main/java/com/uva/api/users/models/Client.java
@@ -1,5 +1,7 @@
 package com.uva.api.users.models;
 
+import java.time.LocalDate;
+
 import jakarta.persistence.Column;
 import jakarta.persistence.Entity;
 import jakarta.persistence.EnumType;
@@ -30,14 +32,4 @@ public class Client extends User {
     super(id, name, email, password, UserRol.CLIENT);
     setStatus(status);
   }
-
-  // public ClientStatus getStatus() {
-  // if (getBookings() == null || getBookings().isEmpty())
-  // return ClientStatus.NO_BOOKINGS;
-  // boolean activeBookings = getBookings().stream()
-  // .anyMatch(booking -> !booking.getEndDate().isBefore(LocalDate.now())); //
-  // reserva >= ahora
-  // return activeBookings ? ClientStatus.WITH_ACTIVE_BOOKINGS :
-  // ClientStatus.WITH_INACTIVE_BOOKINGS;
-  // }
 }
-- 
GitLab