From b91aee47922f603e93305a4c8f0d87fc00b264e5 Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Sat, 25 Jul 2026 14:53:28 +0300 Subject: [PATCH 01/28] feat: add findActiveContractByWorker for per-worker active contract lookup (#55) Provide a dedicated query and service method so banner, remaining-days, and daily-execution guards can share one definition of a worker's active contract. --- .../school/hei/asa/repository/ContractRepository.java | 9 +++++++++ .../asa/repository/jrepository/JContractRepository.java | 4 ++++ .../java/school/hei/asa/service/ContractService.java | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/src/main/java/school/hei/asa/repository/ContractRepository.java b/src/main/java/school/hei/asa/repository/ContractRepository.java index af5aa4bf..0009ed1d 100644 --- a/src/main/java/school/hei/asa/repository/ContractRepository.java +++ b/src/main/java/school/hei/asa/repository/ContractRepository.java @@ -2,6 +2,7 @@ import jakarta.transaction.Transactional; import java.util.List; +import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.stereotype.Repository; import school.hei.asa.model.Worker; @@ -25,6 +26,14 @@ public List findAllByWorker(Worker worker) { workerMapper.toEntity(worker))); } + @Transactional + public Optional findActiveContractByWorker(Worker worker) { + return jContractRepository + .findFirstByWorkerAndDurationInDaysIsNotNullOrderByEntranceInstantDesc( + workerMapper.toEntity(worker)) + .map(jContract -> contractMapper.toDomain(List.of(jContract)).getFirst()); + } + public List findAll() { return contractMapper.toDomain(jContractRepository.findAll()); } diff --git a/src/main/java/school/hei/asa/repository/jrepository/JContractRepository.java b/src/main/java/school/hei/asa/repository/jrepository/JContractRepository.java index bdcf3993..7dd4633a 100644 --- a/src/main/java/school/hei/asa/repository/jrepository/JContractRepository.java +++ b/src/main/java/school/hei/asa/repository/jrepository/JContractRepository.java @@ -1,6 +1,7 @@ package school.hei.asa.repository.jrepository; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @@ -25,4 +26,7 @@ public interface JContractRepository extends JpaRepository { @Query("SELECT c FROM JContract c WHERE c.endInstant IS NULL AND c.durationInDays != 0") List findActiveContracts(); + + Optional findFirstByWorkerAndDurationInDaysIsNotNullOrderByEntranceInstantDesc( + JWorker jWorker); } diff --git a/src/main/java/school/hei/asa/service/ContractService.java b/src/main/java/school/hei/asa/service/ContractService.java index 93b83666..3052f2a5 100644 --- a/src/main/java/school/hei/asa/service/ContractService.java +++ b/src/main/java/school/hei/asa/service/ContractService.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -49,6 +50,10 @@ public List getAllContractsByWorker(Worker worker) { return contractRepository.findAllByWorker(worker); } + public Optional findActiveContractByWorker(Worker worker) { + return contractRepository.findActiveContractByWorker(worker); + } + public String getActualWorkedDaysByDateByWorker( LocalDate startDate, String workerCode, LocalDate endDate) { var dailyExecutions = From 269dc04311814db9f55e1d9d1e76e917b386e03f Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Sat, 25 Jul 2026 20:27:04 +0300 Subject: [PATCH 02/28] feat: add getRemainingDaysOnActiveContractOrZero on ContractService (#56) Compute remaining contract days from the active contract and worked days so banner and alert flows can share one source of truth. --- .../hei/asa/service/ContractService.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/school/hei/asa/service/ContractService.java b/src/main/java/school/hei/asa/service/ContractService.java index 3052f2a5..fa463e6c 100644 --- a/src/main/java/school/hei/asa/service/ContractService.java +++ b/src/main/java/school/hei/asa/service/ContractService.java @@ -1,5 +1,6 @@ package school.hei.asa.service; +import static java.time.ZoneId.systemDefault; import static java.util.Locale.FRENCH; import static school.hei.asa.model.DailyExecution.Type.fullCare; import static school.hei.asa.model.DailyExecution.Type.fullWork; @@ -54,6 +55,23 @@ public Optional findActiveContractByWorker(Worker worker) { return contractRepository.findActiveContractByWorker(worker); } + public double getRemainingDaysOnActiveContractOrZero(Worker worker) { + var activeContractOpt = findActiveContractByWorker(worker); + if (activeContractOpt.isEmpty()) { + return 0d; + } + + var contract = activeContractOpt.get(); + var startDate = contract.entranceInstant().atZone(systemDefault()).toLocalDate(); + var endDate = + contract.endInstant() == null + ? LocalDate.now() + : contract.endInstant().atZone(systemDefault()).toLocalDate(); + var actualWorkedDays = getActualWorkedDaysByDateByWorker(startDate, worker.code(), endDate); + var workedDays = actualWorkedDays.equals("-") ? 0d : Double.parseDouble(actualWorkedDays); + return contract.duration().toDays() - workedDays; + } + public String getActualWorkedDaysByDateByWorker( LocalDate startDate, String workerCode, LocalDate endDate) { var dailyExecutions = From 440c92a09d672f85cc5ec1a7899be4888c235c7a Mon Sep 17 00:00:00 2001 From: haja171106 Date: Sat, 25 Jul 2026 21:35:29 +0300 Subject: [PATCH 03/28] Feat/low remaining days alert event (#57) * feat(event): add LowRemainingDaysAlertRequested event model carrying workerCode and remainingDays * feat(service): add LowRemainingDaysAlertRequestedService to send async alert email to accountants when a worker has few days remaining on their contract * chore: format code --- .../model/LowRemainingDaysAlertRequested.java | 31 ++++++++ ...LowRemainingDaysAlertRequestedService.java | 77 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/main/java/school/hei/asa/endpoint/event/model/LowRemainingDaysAlertRequested.java create mode 100644 src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java diff --git a/src/main/java/school/hei/asa/endpoint/event/model/LowRemainingDaysAlertRequested.java b/src/main/java/school/hei/asa/endpoint/event/model/LowRemainingDaysAlertRequested.java new file mode 100644 index 00000000..66a2bb98 --- /dev/null +++ b/src/main/java/school/hei/asa/endpoint/event/model/LowRemainingDaysAlertRequested.java @@ -0,0 +1,31 @@ +package school.hei.asa.endpoint.event.model; + +import java.time.Duration; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +@Data +@EqualsAndHashCode(callSuper = false) +@ToString +public class LowRemainingDaysAlertRequested extends PojaEvent { + + private String workerCode; + private int remainingDays; + + @Override + public Duration maxConsumerDuration() { + return Duration.ofSeconds(45); + } + + @Override + public Duration maxConsumerBackoffBetweenRetries() { + return Duration.ofSeconds(30); + } +} diff --git a/src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java b/src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java new file mode 100644 index 00000000..4d5de068 --- /dev/null +++ b/src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java @@ -0,0 +1,77 @@ +package school.hei.asa.service.event; + +import static org.reflections.Reflections.log; + +import jakarta.mail.internet.InternetAddress; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import school.hei.asa.endpoint.event.model.LowRemainingDaysAlertRequested; +import school.hei.asa.mail.Email; +import school.hei.asa.mail.Mailer; +import school.hei.asa.service.mapper.InternetAddressMapper; + +@Service +public class LowRemainingDaysAlertRequestedService + implements Consumer { + + private final Mailer mailer; + private final String accountants; + private final InternetAddressMapper internetAddressMapper; + + public LowRemainingDaysAlertRequestedService( + Mailer mailer, + @Value("${ACCOUNTANTS}") String accountants, + InternetAddressMapper internetAddressMapper) { + this.mailer = mailer; + this.accountants = accountants; + this.internetAddressMapper = internetAddressMapper; + } + + @Override + public void accept(LowRemainingDaysAlertRequested event) { + var accountantAddresses = + internetAddressMapper.toInternetAddresses( + Arrays.stream(this.accountants.split(",")).toList()); + + sendAlertToAccountants(accountantAddresses, event.getWorkerCode(), event.getRemainingDays()); + } + + private void sendAlertToAccountants( + List accountantAddresses, String workerCode, int remainingDays) { + if (accountantAddresses.isEmpty()) { + log.info("No accountant address found. Skipping alert email."); + return; + } + + var subject = String.format("ASA - ALERT: low remaining days - Worker %s", workerCode); + var body = + String.format( + "Hello,\n\n" + + "Worker %s has only %d day(s) remaining on their contract.\n\n" + + "Please take the necessary action.\n\n" + + "Best regards,\n" + + "ASA", + workerCode, remainingDays); + + log.info( + "Sending low remaining days alert to accountants for worker={}, remainingDays={}", + workerCode, + remainingDays); + + try { + mailer.accept( + new Email( + accountantAddresses.get(0), + accountantAddresses.subList(1, accountantAddresses.size()), + List.of(), + subject, + body, + List.of())); + } catch (Exception e) { + throw new RuntimeException("Failed to send low remaining days alert email", e); + } + } +} From af5a9e964378531f6f89fc8e3ff8d9bb5ad6af17 Mon Sep 17 00:00:00 2001 From: Tom-1747 Date: Sun, 26 Jul 2026 01:16:48 +0300 Subject: [PATCH 04/28] Feature/wave1 active contract and calendar UI (#59) * fix: auto-initialize spring session jdbc schema on startup * feat: add contract and remaining days attributes to calendar controller * feat: display contract and remaining days state on calendar * feat: add findActiveContractByWorker for per-worker active contract lookup (#55) Provide a dedicated query and service method so banner, remaining-days, and daily-execution guards can share one definition of a worker's active contract. * feat: add getRemainingDaysOnActiveContractOrZero on ContractService (#56) Compute remaining contract days from the active contract and worked days so banner and alert flows can share one source of truth. * fix: drop LowRemainingDaysAlertService from calendar until it lands on base Keep remaining-days UI attributes via ContractService; the alert service is not on low-remaining-days-alert yet. --------- Co-authored-by: yoandiny <63061998+yoandiny@users.noreply.github.com> Co-authored-by: yoandiny --- .../rest/controller/CalendarController.java | 9 + src/main/resources/application.properties | 1 + src/main/resources/templates/calendar.html | 207 ++++++++++-------- 3 files changed, 125 insertions(+), 92 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java index dab40ed8..72949697 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java @@ -27,6 +27,7 @@ import school.hei.asa.model.Mission; import school.hei.asa.model.Worker; import school.hei.asa.service.CalendarService; +import school.hei.asa.service.ContractService; @AllArgsConstructor @Controller @@ -35,6 +36,7 @@ public class CalendarController { private final CalendarService calendarService; private final WorkerFromAuthentication workerFromAuthentication; private final WorkerToModelAdder workerToModelAdder; + private final ContractService contractService; @GetMapping("/work-and-care-calendar") public String getCalendar( @@ -67,6 +69,13 @@ public String getCalendar( }); var lateReportedDaysByMonth = calendarService.lateReportedDaysByMonth(worker, year); + var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); + var hasUsableContract = + contractService.findActiveContractByWorker(worker).isPresent() && remainingDays > 0; + + model.addAttribute("remainingDays", hasUsableContract ? remainingDays : null); + model.addAttribute("hasUsableContract", hasUsableContract); + model.addAttribute("workerCode", workerCodeOrAuth); model.addAttribute("currentYear", now().getYear()); model.addAttribute( diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6fc771ce..452d0b8e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,6 @@ spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true +spring.session.jdbc.initialize-schema=always logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE sentry.dsn=https://175df70cc551efc22dd25f4cc4c17ef7@o4508285839605760.ingest.de.sentry.io/4508691883622480 diff --git a/src/main/resources/templates/calendar.html b/src/main/resources/templates/calendar.html index 7ab958b6..d9b87253 100644 --- a/src/main/resources/templates/calendar.html +++ b/src/main/resources/templates/calendar.html @@ -10,13 +10,36 @@
+
+ +
+ Please note : You do not have an active contract. Please contact your administrator. +
+ +
+ Please note : You have only days on your contract ! +
-

-

+
+

+

+
+ + +
+ Please note : You have only days left on your contract ! +
+
+
@@ -35,113 +58,113 @@

Color description

-
-
-

- - - - - - - - - - - - - - - - - - - - - -
WeekMonTueWedThuFriSatSun
-
- -
-
- - Work Days: - -
-
- - Paid Care Days: - -
-
- - Unpaid Care Days: - -
-
- - Late Reported Days: +
+
+

+ + + + + + + + + + + + + + + + + + + + + +
WeekMonTueWedThuFriSatSun
+
+ +
+
+ + Work Days: + +
+
+ + Paid Care Days: + +
+
+ + Unpaid Care Days: + +
+
+ + Late Reported Days:
Jours pointés 3 jours plus tard ou plus
- -
+
- - +
- -
+ }); + + function updateFilters() { + const workerCode = document.getElementById("worker").value; + const year = document.getElementById("year").value; + window.location.href = `/work-and-care-calendar?workerCode=${workerCode}&year=${year}`; + } + +
\ No newline at end of file From dde2f5a392d06504a9a36e0181dfaaf707818f7d Mon Sep 17 00:00:00 2001 From: nelio-gio Date: Sun, 26 Jul 2026 02:32:36 +0300 Subject: [PATCH 05/28] feat: add LowRemainingDaysAlertService for low contract days alert - Create LowRemainingDaysAlertService with threshold check + event trigger + toast message - Integrate into DailyExecutionController after daily execution save - Fix locale bug in ContractService.executedDays (use Locale.US) - Add LOW_REMAINING_DAYS_THRESHOLD env var to test config - Add integration tests: below threshold, above threshold, no contract --- .../controller/DailyExecutionController.java | 18 ++++ .../hei/asa/service/ContractService.java | 3 +- .../service/LowRemainingDaysAlertService.java | 55 ++++++++++++ .../java/school/hei/asa/conf/EnvConf.java | 1 + .../LowRemainingDaysAlertServiceIT.java | 86 +++++++++++++++++++ 5 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java create mode 100644 src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index f4bd51c9..76beb444 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -6,11 +6,14 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import school.hei.asa.endpoint.rest.controller.mapper.ThDailyExecutionFormMapper; import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; import school.hei.asa.endpoint.rest.security.WorkerFromAuthentication; import school.hei.asa.endpoint.rest.service.ThMissionService; import school.hei.asa.repository.DailyExecutionRepository; +import school.hei.asa.service.LowRemainingDaysAlertService; @Controller @AllArgsConstructor @@ -19,6 +22,7 @@ public class DailyExecutionController { private final DailyExecutionRepository dailyExecutionRepository; private final WorkerFromAuthentication workerFromAuthentication; private final ThMissionService thMissionService; + private final LowRemainingDaysAlertService lowRemainingDaysAlertService; @GetMapping("/daily-execution") public String getDailyExecutionForm(Model model) { @@ -29,10 +33,24 @@ public String getDailyExecutionForm(Model model) { @PostMapping("/daily-execution") public String createDailyExecution(Authentication authentication, ThDailyExecutionForm dmeForm) { + return createDailyExecution(authentication, dmeForm, new RedirectAttributesModelMap()); + } + + public String createDailyExecution( + Authentication authentication, ThDailyExecutionForm dmeForm, RedirectAttributes redirectAttributes) { var worker = workerFromAuthentication.apply(authentication).get(); var dailyExecution = thDailyExecutionFormMapper.toDomain(dmeForm, worker); dailyExecutionRepository.save(dailyExecution); + + lowRemainingDaysAlertService + .checkRemainingDaysAndBuildAlertMessage(worker) + .ifPresent( + message -> { + redirectAttributes.addFlashAttribute("toastMessage", message); + redirectAttributes.addFlashAttribute("toastType", "warning"); + }); + return "redirect:/work-and-care-calendar"; } } diff --git a/src/main/java/school/hei/asa/service/ContractService.java b/src/main/java/school/hei/asa/service/ContractService.java index fa463e6c..6915e747 100644 --- a/src/main/java/school/hei/asa/service/ContractService.java +++ b/src/main/java/school/hei/asa/service/ContractService.java @@ -2,6 +2,7 @@ import static java.time.ZoneId.systemDefault; import static java.util.Locale.FRENCH; +import static java.util.Locale.US; import static school.hei.asa.model.DailyExecution.Type.fullCare; import static school.hei.asa.model.DailyExecution.Type.fullWork; @@ -103,7 +104,7 @@ private String executedDays(List executions) { }) .reduce(Double::sum) .get(); - return String.format("%.1f", result); + return String.format(US, "%.1f", result); } public List findActiveContracts() { diff --git a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java new file mode 100644 index 00000000..1030e645 --- /dev/null +++ b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java @@ -0,0 +1,55 @@ +package school.hei.asa.service; + +import static java.util.Locale.US; + +import java.util.List; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import school.hei.asa.endpoint.event.EventProducer; +import school.hei.asa.endpoint.event.model.LowRemainingDaysAlertRequested; +import school.hei.asa.model.Worker; + +@Slf4j +@Service +public class LowRemainingDaysAlertService { + + private final ContractService contractService; + private final EventProducer eventProducer; + private final int lowRemainingDaysThreshold; + + public LowRemainingDaysAlertService( + ContractService contractService, + EventProducer eventProducer, + @Value("${LOW_REMAINING_DAYS_THRESHOLD}") int lowRemainingDaysThreshold) { + this.contractService = contractService; + this.eventProducer = eventProducer; + this.lowRemainingDaysThreshold = lowRemainingDaysThreshold; + } + + public Optional checkRemainingDaysAndBuildAlertMessage(Worker worker) { + var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); + + if (remainingDays == 0 || remainingDays >= lowRemainingDaysThreshold) { + return Optional.empty(); + } + + log.info("Requesting alert email to accountants for worker '{}'", worker.code()); + eventProducer.accept( + List.of( + LowRemainingDaysAlertRequested.builder() + .workerCode(worker.code()) + .remainingDays((int) remainingDays) + .build())); + + return Optional.of( + "Please note : You have " + + formatDays(remainingDays) + + " day(s) left on your contract !"); + } + + private static String formatDays(double days) { + return days == (long) days ? String.valueOf((long) days) : String.format(US, "%.1f", days); + } +} diff --git a/src/test/java/school/hei/asa/conf/EnvConf.java b/src/test/java/school/hei/asa/conf/EnvConf.java index 153703ef..9de6e4c8 100644 --- a/src/test/java/school/hei/asa/conf/EnvConf.java +++ b/src/test/java/school/hei/asa/conf/EnvConf.java @@ -25,5 +25,6 @@ void configureProperties(DynamicPropertyRegistry registry) { registry.add("ACCOUNTANTS", () -> "dummy,dummy2"); registry.add("MAX_LATENESS_REPORT", () -> 4); registry.add("SENSITIVE_WORKERS_CODES", () -> "W-059,W-00"); + registry.add("LOW_REMAINING_DAYS_THRESHOLD", () -> 10); } } diff --git a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java new file mode 100644 index 00000000..ea2d2362 --- /dev/null +++ b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java @@ -0,0 +1,86 @@ +package school.hei.asa.service; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import school.hei.asa.conf.FacadeIT; +import school.hei.asa.endpoint.event.EventProducer; +import school.hei.asa.model.Worker; +import school.hei.asa.repository.WorkerRepository; +import school.hei.asa.repository.jrepository.JContractRepository; +import school.hei.asa.repository.model.JContract; +import school.hei.asa.repository.model.JContractLevel; +import school.hei.asa.repository.model.JWorker; + +class LowRemainingDaysAlertServiceIT extends FacadeIT { + + @Autowired LowRemainingDaysAlertService lowRemainingDaysAlertService; + @Autowired WorkerRepository workerRepository; + @Autowired JContractRepository jContractRepository; + + @MockBean EventProducer eventProducer; + + Worker worker; + + @BeforeEach + void setUp() { + worker = new Worker("alert-test-worker", "Test", "test@test.com", "Test Worker", "addr", "city", "nif", "stat"); + workerRepository.save(worker); + } + + @Test + void alert_triggered_when_remaining_days_below_threshold() { + saveContract(5, "2026-07-01T00:00:00Z"); + + var result = lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(worker); + + assertTrue(result.isPresent()); + assertTrue(result.get().contains("day(s) left")); + verify(eventProducer).accept(any(List.class)); + } + + @Test + void no_alert_when_remaining_days_above_threshold() { + saveContract(15, "2026-08-01T00:00:00Z"); + + var result = lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(worker); + + assertTrue(result.isEmpty()); + verify(eventProducer, never()).accept(any()); + } + + @Test + void no_alert_when_no_active_contract() { + var result = lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(worker); + + assertTrue(result.isEmpty()); + verify(eventProducer, never()).accept(any()); + } + + private void saveContract(int durationInDays, String entranceInstantStr) { + var jWorker = new JWorker(); + jWorker.setCode(worker.code()); + var level = new JContractLevel(); + level.setCode("L4P-2026"); + var jContract = new JContract(); + jContract.setId(UUID.randomUUID().toString()); + jContract.setWorker(jWorker); + jContract.setLevel(level); + jContract.setEntranceInstant(Instant.parse(entranceInstantStr)); + jContract.setEndInstant(null); + jContract.setDurationInDays(durationInDays); + jContract.setJobTitle("Test Job"); + jContract.setCompany("Test Company"); + jContract.setContractBucketKey("test-key"); + jContractRepository.save(jContract); + } +} From 46ad9319c6ad874283278ccff8a3fdbaf94597d6 Mon Sep 17 00:00:00 2001 From: nelio-gio Date: Sun, 26 Jul 2026 02:34:41 +0300 Subject: [PATCH 06/28] chore: format code --- .../rest/controller/DailyExecutionController.java | 4 +++- .../hei/asa/service/LowRemainingDaysAlertService.java | 4 +--- .../asa/service/LowRemainingDaysAlertServiceIT.java | 11 ++++++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index 76beb444..7172984c 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -37,7 +37,9 @@ public String createDailyExecution(Authentication authentication, ThDailyExecuti } public String createDailyExecution( - Authentication authentication, ThDailyExecutionForm dmeForm, RedirectAttributes redirectAttributes) { + Authentication authentication, + ThDailyExecutionForm dmeForm, + RedirectAttributes redirectAttributes) { var worker = workerFromAuthentication.apply(authentication).get(); var dailyExecution = thDailyExecutionFormMapper.toDomain(dmeForm, worker); diff --git a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java index 1030e645..66092298 100644 --- a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java +++ b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java @@ -44,9 +44,7 @@ public Optional checkRemainingDaysAndBuildAlertMessage(Worker worker) { .build())); return Optional.of( - "Please note : You have " - + formatDays(remainingDays) - + " day(s) left on your contract !"); + "Please note : You have " + formatDays(remainingDays) + " day(s) left on your contract !"); } private static String formatDays(double days) { diff --git a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java index ea2d2362..4f6df324 100644 --- a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java +++ b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java @@ -33,7 +33,16 @@ class LowRemainingDaysAlertServiceIT extends FacadeIT { @BeforeEach void setUp() { - worker = new Worker("alert-test-worker", "Test", "test@test.com", "Test Worker", "addr", "city", "nif", "stat"); + worker = + new Worker( + "alert-test-worker", + "Test", + "test@test.com", + "Test Worker", + "addr", + "city", + "nif", + "stat"); workerRepository.save(worker); } From d033f3a4261200f6a2a20b68c6b62c8a6b3fcec0 Mon Sep 17 00:00:00 2001 From: Yoan Date: Sun, 26 Jul 2026 11:47:17 +0300 Subject: [PATCH 07/28] test: clean up alert IT fixtures to avoid shared DB pollution Delete the temporary worker and contracts after each LowRemainingDaysAlertServiceIT run so WorkerServiceIT keeps a stable year-2026 dataset. --- .../LowRemainingDaysAlertServiceIT.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java index 4f6df324..ee48840b 100644 --- a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java +++ b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java @@ -8,6 +8,7 @@ import java.time.Instant; import java.util.List; import java.util.UUID; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -17,6 +18,7 @@ import school.hei.asa.model.Worker; import school.hei.asa.repository.WorkerRepository; import school.hei.asa.repository.jrepository.JContractRepository; +import school.hei.asa.repository.jrepository.JWorkerRepository; import school.hei.asa.repository.model.JContract; import school.hei.asa.repository.model.JContractLevel; import school.hei.asa.repository.model.JWorker; @@ -26,6 +28,7 @@ class LowRemainingDaysAlertServiceIT extends FacadeIT { @Autowired LowRemainingDaysAlertService lowRemainingDaysAlertService; @Autowired WorkerRepository workerRepository; @Autowired JContractRepository jContractRepository; + @Autowired JWorkerRepository jWorkerRepository; @MockBean EventProducer eventProducer; @@ -46,6 +49,21 @@ void setUp() { workerRepository.save(worker); } + @AfterEach + void tearDown() { + if (worker == null) { + return; + } + var jWorker = jWorkerRepository.findByCode(worker.code()); + if (jWorker == null) { + return; + } + jContractRepository + .findAllByWorkerOrderByEntranceInstantDesc(jWorker) + .forEach(jContractRepository::delete); + jWorkerRepository.delete(jWorker); + } + @Test void alert_triggered_when_remaining_days_below_threshold() { saveContract(5, "2026-07-01T00:00:00Z"); From ca67b3e8c43a2db107c5bcb79a1acb48501c9c27 Mon Sep 17 00:00:00 2001 From: saviola24 Date: Sun, 26 Jul 2026 16:48:33 +0300 Subject: [PATCH 08/28] Feature/daily execution default date tests (#61) * feat: default date to today on daily-execution + unit tests on active contract/remaining days * feat: default date to today on daily-execution + unit tests on active contract/remaining days * test: add integration test for ContractService using TestContainers --- .../resources/templates/daily-execution.html | 3 +- .../hei/asa/service/ContractServiceIT.java | 53 +++++++ .../hei/asa/service/ContractServiceTest.java | 139 ++++++++++++++++++ 3 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 src/test/java/school/hei/asa/service/ContractServiceIT.java create mode 100644 src/test/java/school/hei/asa/service/ContractServiceTest.java diff --git a/src/main/resources/templates/daily-execution.html b/src/main/resources/templates/daily-execution.html index 2f71f23f..8b9eab4e 100644 --- a/src/main/resources/templates/daily-execution.html +++ b/src/main/resources/templates/daily-execution.html @@ -112,7 +112,8 @@

What did you do that day?

Date: Sun, 26 Jul 2026 17:32:05 +0300 Subject: [PATCH 09/28] feat(calendar): integrate low remaining days alert service (#62) --- .../rest/controller/CalendarController.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java index 72949697..d5182be4 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java @@ -28,6 +28,7 @@ import school.hei.asa.model.Worker; import school.hei.asa.service.CalendarService; import school.hei.asa.service.ContractService; +import school.hei.asa.service.LowRemainingDaysAlertService; @AllArgsConstructor @Controller @@ -37,6 +38,7 @@ public class CalendarController { private final WorkerFromAuthentication workerFromAuthentication; private final WorkerToModelAdder workerToModelAdder; private final ContractService contractService; + private final LowRemainingDaysAlertService lowRemainingDaysAlertService; @GetMapping("/work-and-care-calendar") public String getCalendar( @@ -70,11 +72,14 @@ public String getCalendar( var lateReportedDaysByMonth = calendarService.lateReportedDaysByMonth(worker, year); var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); - var hasUsableContract = - contractService.findActiveContractByWorker(worker).isPresent() && remainingDays > 0; + var hasActiveContract = contractService.findActiveContractByWorker(worker).isPresent(); + var showWarning = + lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(worker).isPresent() + || (hasActiveContract && remainingDays == 0); - model.addAttribute("remainingDays", hasUsableContract ? remainingDays : null); - model.addAttribute("hasUsableContract", hasUsableContract); + model.addAttribute("remainingDays", hasActiveContract ? remainingDays : null); + model.addAttribute("hasUsableContract", hasActiveContract); + model.addAttribute("showWarning", showWarning); model.addAttribute("workerCode", workerCodeOrAuth); model.addAttribute("currentYear", now().getYear()); From 4120bbbedd7c13e1c97d29380c5131b6bb66af51 Mon Sep 17 00:00:00 2001 From: haja171106 Date: Sun, 26 Jul 2026 20:35:22 +0300 Subject: [PATCH 10/28] fix: prevent alert email on calendar view and trigger after pointage (#63) --- .../controller/DailyExecutionController.java | 2 ++ .../service/LowRemainingDaysAlertService.java | 14 ++++++++++--- .../LowRemainingDaysAlertServiceIT.java | 20 ++++++++++++++++++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index 7172984c..a0c88491 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -45,6 +45,8 @@ public String createDailyExecution( dailyExecutionRepository.save(dailyExecution); + lowRemainingDaysAlertService.sendAlertEmailIfLowRemainingDays(worker); + lowRemainingDaysAlertService .checkRemainingDaysAndBuildAlertMessage(worker) .ifPresent( diff --git a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java index 66092298..02819e39 100644 --- a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java +++ b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java @@ -35,6 +35,17 @@ public Optional checkRemainingDaysAndBuildAlertMessage(Worker worker) { return Optional.empty(); } + return Optional.of( + "Please note : You have " + formatDays(remainingDays) + " day(s) left on your contract !"); + } + + public void sendAlertEmailIfLowRemainingDays(Worker worker) { + var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); + + if (remainingDays == 0 || remainingDays >= lowRemainingDaysThreshold) { + return; + } + log.info("Requesting alert email to accountants for worker '{}'", worker.code()); eventProducer.accept( List.of( @@ -42,9 +53,6 @@ public Optional checkRemainingDaysAndBuildAlertMessage(Worker worker) { .workerCode(worker.code()) .remainingDays((int) remainingDays) .build())); - - return Optional.of( - "Please note : You have " + formatDays(remainingDays) + " day(s) left on your contract !"); } private static String formatDays(double days) { diff --git a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java index ee48840b..c149d5fd 100644 --- a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java +++ b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java @@ -65,16 +65,34 @@ void tearDown() { } @Test - void alert_triggered_when_remaining_days_below_threshold() { + void alert_message_built_without_sending_email_when_remaining_days_below_threshold() { saveContract(5, "2026-07-01T00:00:00Z"); var result = lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(worker); assertTrue(result.isPresent()); assertTrue(result.get().contains("day(s) left")); + verify(eventProducer, never()).accept(any()); + } + + @Test + void send_alert_email_when_remaining_days_below_threshold() { + saveContract(5, "2026-07-01T00:00:00Z"); + + lowRemainingDaysAlertService.sendAlertEmailIfLowRemainingDays(worker); + verify(eventProducer).accept(any(List.class)); } + @Test + void no_alert_email_when_remaining_days_above_threshold() { + saveContract(15, "2026-08-01T00:00:00Z"); + + lowRemainingDaysAlertService.sendAlertEmailIfLowRemainingDays(worker); + + verify(eventProducer, never()).accept(any()); + } + @Test void no_alert_when_remaining_days_above_threshold() { saveContract(15, "2026-08-01T00:00:00Z"); From e6cf78e49227025fecc95a3578c7b1a74b9ec7fe Mon Sep 17 00:00:00 2001 From: nyamyjese Date: Mon, 27 Jul 2026 08:31:07 +0100 Subject: [PATCH 11/28] fix: reject punch-in when contract is inactive (#64) * fix: reject punch-in when contract is inactive * fix(test) : add active contract fixture to IT tests * fix(test): add active contract fixture to CalendarServiceIT * chore: format code * fix(test): use fixed past dates for contract fixtures to avoid polluting year-based queries --- .../controller/DailyExecutionController.java | 10 +++++ .../DailyExecutionControllerIT.java | 45 ++++++++++++++++--- .../rest/service/CalendarServiceIT.java | 44 ++++++++++++++++++ 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index a0c88491..ecc89ba5 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -13,6 +13,7 @@ import school.hei.asa.endpoint.rest.security.WorkerFromAuthentication; import school.hei.asa.endpoint.rest.service.ThMissionService; import school.hei.asa.repository.DailyExecutionRepository; +import school.hei.asa.service.ContractService; import school.hei.asa.service.LowRemainingDaysAlertService; @Controller @@ -23,6 +24,7 @@ public class DailyExecutionController { private final WorkerFromAuthentication workerFromAuthentication; private final ThMissionService thMissionService; private final LowRemainingDaysAlertService lowRemainingDaysAlertService; + private final ContractService contractService; @GetMapping("/daily-execution") public String getDailyExecutionForm(Model model) { @@ -41,6 +43,14 @@ public String createDailyExecution( ThDailyExecutionForm dmeForm, RedirectAttributes redirectAttributes) { var worker = workerFromAuthentication.apply(authentication).get(); + + if (contractService.findActiveContractByWorker(worker).isEmpty()) { + redirectAttributes.addFlashAttribute( + "toastMessage", "Unable to punch in : you have no active contract."); + redirectAttributes.addFlashAttribute("toastType", "error"); + return "redirect:/work-and-care-calendar"; + } + var dailyExecution = thDailyExecutionFormMapper.toDomain(dmeForm, worker); dailyExecutionRepository.save(dailyExecution); diff --git a/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java b/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java index c219bab4..707dbbe5 100644 --- a/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java +++ b/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java @@ -10,10 +10,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import jakarta.persistence.EntityManager; +import java.time.Instant; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import org.junit.jupiter.api.BeforeEach; @@ -21,6 +24,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.core.Authentication; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; import org.springframework.ui.Model; import school.hei.asa.conf.FacadeIT; import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; @@ -29,10 +34,15 @@ import school.hei.asa.model.Mission; import school.hei.asa.model.Product; import school.hei.asa.model.Worker; +import school.hei.asa.model.contract.ContractType; import school.hei.asa.repository.DailyExecutionRepository; import school.hei.asa.repository.MissionRepository; import school.hei.asa.repository.ProductRepository; import school.hei.asa.repository.WorkerRepository; +import school.hei.asa.repository.jrepository.JContractRepository; +import school.hei.asa.repository.mapper.WorkerMapper; +import school.hei.asa.repository.model.JContract; +import school.hei.asa.repository.model.JContractLevel; class DailyExecutionControllerIT extends FacadeIT { @@ -42,6 +52,10 @@ class DailyExecutionControllerIT extends FacadeIT { @Autowired MissionRepository missionRepository; @Autowired DailyExecutionRepository dailyExecutionRepository; @Autowired CalendarController calendarController; + @Autowired JContractRepository jContractRepository; + @Autowired WorkerMapper workerMapper; + @Autowired EntityManager entityManager; + @Autowired PlatformTransactionManager transactionManager; @MockBean SecurityConfig securityConfig; @MockBean WorkerFromAuthentication workerFromAuthentication; @@ -59,6 +73,32 @@ void setUp() { workerRepository.save(authenticatedWorker); when(workerFromAuthentication.apply(authentication)) .thenReturn(Optional.of(authenticatedWorker)); + + var contractLevelCode = "level-code-" + UUID.randomUUID(); + new TransactionTemplate(transactionManager) + .execute( + status -> { + var jContractLevel = new JContractLevel(); + jContractLevel.setCode(contractLevelCode); + jContractLevel.setType(ContractType.fullTimeEmployee); + jContractLevel.setMonthlyPay(1000.0); + jContractLevel.setDailyPay(50.0); + entityManager.persist(jContractLevel); + return null; + }); + + var jContract = new JContract(); + jContract.setId("contract-test-id-" + UUID.randomUUID()); + jContract.setWorker(workerMapper.toEntity(authenticatedWorker)); + jContract.setLevel(entityManager.find(JContractLevel.class, contractLevelCode)); + jContract.setEntranceInstant(Instant.parse("2010-01-01T00:00:00Z")); + jContract.setEndInstant(Instant.parse("2010-12-31T00:00:00Z")); + jContract.setJobTitle("job-title"); + jContract.setDurationInDays(180); + jContract.setCompany("company"); + jContract.setContractBucketKey("contract-bucket-key"); + jContractRepository.save(jContract); + var product = new Product("pcode", "pname", "pdescription"); productRepository.save(product); var mission1 = new Mission("mission1-code", "title1", "description1", 10, product); @@ -69,7 +109,6 @@ void setUp() { @Test void save_then_read_with_duplicates_ok_if_sum_of_set_is_100() { - setUp(); var dmeForm = new ThDailyExecutionForm( "2024-12-03", @@ -79,7 +118,6 @@ void save_then_read_with_duplicates_ok_if_sum_of_set_is_100() { "mission2-code", "0.6", "missionComment2", - // duplicate of mission2 (missionCode2, missionPercentage2, missionComment2) "mission2-code", "0.6", "missionComment2", @@ -110,7 +148,6 @@ void save_then_read_with_duplicates_ok_if_sum_of_set_is_100() { @Test void cannot_save_if_mission_execution_already_exists() { - setUp(); var dmeForm = new ThDailyExecutionForm( "2024-12-01", @@ -145,7 +182,6 @@ void read_worker_lita_with_duplicate_missions_and_percentage_over_100_ok() { @Test void concurrently_create_daily_execution() { - setUp(); var dmeForm = new ThDailyExecutionForm( "2024-12-01", @@ -196,7 +232,6 @@ void concurrently_create_daily_execution() { @Test void can_get_daily_execution_form() { - setUp(); var viewName = dailyExecutionController.getDailyExecutionForm(model); verify(model).addAttribute(eq("missions"), any(List.class)); diff --git a/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java b/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java index 34db5066..2a41ace8 100644 --- a/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java +++ b/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java @@ -9,14 +9,19 @@ import static school.hei.asa.model.DailyExecution.Type.fullWork; import static school.hei.asa.model.DailyExecution.Type.mixedWorkAndCare; +import jakarta.persistence.EntityManager; +import java.time.Instant; import java.time.LocalDate; import java.util.List; import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.core.Authentication; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; import school.hei.asa.conf.FacadeIT; import school.hei.asa.endpoint.rest.controller.DailyExecutionController; import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; @@ -25,9 +30,14 @@ import school.hei.asa.model.Mission; import school.hei.asa.model.Product; import school.hei.asa.model.Worker; +import school.hei.asa.model.contract.ContractType; import school.hei.asa.repository.MissionRepository; import school.hei.asa.repository.ProductRepository; import school.hei.asa.repository.WorkerRepository; +import school.hei.asa.repository.jrepository.JContractRepository; +import school.hei.asa.repository.mapper.WorkerMapper; +import school.hei.asa.repository.model.JContract; +import school.hei.asa.repository.model.JContractLevel; import school.hei.asa.service.CalendarService; class CalendarServiceIT extends FacadeIT { @@ -35,6 +45,10 @@ class CalendarServiceIT extends FacadeIT { @Autowired WorkerRepository workerRepository; @Autowired ProductRepository productRepository; @Autowired MissionRepository missionRepository; + @Autowired JContractRepository jContractRepository; + @Autowired WorkerMapper workerMapper; + @Autowired EntityManager entityManager; + @Autowired PlatformTransactionManager transactionManager; @MockBean SecurityConfig securityConfig; @MockBean WorkerFromAuthentication workerFromAuthentication; @@ -47,6 +61,7 @@ class CalendarServiceIT extends FacadeIT { @BeforeEach void setUp() { authentication = authentication(); + setUpActiveContract(); setUpProductsAndMissions(); } @@ -188,4 +203,33 @@ private void setUpProductsAndMissions() { var careMission = new Mission("careMission-code", "", "", 2, careProduct); missionRepository.saveAll(List.of(mission1, mission2, careMission)); } + + private void setUpActiveContract() { + var worker = workerRepository.findByCode(authenticatedWorkerCode); + var contractLevelCode = "level-code-" + UUID.randomUUID(); + + new TransactionTemplate(transactionManager) + .execute( + status -> { + var jContractLevel = new JContractLevel(); + jContractLevel.setCode(contractLevelCode); + jContractLevel.setType(ContractType.fullTimeEmployee); + jContractLevel.setMonthlyPay(1000.0); + jContractLevel.setDailyPay(50.0); + entityManager.persist(jContractLevel); + return null; + }); + + var jContract = new JContract(); + jContract.setId("contract-test-id-" + UUID.randomUUID()); + jContract.setWorker(workerMapper.toEntity(worker)); + jContract.setLevel(entityManager.find(JContractLevel.class, contractLevelCode)); + jContract.setEntranceInstant(Instant.parse("2010-01-01T00:00:00Z")); + jContract.setEndInstant(Instant.parse("2010-12-31T00:00:00Z")); + jContract.setJobTitle("job-title"); + jContract.setDurationInDays(180); + jContract.setCompany("company"); + jContract.setContractBucketKey("contract-bucket-key"); + jContractRepository.save(jContract); + } } From 0ffd66174092727ee1ac6f59fe1cad3852e2f0b4 Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Mon, 27 Jul 2026 20:55:11 +0300 Subject: [PATCH 12/28] fix: calendar assets, zero-days banner, and pointage Loza guard (#65) * fix: restore calendar toast/DaysFormatter and treat zero days as no usable contract Add missing template assets that caused 500s, and show the inactive-contract banner instead of a J-0 warning when remaining days are exhausted. * fix: reject pointage with Loza error when worker has no usable contract Throw instead of toast-redirect so ErrorHandler shows Loza Oops when there is no active contract or remaining days are exhausted. * format code --- .../rest/controller/CalendarController.java | 13 ++++---- .../controller/DailyExecutionController.java | 10 +++---- .../school/hei/asa/number/DaysFormatter.java | 16 ++++++++++ .../resources/templates/fragments/toast.html | 30 +++++++++++++++++++ 4 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 src/main/java/school/hei/asa/number/DaysFormatter.java create mode 100644 src/main/resources/templates/fragments/toast.html diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java index d5182be4..6402c98d 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java @@ -72,13 +72,16 @@ public String getCalendar( var lateReportedDaysByMonth = calendarService.lateReportedDaysByMonth(worker, year); var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); - var hasActiveContract = contractService.findActiveContractByWorker(worker).isPresent(); + var hasUsableContract = + contractService.findActiveContractByWorker(worker).isPresent() && remainingDays > 0; var showWarning = - lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(worker).isPresent() - || (hasActiveContract && remainingDays == 0); + hasUsableContract + && lowRemainingDaysAlertService + .checkRemainingDaysAndBuildAlertMessage(worker) + .isPresent(); - model.addAttribute("remainingDays", hasActiveContract ? remainingDays : null); - model.addAttribute("hasUsableContract", hasActiveContract); + model.addAttribute("remainingDays", hasUsableContract ? remainingDays : null); + model.addAttribute("hasUsableContract", hasUsableContract); model.addAttribute("showWarning", showWarning); model.addAttribute("workerCode", workerCodeOrAuth); diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index ecc89ba5..5de51078 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -44,11 +44,11 @@ public String createDailyExecution( RedirectAttributes redirectAttributes) { var worker = workerFromAuthentication.apply(authentication).get(); - if (contractService.findActiveContractByWorker(worker).isEmpty()) { - redirectAttributes.addFlashAttribute( - "toastMessage", "Unable to punch in : you have no active contract."); - redirectAttributes.addFlashAttribute("toastType", "error"); - return "redirect:/work-and-care-calendar"; + var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); + var hasUsableContract = + contractService.findActiveContractByWorker(worker).isPresent() && remainingDays > 0; + if (!hasUsableContract) { + throw new IllegalStateException("Unable to punch in : you have no active contract."); } var dailyExecution = thDailyExecutionFormMapper.toDomain(dmeForm, worker); diff --git a/src/main/java/school/hei/asa/number/DaysFormatter.java b/src/main/java/school/hei/asa/number/DaysFormatter.java new file mode 100644 index 00000000..71ba155c --- /dev/null +++ b/src/main/java/school/hei/asa/number/DaysFormatter.java @@ -0,0 +1,16 @@ +package school.hei.asa.number; + +import java.math.BigDecimal; + +public final class DaysFormatter { + + private DaysFormatter() {} + + public static String format(double days) { + var normalized = BigDecimal.valueOf(days).stripTrailingZeros(); + if (normalized.scale() <= 0) { + return normalized.toBigInteger().toString(); + } + return normalized.toPlainString(); + } +} diff --git a/src/main/resources/templates/fragments/toast.html b/src/main/resources/templates/fragments/toast.html new file mode 100644 index 00000000..2edc8647 --- /dev/null +++ b/src/main/resources/templates/fragments/toast.html @@ -0,0 +1,30 @@ + + + +
+ + + +
+ + From f30f75daccee42de6489b09fc49883d60f3e7fdd Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Mon, 27 Jul 2026 23:39:13 +0300 Subject: [PATCH 13/28] chore: restore application.properties without session jdbc init (#66) Remove spring.session.jdbc.initialize-schema=always introduced in #59 so the file matches its upstream state again. --- src/main/resources/application.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 452d0b8e..6fc771ce 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,5 @@ spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true -spring.session.jdbc.initialize-schema=always logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE sentry.dsn=https://175df70cc551efc22dd25f4cc4c17ef7@o4508285839605760.ingest.de.sentry.io/4508691883622480 From d506bcfde5969b78275918b10d3cc523748fdb43 Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Tue, 28 Jul 2026 00:50:57 +0300 Subject: [PATCH 14/28] docs: document ACCOUNTANTS and LOW_REMAINING_DAYS_THRESHOLD in README (#67) List the env vars required for accountant emails and the low remaining days alert threshold for local runs. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index de767619..d05b1ca3 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ ASA_CARE_PRODUCT_CODE= ASA_PAID_CARE_MISSION_CODES= SENSITIVE_WORKERS_CODES= MAX_LATENESS_REPORT= +ACCOUNTANTS= +LOW_REMAINING_DAYS_THRESHOLD= ``` Then, run Spring Boot as usual, From fc2f01f51a6d7fb2f5ac71b9b964f95ccd926c87 Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:05:45 +0300 Subject: [PATCH 15/28] refactor: simplify low remaining alert wiring (#68) * refactor: simplify low remaining alert wiring Reduce review noise by removing duplicate alert-message construction and small helper indirections without touching upstream-owned tests. * fix: remove unused import in alert requested service Align the review-cleanup branch with CI formatting by dropping the unused InternetAddress import. --- .../asa/endpoint/rest/controller/CalendarController.java | 9 +++++---- .../hei/asa/service/LowRemainingDaysAlertService.java | 8 ++++++-- .../event/LowRemainingDaysAlertRequestedService.java | 8 ++------ src/main/resources/templates/calendar.html | 8 ++++---- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java index 6402c98d..4100a1f5 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java @@ -74,15 +74,16 @@ public String getCalendar( var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); var hasUsableContract = contractService.findActiveContractByWorker(worker).isPresent() && remainingDays > 0; - var showWarning = + var alertMessage = hasUsableContract - && lowRemainingDaysAlertService + ? lowRemainingDaysAlertService .checkRemainingDaysAndBuildAlertMessage(worker) - .isPresent(); + .orElse(null) + : null; model.addAttribute("remainingDays", hasUsableContract ? remainingDays : null); model.addAttribute("hasUsableContract", hasUsableContract); - model.addAttribute("showWarning", showWarning); + model.addAttribute("alertMessage", alertMessage); model.addAttribute("workerCode", workerCodeOrAuth); model.addAttribute("currentYear", now().getYear()); diff --git a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java index 02819e39..66165918 100644 --- a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java +++ b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java @@ -31,7 +31,7 @@ public LowRemainingDaysAlertService( public Optional checkRemainingDaysAndBuildAlertMessage(Worker worker) { var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); - if (remainingDays == 0 || remainingDays >= lowRemainingDaysThreshold) { + if (!isBelowThreshold(remainingDays)) { return Optional.empty(); } @@ -42,7 +42,7 @@ public Optional checkRemainingDaysAndBuildAlertMessage(Worker worker) { public void sendAlertEmailIfLowRemainingDays(Worker worker) { var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); - if (remainingDays == 0 || remainingDays >= lowRemainingDaysThreshold) { + if (!isBelowThreshold(remainingDays)) { return; } @@ -55,6 +55,10 @@ public void sendAlertEmailIfLowRemainingDays(Worker worker) { .build())); } + private boolean isBelowThreshold(double remainingDays) { + return remainingDays > 0 && remainingDays < lowRemainingDaysThreshold; + } + private static String formatDays(double days) { return days == (long) days ? String.valueOf((long) days) : String.format(US, "%.1f", days); } diff --git a/src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java b/src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java index 4d5de068..e790186c 100644 --- a/src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java +++ b/src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java @@ -2,7 +2,6 @@ import static org.reflections.Reflections.log; -import jakarta.mail.internet.InternetAddress; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; @@ -36,16 +35,13 @@ public void accept(LowRemainingDaysAlertRequested event) { internetAddressMapper.toInternetAddresses( Arrays.stream(this.accountants.split(",")).toList()); - sendAlertToAccountants(accountantAddresses, event.getWorkerCode(), event.getRemainingDays()); - } - - private void sendAlertToAccountants( - List accountantAddresses, String workerCode, int remainingDays) { if (accountantAddresses.isEmpty()) { log.info("No accountant address found. Skipping alert email."); return; } + var workerCode = event.getWorkerCode(); + var remainingDays = event.getRemainingDays(); var subject = String.format("ASA - ALERT: low remaining days - Worker %s", workerCode); var body = String.format( diff --git a/src/main/resources/templates/calendar.html b/src/main/resources/templates/calendar.html index d9b87253..e13bdf30 100644 --- a/src/main/resources/templates/calendar.html +++ b/src/main/resources/templates/calendar.html @@ -16,12 +16,12 @@ Please note : You do not have an active contract. Please contact your administrator.
-
- Please note : You have only days on your contract ! +
@@ -35,8 +35,8 @@ -
- Please note : You have only days left on your contract ! +
+
From 757b63eea1bc424a943ca0e16d591217835ceeeb Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:29:42 +0300 Subject: [PATCH 16/28] fix: align pointage and calendar flow with review feedback (#70) * refactor: simplify low remaining alert wiring Reduce review noise by removing duplicate alert-message construction and small helper indirections without touching upstream-owned tests. * fix: remove unused import in alert requested service Align the review-cleanup branch with CI formatting by dropping the unused InternetAddress import. * fix: align pointage and calendar flow with review feedback Keep the low-days warning on redirect via flash attributes, add a repository guard for missing active contracts, and remove the calendar-side low-days warning rendering. --- .../rest/controller/CalendarController.java | 9 --------- .../rest/controller/DailyExecutionController.java | 5 ----- .../asa/repository/DailyExecutionRepository.java | 4 ++++ src/main/resources/templates/calendar.html | 11 ----------- .../controller/DailyExecutionControllerIT.java | 14 ++++++++++---- .../endpoint/rest/service/CalendarServiceIT.java | 13 +++++++++---- 6 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java index 4100a1f5..72949697 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java @@ -28,7 +28,6 @@ import school.hei.asa.model.Worker; import school.hei.asa.service.CalendarService; import school.hei.asa.service.ContractService; -import school.hei.asa.service.LowRemainingDaysAlertService; @AllArgsConstructor @Controller @@ -38,7 +37,6 @@ public class CalendarController { private final WorkerFromAuthentication workerFromAuthentication; private final WorkerToModelAdder workerToModelAdder; private final ContractService contractService; - private final LowRemainingDaysAlertService lowRemainingDaysAlertService; @GetMapping("/work-and-care-calendar") public String getCalendar( @@ -74,16 +72,9 @@ public String getCalendar( var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); var hasUsableContract = contractService.findActiveContractByWorker(worker).isPresent() && remainingDays > 0; - var alertMessage = - hasUsableContract - ? lowRemainingDaysAlertService - .checkRemainingDaysAndBuildAlertMessage(worker) - .orElse(null) - : null; model.addAttribute("remainingDays", hasUsableContract ? remainingDays : null); model.addAttribute("hasUsableContract", hasUsableContract); - model.addAttribute("alertMessage", alertMessage); model.addAttribute("workerCode", workerCodeOrAuth); model.addAttribute("currentYear", now().getYear()); diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index 5de51078..b8b22aa8 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -7,7 +7,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import school.hei.asa.endpoint.rest.controller.mapper.ThDailyExecutionFormMapper; import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; import school.hei.asa.endpoint.rest.security.WorkerFromAuthentication; @@ -34,10 +33,6 @@ public String getDailyExecutionForm(Model model) { } @PostMapping("/daily-execution") - public String createDailyExecution(Authentication authentication, ThDailyExecutionForm dmeForm) { - return createDailyExecution(authentication, dmeForm, new RedirectAttributesModelMap()); - } - public String createDailyExecution( Authentication authentication, ThDailyExecutionForm dmeForm, diff --git a/src/main/java/school/hei/asa/repository/DailyExecutionRepository.java b/src/main/java/school/hei/asa/repository/DailyExecutionRepository.java index 67413427..0e86ee25 100644 --- a/src/main/java/school/hei/asa/repository/DailyExecutionRepository.java +++ b/src/main/java/school/hei/asa/repository/DailyExecutionRepository.java @@ -22,6 +22,7 @@ @Repository public class DailyExecutionRepository { + private final ContractRepository contractRepository; private final MissionExecutionRepository missionExecutionRepository; private final JMissionExecutionRepository jMissionExecutionRepository; private final JWorkerRepository jWorkerRepository; @@ -33,6 +34,9 @@ public class DailyExecutionRepository { @Transactional(isolation = SERIALIZABLE) public void save(DailyExecution dailyExecution) { var date = dailyExecution.date(); + if (contractRepository.findActiveContractByWorker(dailyExecution.worker()).isEmpty()) { + throw new IllegalStateException("Unable to punch in : you have no active contract."); + } if (!missionExecutionRepository.findAllBy(dailyExecution.worker(), date).isEmpty()) { throw new IllegalArgumentException("Day already has MissionExecution: " + date); } diff --git a/src/main/resources/templates/calendar.html b/src/main/resources/templates/calendar.html index e13bdf30..27ff4a5f 100644 --- a/src/main/resources/templates/calendar.html +++ b/src/main/resources/templates/calendar.html @@ -16,14 +16,6 @@ Please note : You do not have an active contract. Please contact your administrator.
-
- -
-
@@ -35,9 +27,6 @@ -
- -
diff --git a/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java b/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java index 707dbbe5..80f1bb1b 100644 --- a/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java +++ b/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java @@ -27,6 +27,7 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.support.TransactionTemplate; import org.springframework.ui.Model; +import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import school.hei.asa.conf.FacadeIT; import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; import school.hei.asa.endpoint.rest.security.SecurityConfig; @@ -128,7 +129,8 @@ void save_then_read_with_duplicates_ok_if_sum_of_set_is_100() { null, null); - dailyExecutionController.createDailyExecution(authentication, dmeForm); + dailyExecutionController.createDailyExecution( + authentication, dmeForm, new RedirectAttributesModelMap()); var savedWorker = workerRepository.findByCode(authenticatedWorker.code()); var dailyExecutions = @@ -167,10 +169,13 @@ void cannot_save_if_mission_execution_already_exists() { null, null); - dailyExecutionController.createDailyExecution(authentication, dmeForm); + dailyExecutionController.createDailyExecution( + authentication, dmeForm, new RedirectAttributesModelMap()); assertThrows( Exception.class, - () -> dailyExecutionController.createDailyExecution(authentication, dmeForm)); + () -> + dailyExecutionController.createDailyExecution( + authentication, dmeForm, new RedirectAttributesModelMap())); } @Test @@ -211,7 +216,8 @@ void concurrently_create_daily_execution() { () -> { try { latch.await(); - return dailyExecutionController.createDailyExecution(authentication, dmeForm); + return dailyExecutionController.createDailyExecution( + authentication, dmeForm, new RedirectAttributesModelMap()); } catch (Exception e) { return e.getMessage(); } diff --git a/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java b/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java index 2a41ace8..4c693718 100644 --- a/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java +++ b/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java @@ -22,6 +22,7 @@ import org.springframework.security.core.Authentication; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import school.hei.asa.conf.FacadeIT; import school.hei.asa.endpoint.rest.controller.DailyExecutionController; import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; @@ -85,7 +86,8 @@ void datesByDailyExecution_by_fullWork() { null, null, null, - null)); + null), + new RedirectAttributesModelMap()); var worker = workerRepository.findByCode(authenticatedWorkerCode); var datesByDailyExecutionType = calendarService.datesByDailyExecutionType(worker, 2024); @@ -117,7 +119,8 @@ void datesByDailyExecution_by_fullCare() { null, null, null, - null)); + null), + new RedirectAttributesModelMap()); dailyExecutionController.createDailyExecution( authentication, new ThDailyExecutionForm( @@ -136,7 +139,8 @@ void datesByDailyExecution_by_fullCare() { null, null, null, - null)); + null), + new RedirectAttributesModelMap()); var worker = workerRepository.findByCode(authenticatedWorkerCode); var datesByDailyExecutionType = calendarService.datesByDailyExecutionType(worker, 2025); @@ -165,7 +169,8 @@ void datesByDailyExecution_by_mixedWorkAndCare() { null, null, null, - null)); + null), + new RedirectAttributesModelMap()); var worker = workerRepository.findByCode(authenticatedWorkerCode); var datesByDailyExecutionType = calendarService.datesByDailyExecutionType(worker, 2024); From 8f74be7fce9689ad8495810ea85cd08993d5d9a0 Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Tue, 28 Jul 2026 19:37:23 +0300 Subject: [PATCH 17/28] fix: show low-days banner from calendar GET (#72) Move alert message display off POST flash attributes onto the calendar Model, and keep email sending on pointage only. --- .../rest/controller/CalendarController.java | 11 +++++++++++ .../controller/DailyExecutionController.java | 19 ------------------- src/main/resources/templates/calendar.html | 4 ++++ 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java index 72949697..80d99a91 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java @@ -28,6 +28,7 @@ import school.hei.asa.model.Worker; import school.hei.asa.service.CalendarService; import school.hei.asa.service.ContractService; +import school.hei.asa.service.LowRemainingDaysAlertService; @AllArgsConstructor @Controller @@ -37,6 +38,7 @@ public class CalendarController { private final WorkerFromAuthentication workerFromAuthentication; private final WorkerToModelAdder workerToModelAdder; private final ContractService contractService; + private final LowRemainingDaysAlertService lowRemainingDaysAlertService; @GetMapping("/work-and-care-calendar") public String getCalendar( @@ -76,6 +78,15 @@ public String getCalendar( model.addAttribute("remainingDays", hasUsableContract ? remainingDays : null); model.addAttribute("hasUsableContract", hasUsableContract); + lowRemainingDaysAlertService + .checkRemainingDaysAndBuildAlertMessage(worker) + .ifPresent( + message -> { + model.addAttribute("toastMessage", message); + model.addAttribute("toastType", "warning"); + model.addAttribute("warningBannerMessage", message); + }); + model.addAttribute("workerCode", workerCodeOrAuth); model.addAttribute("currentYear", now().getYear()); model.addAttribute( diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index b8b22aa8..a110264e 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -12,7 +12,6 @@ import school.hei.asa.endpoint.rest.security.WorkerFromAuthentication; import school.hei.asa.endpoint.rest.service.ThMissionService; import school.hei.asa.repository.DailyExecutionRepository; -import school.hei.asa.service.ContractService; import school.hei.asa.service.LowRemainingDaysAlertService; @Controller @@ -23,7 +22,6 @@ public class DailyExecutionController { private final WorkerFromAuthentication workerFromAuthentication; private final ThMissionService thMissionService; private final LowRemainingDaysAlertService lowRemainingDaysAlertService; - private final ContractService contractService; @GetMapping("/daily-execution") public String getDailyExecutionForm(Model model) { @@ -38,28 +36,11 @@ public String createDailyExecution( ThDailyExecutionForm dmeForm, RedirectAttributes redirectAttributes) { var worker = workerFromAuthentication.apply(authentication).get(); - - var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); - var hasUsableContract = - contractService.findActiveContractByWorker(worker).isPresent() && remainingDays > 0; - if (!hasUsableContract) { - throw new IllegalStateException("Unable to punch in : you have no active contract."); - } - var dailyExecution = thDailyExecutionFormMapper.toDomain(dmeForm, worker); dailyExecutionRepository.save(dailyExecution); - lowRemainingDaysAlertService.sendAlertEmailIfLowRemainingDays(worker); - lowRemainingDaysAlertService - .checkRemainingDaysAndBuildAlertMessage(worker) - .ifPresent( - message -> { - redirectAttributes.addFlashAttribute("toastMessage", message); - redirectAttributes.addFlashAttribute("toastType", "warning"); - }); - return "redirect:/work-and-care-calendar"; } } diff --git a/src/main/resources/templates/calendar.html b/src/main/resources/templates/calendar.html index 27ff4a5f..b8e76258 100644 --- a/src/main/resources/templates/calendar.html +++ b/src/main/resources/templates/calendar.html @@ -16,6 +16,10 @@ Please note : You do not have an active contract. Please contact your administrator.
+
+ +
+
From c350a921b495c4ea6f661e147c3ea27eaf75fae0 Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:14:38 +0300 Subject: [PATCH 18/28] test: move alert IT fixtures to Flyway migration (#73) * test: move alert IT fixtures to Flyway migration Replace runtime worker/contract saves in LowRemainingDaysAlertServiceIT with stable V101_4 test data. * format code * test: keep alert fixtures out of WorkerServiceIT year 2026 Use 2027 entrance dates so low-remaining alert contracts are not returned by findByYearBetween(2026, 2027). --- .../LowRemainingDaysAlertServiceIT.java | 93 ++++--------------- ...sert_low_remaining_days_alert_fixtures.sql | 9 ++ 2 files changed, 28 insertions(+), 74 deletions(-) create mode 100644 src/test/resources/db/migration/V101_4__Insert_low_remaining_days_alert_fixtures.sql diff --git a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java index c149d5fd..879c149a 100644 --- a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java +++ b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java @@ -5,70 +5,24 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import java.time.Instant; import java.util.List; -import java.util.UUID; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import school.hei.asa.conf.FacadeIT; import school.hei.asa.endpoint.event.EventProducer; import school.hei.asa.model.Worker; -import school.hei.asa.repository.WorkerRepository; -import school.hei.asa.repository.jrepository.JContractRepository; -import school.hei.asa.repository.jrepository.JWorkerRepository; -import school.hei.asa.repository.model.JContract; -import school.hei.asa.repository.model.JContractLevel; -import school.hei.asa.repository.model.JWorker; class LowRemainingDaysAlertServiceIT extends FacadeIT { @Autowired LowRemainingDaysAlertService lowRemainingDaysAlertService; - @Autowired WorkerRepository workerRepository; - @Autowired JContractRepository jContractRepository; - @Autowired JWorkerRepository jWorkerRepository; @MockBean EventProducer eventProducer; - Worker worker; - - @BeforeEach - void setUp() { - worker = - new Worker( - "alert-test-worker", - "Test", - "test@test.com", - "Test Worker", - "addr", - "city", - "nif", - "stat"); - workerRepository.save(worker); - } - - @AfterEach - void tearDown() { - if (worker == null) { - return; - } - var jWorker = jWorkerRepository.findByCode(worker.code()); - if (jWorker == null) { - return; - } - jContractRepository - .findAllByWorkerOrderByEntranceInstantDesc(jWorker) - .forEach(jContractRepository::delete); - jWorkerRepository.delete(jWorker); - } - @Test void alert_message_built_without_sending_email_when_remaining_days_below_threshold() { - saveContract(5, "2026-07-01T00:00:00Z"); - - var result = lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(worker); + var result = + lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(workerBelowThreshold()); assertTrue(result.isPresent()); assertTrue(result.get().contains("day(s) left")); @@ -77,27 +31,22 @@ void alert_message_built_without_sending_email_when_remaining_days_below_thresho @Test void send_alert_email_when_remaining_days_below_threshold() { - saveContract(5, "2026-07-01T00:00:00Z"); - - lowRemainingDaysAlertService.sendAlertEmailIfLowRemainingDays(worker); + lowRemainingDaysAlertService.sendAlertEmailIfLowRemainingDays(workerBelowThreshold()); verify(eventProducer).accept(any(List.class)); } @Test void no_alert_email_when_remaining_days_above_threshold() { - saveContract(15, "2026-08-01T00:00:00Z"); - - lowRemainingDaysAlertService.sendAlertEmailIfLowRemainingDays(worker); + lowRemainingDaysAlertService.sendAlertEmailIfLowRemainingDays(workerAboveThreshold()); verify(eventProducer, never()).accept(any()); } @Test void no_alert_when_remaining_days_above_threshold() { - saveContract(15, "2026-08-01T00:00:00Z"); - - var result = lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(worker); + var result = + lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(workerAboveThreshold()); assertTrue(result.isEmpty()); verify(eventProducer, never()).accept(any()); @@ -105,27 +54,23 @@ void no_alert_when_remaining_days_above_threshold() { @Test void no_alert_when_no_active_contract() { - var result = lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(worker); + var result = + lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage( + workerWithoutContract()); assertTrue(result.isEmpty()); verify(eventProducer, never()).accept(any()); } - private void saveContract(int durationInDays, String entranceInstantStr) { - var jWorker = new JWorker(); - jWorker.setCode(worker.code()); - var level = new JContractLevel(); - level.setCode("L4P-2026"); - var jContract = new JContract(); - jContract.setId(UUID.randomUUID().toString()); - jContract.setWorker(jWorker); - jContract.setLevel(level); - jContract.setEntranceInstant(Instant.parse(entranceInstantStr)); - jContract.setEndInstant(null); - jContract.setDurationInDays(durationInDays); - jContract.setJobTitle("Test Job"); - jContract.setCompany("Test Company"); - jContract.setContractBucketKey("test-key"); - jContractRepository.save(jContract); + private Worker workerBelowThreshold() { + return new Worker("alert-worker-below", "Alert Worker Below", "", "", "", "", "", ""); + } + + private Worker workerAboveThreshold() { + return new Worker("alert-worker-above", "Alert Worker Above", "", "", "", "", "", ""); + } + + private Worker workerWithoutContract() { + return new Worker("alert-worker-none", "Alert Worker None", "", "", "", "", "", ""); } } diff --git a/src/test/resources/db/migration/V101_4__Insert_low_remaining_days_alert_fixtures.sql b/src/test/resources/db/migration/V101_4__Insert_low_remaining_days_alert_fixtures.sql new file mode 100644 index 00000000..7e8911c3 --- /dev/null +++ b/src/test/resources/db/migration/V101_4__Insert_low_remaining_days_alert_fixtures.sql @@ -0,0 +1,9 @@ +insert into worker (code, name) +values ('alert-worker-below', 'Alert Worker Below'), + ('alert-worker-above', 'Alert Worker Above'), + ('alert-worker-none', 'Alert Worker None'); + +INSERT INTO contract + (id, worker_code, level, entrance_instant, job_title, duration_in_days, contract_bucket_key) +VALUES ('alert-contract-below', 'alert-worker-below', 'L4P-2026', '2027-07-01 00:00:00.000000', 'Test Job', 5, 'test-key'), + ('alert-contract-above', 'alert-worker-above', 'L4P-2026', '2027-08-01 00:00:00.000000', 'Test Job', 15, 'test-key'); From b42402d4f103f46dbe773d15ed8b493712252dc9 Mon Sep 17 00:00:00 2001 From: Tom-1747 Date: Tue, 28 Jul 2026 20:43:28 +0300 Subject: [PATCH 19/28] refactor: use double for remaining days calculations (#75) * refactor: replace long with double in remaining days handling * fix: format the project --- .../school/hei/asa/service/LowRemainingDaysAlertService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java index 66165918..3d32b266 100644 --- a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java +++ b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java @@ -60,6 +60,8 @@ private boolean isBelowThreshold(double remainingDays) { } private static String formatDays(double days) { - return days == (long) days ? String.valueOf((long) days) : String.format(US, "%.1f", days); + return days == Math.floor(days) + ? String.format(US, "%.0f", days) + : String.format(US, "%.1f", days); } } From 4dc7a55576de200c1fe2b805c5ad715ff9785bf9 Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:34:58 +0300 Subject: [PATCH 20/28] refactor: rename checkRemainingDays alert method to verify (#76) * refactor: rename checkRemainingDays alert method to verify Align naming with review feedback by renaming checkRemainingDaysAndBuildAlertMessage to verifyRemainingDaysAndBuildAlertMessage. * style: apply google-java-format on alert service IT --- .../asa/endpoint/rest/controller/CalendarController.java | 2 +- .../hei/asa/service/LowRemainingDaysAlertService.java | 2 +- .../hei/asa/service/LowRemainingDaysAlertServiceIT.java | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java index 80d99a91..2d219fd1 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java @@ -79,7 +79,7 @@ public String getCalendar( model.addAttribute("hasUsableContract", hasUsableContract); lowRemainingDaysAlertService - .checkRemainingDaysAndBuildAlertMessage(worker) + .verifyRemainingDaysAndBuildAlertMessage(worker) .ifPresent( message -> { model.addAttribute("toastMessage", message); diff --git a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java index 3d32b266..8d406c08 100644 --- a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java +++ b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java @@ -28,7 +28,7 @@ public LowRemainingDaysAlertService( this.lowRemainingDaysThreshold = lowRemainingDaysThreshold; } - public Optional checkRemainingDaysAndBuildAlertMessage(Worker worker) { + public Optional verifyRemainingDaysAndBuildAlertMessage(Worker worker) { var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); if (!isBelowThreshold(remainingDays)) { diff --git a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java index 879c149a..6ce6035a 100644 --- a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java +++ b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java @@ -22,7 +22,8 @@ class LowRemainingDaysAlertServiceIT extends FacadeIT { @Test void alert_message_built_without_sending_email_when_remaining_days_below_threshold() { var result = - lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(workerBelowThreshold()); + lowRemainingDaysAlertService.verifyRemainingDaysAndBuildAlertMessage( + workerBelowThreshold()); assertTrue(result.isPresent()); assertTrue(result.get().contains("day(s) left")); @@ -46,7 +47,8 @@ void no_alert_email_when_remaining_days_above_threshold() { @Test void no_alert_when_remaining_days_above_threshold() { var result = - lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage(workerAboveThreshold()); + lowRemainingDaysAlertService.verifyRemainingDaysAndBuildAlertMessage( + workerAboveThreshold()); assertTrue(result.isEmpty()); verify(eventProducer, never()).accept(any()); @@ -55,7 +57,7 @@ void no_alert_when_remaining_days_above_threshold() { @Test void no_alert_when_no_active_contract() { var result = - lowRemainingDaysAlertService.checkRemainingDaysAndBuildAlertMessage( + lowRemainingDaysAlertService.verifyRemainingDaysAndBuildAlertMessage( workerWithoutContract()); assertTrue(result.isEmpty()); From 18966835b1608d0fc19e7f251e75373f161551a3 Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:39:40 +0300 Subject: [PATCH 21/28] refactor: add DailyExecutionService.verifyAndSave for pointage (#77) Move save and low-remaining email trigger behind a single service method called from the controller. --- .../controller/DailyExecutionController.java | 9 +++------ .../hei/asa/service/DailyExecutionService.java | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 src/main/java/school/hei/asa/service/DailyExecutionService.java diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index a110264e..46f8dab9 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -11,17 +11,15 @@ import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; import school.hei.asa.endpoint.rest.security.WorkerFromAuthentication; import school.hei.asa.endpoint.rest.service.ThMissionService; -import school.hei.asa.repository.DailyExecutionRepository; -import school.hei.asa.service.LowRemainingDaysAlertService; +import school.hei.asa.service.DailyExecutionService; @Controller @AllArgsConstructor public class DailyExecutionController { private final ThDailyExecutionFormMapper thDailyExecutionFormMapper; - private final DailyExecutionRepository dailyExecutionRepository; + private final DailyExecutionService dailyExecutionService; private final WorkerFromAuthentication workerFromAuthentication; private final ThMissionService thMissionService; - private final LowRemainingDaysAlertService lowRemainingDaysAlertService; @GetMapping("/daily-execution") public String getDailyExecutionForm(Model model) { @@ -38,8 +36,7 @@ public String createDailyExecution( var worker = workerFromAuthentication.apply(authentication).get(); var dailyExecution = thDailyExecutionFormMapper.toDomain(dmeForm, worker); - dailyExecutionRepository.save(dailyExecution); - lowRemainingDaysAlertService.sendAlertEmailIfLowRemainingDays(worker); + dailyExecutionService.verifyAndSave(dailyExecution); return "redirect:/work-and-care-calendar"; } diff --git a/src/main/java/school/hei/asa/service/DailyExecutionService.java b/src/main/java/school/hei/asa/service/DailyExecutionService.java new file mode 100644 index 00000000..61762f37 --- /dev/null +++ b/src/main/java/school/hei/asa/service/DailyExecutionService.java @@ -0,0 +1,18 @@ +package school.hei.asa.service; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import school.hei.asa.model.DailyExecution; +import school.hei.asa.repository.DailyExecutionRepository; + +@Service +@AllArgsConstructor +public class DailyExecutionService { + private final DailyExecutionRepository dailyExecutionRepository; + private final LowRemainingDaysAlertService lowRemainingDaysAlertService; + + public void verifyAndSave(DailyExecution dailyExecution) { + dailyExecutionRepository.save(dailyExecution); + lowRemainingDaysAlertService.sendAlertEmailIfLowRemainingDays(dailyExecution.worker()); + } +} From f4f4bdc96ed7e04b271749dce7634d34abdfa62d Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:11:58 +0300 Subject: [PATCH 22/28] Move pointage IT contract fixtures to Flyway to avoid runtime team saves (#78) --- .../DailyExecutionControllerIT.java | 39 ---------------- .../rest/service/CalendarServiceIT.java | 44 ------------------- ..._5__Insert_pointage_it_worker_contract.sql | 16 +++++++ 3 files changed, 16 insertions(+), 83 deletions(-) create mode 100644 src/test/resources/db/migration/V101_5__Insert_pointage_it_worker_contract.sql diff --git a/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java b/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java index 80f1bb1b..c960553e 100644 --- a/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java +++ b/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java @@ -10,13 +10,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import jakarta.persistence.EntityManager; -import java.time.Instant; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import org.junit.jupiter.api.BeforeEach; @@ -24,8 +21,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.core.Authentication; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.support.TransactionTemplate; import org.springframework.ui.Model; import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import school.hei.asa.conf.FacadeIT; @@ -35,15 +30,10 @@ import school.hei.asa.model.Mission; import school.hei.asa.model.Product; import school.hei.asa.model.Worker; -import school.hei.asa.model.contract.ContractType; import school.hei.asa.repository.DailyExecutionRepository; import school.hei.asa.repository.MissionRepository; import school.hei.asa.repository.ProductRepository; import school.hei.asa.repository.WorkerRepository; -import school.hei.asa.repository.jrepository.JContractRepository; -import school.hei.asa.repository.mapper.WorkerMapper; -import school.hei.asa.repository.model.JContract; -import school.hei.asa.repository.model.JContractLevel; class DailyExecutionControllerIT extends FacadeIT { @@ -53,10 +43,6 @@ class DailyExecutionControllerIT extends FacadeIT { @Autowired MissionRepository missionRepository; @Autowired DailyExecutionRepository dailyExecutionRepository; @Autowired CalendarController calendarController; - @Autowired JContractRepository jContractRepository; - @Autowired WorkerMapper workerMapper; - @Autowired EntityManager entityManager; - @Autowired PlatformTransactionManager transactionManager; @MockBean SecurityConfig securityConfig; @MockBean WorkerFromAuthentication workerFromAuthentication; @@ -75,31 +61,6 @@ void setUp() { when(workerFromAuthentication.apply(authentication)) .thenReturn(Optional.of(authenticatedWorker)); - var contractLevelCode = "level-code-" + UUID.randomUUID(); - new TransactionTemplate(transactionManager) - .execute( - status -> { - var jContractLevel = new JContractLevel(); - jContractLevel.setCode(contractLevelCode); - jContractLevel.setType(ContractType.fullTimeEmployee); - jContractLevel.setMonthlyPay(1000.0); - jContractLevel.setDailyPay(50.0); - entityManager.persist(jContractLevel); - return null; - }); - - var jContract = new JContract(); - jContract.setId("contract-test-id-" + UUID.randomUUID()); - jContract.setWorker(workerMapper.toEntity(authenticatedWorker)); - jContract.setLevel(entityManager.find(JContractLevel.class, contractLevelCode)); - jContract.setEntranceInstant(Instant.parse("2010-01-01T00:00:00Z")); - jContract.setEndInstant(Instant.parse("2010-12-31T00:00:00Z")); - jContract.setJobTitle("job-title"); - jContract.setDurationInDays(180); - jContract.setCompany("company"); - jContract.setContractBucketKey("contract-bucket-key"); - jContractRepository.save(jContract); - var product = new Product("pcode", "pname", "pdescription"); productRepository.save(product); var mission1 = new Mission("mission1-code", "title1", "description1", 10, product); diff --git a/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java b/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java index 4c693718..df4bfeb1 100644 --- a/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java +++ b/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java @@ -9,19 +9,14 @@ import static school.hei.asa.model.DailyExecution.Type.fullWork; import static school.hei.asa.model.DailyExecution.Type.mixedWorkAndCare; -import jakarta.persistence.EntityManager; -import java.time.Instant; import java.time.LocalDate; import java.util.List; import java.util.Optional; -import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.core.Authentication; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.support.TransactionTemplate; import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import school.hei.asa.conf.FacadeIT; import school.hei.asa.endpoint.rest.controller.DailyExecutionController; @@ -31,14 +26,9 @@ import school.hei.asa.model.Mission; import school.hei.asa.model.Product; import school.hei.asa.model.Worker; -import school.hei.asa.model.contract.ContractType; import school.hei.asa.repository.MissionRepository; import school.hei.asa.repository.ProductRepository; import school.hei.asa.repository.WorkerRepository; -import school.hei.asa.repository.jrepository.JContractRepository; -import school.hei.asa.repository.mapper.WorkerMapper; -import school.hei.asa.repository.model.JContract; -import school.hei.asa.repository.model.JContractLevel; import school.hei.asa.service.CalendarService; class CalendarServiceIT extends FacadeIT { @@ -46,10 +36,6 @@ class CalendarServiceIT extends FacadeIT { @Autowired WorkerRepository workerRepository; @Autowired ProductRepository productRepository; @Autowired MissionRepository missionRepository; - @Autowired JContractRepository jContractRepository; - @Autowired WorkerMapper workerMapper; - @Autowired EntityManager entityManager; - @Autowired PlatformTransactionManager transactionManager; @MockBean SecurityConfig securityConfig; @MockBean WorkerFromAuthentication workerFromAuthentication; @@ -62,7 +48,6 @@ class CalendarServiceIT extends FacadeIT { @BeforeEach void setUp() { authentication = authentication(); - setUpActiveContract(); setUpProductsAndMissions(); } @@ -208,33 +193,4 @@ private void setUpProductsAndMissions() { var careMission = new Mission("careMission-code", "", "", 2, careProduct); missionRepository.saveAll(List.of(mission1, mission2, careMission)); } - - private void setUpActiveContract() { - var worker = workerRepository.findByCode(authenticatedWorkerCode); - var contractLevelCode = "level-code-" + UUID.randomUUID(); - - new TransactionTemplate(transactionManager) - .execute( - status -> { - var jContractLevel = new JContractLevel(); - jContractLevel.setCode(contractLevelCode); - jContractLevel.setType(ContractType.fullTimeEmployee); - jContractLevel.setMonthlyPay(1000.0); - jContractLevel.setDailyPay(50.0); - entityManager.persist(jContractLevel); - return null; - }); - - var jContract = new JContract(); - jContract.setId("contract-test-id-" + UUID.randomUUID()); - jContract.setWorker(workerMapper.toEntity(worker)); - jContract.setLevel(entityManager.find(JContractLevel.class, contractLevelCode)); - jContract.setEntranceInstant(Instant.parse("2010-01-01T00:00:00Z")); - jContract.setEndInstant(Instant.parse("2010-12-31T00:00:00Z")); - jContract.setJobTitle("job-title"); - jContract.setDurationInDays(180); - jContract.setCompany("company"); - jContract.setContractBucketKey("contract-bucket-key"); - jContractRepository.save(jContract); - } } diff --git a/src/test/resources/db/migration/V101_5__Insert_pointage_it_worker_contract.sql b/src/test/resources/db/migration/V101_5__Insert_pointage_it_worker_contract.sql new file mode 100644 index 00000000..7bf1f6e9 --- /dev/null +++ b/src/test/resources/db/migration/V101_5__Insert_pointage_it_worker_contract.sql @@ -0,0 +1,16 @@ +insert into worker (code, name) +values ('worker-code', 'code'); + +INSERT INTO contract + (id, worker_code, level, entrance_instant, end_instant, job_title, duration_in_days, contract_bucket_key, company) +VALUES ( + 'it-worker-code-contract', + 'worker-code', + 'L4P-2026', + '2010-01-01 00:00:00.000000', + '2010-12-31 00:00:00.000000', + 'job-title', + 180, + 'contract-bucket-key', + 'company' +); From a57abe4425bdbe739cc314e63ff59d44b15082e3 Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:26:10 +0300 Subject: [PATCH 23/28] Rename verifyAndSave to saveAndAlert for clearer pointage save flow (#79) --- .../asa/endpoint/rest/controller/DailyExecutionController.java | 2 +- src/main/java/school/hei/asa/service/DailyExecutionService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index 46f8dab9..21934dbc 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -36,7 +36,7 @@ public String createDailyExecution( var worker = workerFromAuthentication.apply(authentication).get(); var dailyExecution = thDailyExecutionFormMapper.toDomain(dmeForm, worker); - dailyExecutionService.verifyAndSave(dailyExecution); + dailyExecutionService.saveAndAlert(dailyExecution); return "redirect:/work-and-care-calendar"; } diff --git a/src/main/java/school/hei/asa/service/DailyExecutionService.java b/src/main/java/school/hei/asa/service/DailyExecutionService.java index 61762f37..ad095ee7 100644 --- a/src/main/java/school/hei/asa/service/DailyExecutionService.java +++ b/src/main/java/school/hei/asa/service/DailyExecutionService.java @@ -11,7 +11,7 @@ public class DailyExecutionService { private final DailyExecutionRepository dailyExecutionRepository; private final LowRemainingDaysAlertService lowRemainingDaysAlertService; - public void verifyAndSave(DailyExecution dailyExecution) { + public void saveAndAlert(DailyExecution dailyExecution) { dailyExecutionRepository.save(dailyExecution); lowRemainingDaysAlertService.sendAlertEmailIfLowRemainingDays(dailyExecution.worker()); } From 69ad63821a7021d09db31f45a1ce7a7670bcc14c Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:00:32 +0300 Subject: [PATCH 24/28] Show low remaining days warning banner on daily-execution GET. (#80) --- .../controller/DailyExecutionController.java | 16 ++++++++++------ .../resources/templates/daily-execution.html | 4 ++++ .../controller/DailyExecutionControllerIT.java | 16 +++++----------- .../endpoint/rest/service/CalendarServiceIT.java | 13 ++++--------- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index 21934dbc..85ebf285 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -6,12 +6,12 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; import school.hei.asa.endpoint.rest.controller.mapper.ThDailyExecutionFormMapper; import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; import school.hei.asa.endpoint.rest.security.WorkerFromAuthentication; import school.hei.asa.endpoint.rest.service.ThMissionService; import school.hei.asa.service.DailyExecutionService; +import school.hei.asa.service.LowRemainingDaysAlertService; @Controller @AllArgsConstructor @@ -20,19 +20,23 @@ public class DailyExecutionController { private final DailyExecutionService dailyExecutionService; private final WorkerFromAuthentication workerFromAuthentication; private final ThMissionService thMissionService; + private final LowRemainingDaysAlertService lowRemainingDaysAlertService; @GetMapping("/daily-execution") - public String getDailyExecutionForm(Model model) { + public String getDailyExecutionForm(Model model, Authentication authentication) { + var worker = workerFromAuthentication.apply(authentication).get(); var sortedMissions = thMissionService.sortedMissionsWithoutMissionExecution(); model.addAttribute("missions", sortedMissions); + + lowRemainingDaysAlertService + .verifyRemainingDaysAndBuildAlertMessage(worker) + .ifPresent(message -> model.addAttribute("warningBannerMessage", message)); + return "daily-execution"; } @PostMapping("/daily-execution") - public String createDailyExecution( - Authentication authentication, - ThDailyExecutionForm dmeForm, - RedirectAttributes redirectAttributes) { + public String createDailyExecution(Authentication authentication, ThDailyExecutionForm dmeForm) { var worker = workerFromAuthentication.apply(authentication).get(); var dailyExecution = thDailyExecutionFormMapper.toDomain(dmeForm, worker); diff --git a/src/main/resources/templates/daily-execution.html b/src/main/resources/templates/daily-execution.html index 8b9eab4e..5f34ef57 100644 --- a/src/main/resources/templates/daily-execution.html +++ b/src/main/resources/templates/daily-execution.html @@ -56,6 +56,10 @@
+
+ +
+

What did you do that day?

diff --git a/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java b/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java index c960553e..ea8c86bc 100644 --- a/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java +++ b/src/test/java/school/hei/asa/endpoint/rest/controller/DailyExecutionControllerIT.java @@ -22,7 +22,6 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.core.Authentication; import org.springframework.ui.Model; -import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import school.hei.asa.conf.FacadeIT; import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; import school.hei.asa.endpoint.rest.security.SecurityConfig; @@ -90,8 +89,7 @@ void save_then_read_with_duplicates_ok_if_sum_of_set_is_100() { null, null); - dailyExecutionController.createDailyExecution( - authentication, dmeForm, new RedirectAttributesModelMap()); + dailyExecutionController.createDailyExecution(authentication, dmeForm); var savedWorker = workerRepository.findByCode(authenticatedWorker.code()); var dailyExecutions = @@ -130,13 +128,10 @@ void cannot_save_if_mission_execution_already_exists() { null, null); - dailyExecutionController.createDailyExecution( - authentication, dmeForm, new RedirectAttributesModelMap()); + dailyExecutionController.createDailyExecution(authentication, dmeForm); assertThrows( Exception.class, - () -> - dailyExecutionController.createDailyExecution( - authentication, dmeForm, new RedirectAttributesModelMap())); + () -> dailyExecutionController.createDailyExecution(authentication, dmeForm)); } @Test @@ -177,8 +172,7 @@ void concurrently_create_daily_execution() { () -> { try { latch.await(); - return dailyExecutionController.createDailyExecution( - authentication, dmeForm, new RedirectAttributesModelMap()); + return dailyExecutionController.createDailyExecution(authentication, dmeForm); } catch (Exception e) { return e.getMessage(); } @@ -199,7 +193,7 @@ void concurrently_create_daily_execution() { @Test void can_get_daily_execution_form() { - var viewName = dailyExecutionController.getDailyExecutionForm(model); + var viewName = dailyExecutionController.getDailyExecutionForm(model, authentication); verify(model).addAttribute(eq("missions"), any(List.class)); diff --git a/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java b/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java index df4bfeb1..34db5066 100644 --- a/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java +++ b/src/test/java/school/hei/asa/endpoint/rest/service/CalendarServiceIT.java @@ -17,7 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.core.Authentication; -import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; import school.hei.asa.conf.FacadeIT; import school.hei.asa.endpoint.rest.controller.DailyExecutionController; import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; @@ -71,8 +70,7 @@ void datesByDailyExecution_by_fullWork() { null, null, null, - null), - new RedirectAttributesModelMap()); + null)); var worker = workerRepository.findByCode(authenticatedWorkerCode); var datesByDailyExecutionType = calendarService.datesByDailyExecutionType(worker, 2024); @@ -104,8 +102,7 @@ void datesByDailyExecution_by_fullCare() { null, null, null, - null), - new RedirectAttributesModelMap()); + null)); dailyExecutionController.createDailyExecution( authentication, new ThDailyExecutionForm( @@ -124,8 +121,7 @@ void datesByDailyExecution_by_fullCare() { null, null, null, - null), - new RedirectAttributesModelMap()); + null)); var worker = workerRepository.findByCode(authenticatedWorkerCode); var datesByDailyExecutionType = calendarService.datesByDailyExecutionType(worker, 2025); @@ -154,8 +150,7 @@ void datesByDailyExecution_by_mixedWorkAndCare() { null, null, null, - null), - new RedirectAttributesModelMap()); + null)); var worker = workerRepository.findByCode(authenticatedWorkerCode); var datesByDailyExecutionType = calendarService.datesByDailyExecutionType(worker, 2024); From 686960630b9dfd0b539c31241dd9e6d43b826d31 Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:19:48 +0300 Subject: [PATCH 25/28] Mirror calendar contract banners on daily-execution GET (#81) * Show low remaining days warning banner on daily-execution GET. * Mirror calendar contract banners on daily-execution GET. --- .../endpoint/rest/controller/DailyExecutionController.java | 7 +++++++ src/main/resources/templates/daily-execution.html | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index 85ebf285..7068f7a8 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -10,6 +10,7 @@ import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; import school.hei.asa.endpoint.rest.security.WorkerFromAuthentication; import school.hei.asa.endpoint.rest.service.ThMissionService; +import school.hei.asa.service.ContractService; import school.hei.asa.service.DailyExecutionService; import school.hei.asa.service.LowRemainingDaysAlertService; @@ -21,6 +22,7 @@ public class DailyExecutionController { private final WorkerFromAuthentication workerFromAuthentication; private final ThMissionService thMissionService; private final LowRemainingDaysAlertService lowRemainingDaysAlertService; + private final ContractService contractService; @GetMapping("/daily-execution") public String getDailyExecutionForm(Model model, Authentication authentication) { @@ -28,6 +30,11 @@ public String getDailyExecutionForm(Model model, Authentication authentication) var sortedMissions = thMissionService.sortedMissionsWithoutMissionExecution(); model.addAttribute("missions", sortedMissions); + var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); + var hasUsableContract = + contractService.findActiveContractByWorker(worker).isPresent() && remainingDays > 0; + model.addAttribute("hasUsableContract", hasUsableContract); + lowRemainingDaysAlertService .verifyRemainingDaysAndBuildAlertMessage(worker) .ifPresent(message -> model.addAttribute("warningBannerMessage", message)); diff --git a/src/main/resources/templates/daily-execution.html b/src/main/resources/templates/daily-execution.html index 5f34ef57..d3a1fbeb 100644 --- a/src/main/resources/templates/daily-execution.html +++ b/src/main/resources/templates/daily-execution.html @@ -56,6 +56,10 @@
+
+ Please note : You do not have an active contract. Please contact your administrator. +
+
From a75122ad0cef6f6ea80860d91c3e9123ee07d1bf Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Wed, 29 Jul 2026 00:12:16 +0300 Subject: [PATCH 26/28] Unify contract warning into one banner with conditional color (#82) * Unify contract warning into one banner with conditional color. * Format DailyExecutionController for google-java-format. * Drop hasUsableContract; set banner color from message in the template. --- .../rest/controller/CalendarController.java | 22 +++++++------------ .../controller/DailyExecutionController.java | 13 +++-------- .../service/LowRemainingDaysAlertService.java | 5 +++++ src/main/resources/templates/calendar.html | 11 +++++----- .../resources/templates/daily-execution.html | 11 +++++----- .../LowRemainingDaysAlertServiceIT.java | 5 +++-- 6 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java index 2d219fd1..6fd89c97 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java @@ -72,21 +72,15 @@ public String getCalendar( var lateReportedDaysByMonth = calendarService.lateReportedDaysByMonth(worker, year); var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); - var hasUsableContract = - contractService.findActiveContractByWorker(worker).isPresent() && remainingDays > 0; - - model.addAttribute("remainingDays", hasUsableContract ? remainingDays : null); - model.addAttribute("hasUsableContract", hasUsableContract); - - lowRemainingDaysAlertService - .verifyRemainingDaysAndBuildAlertMessage(worker) - .ifPresent( - message -> { - model.addAttribute("toastMessage", message); - model.addAttribute("toastType", "warning"); - model.addAttribute("warningBannerMessage", message); - }); + model.addAttribute("remainingDays", remainingDays > 0 ? remainingDays : null); + var warningBannerMessage = + lowRemainingDaysAlertService.verifyRemainingDaysAndBuildAlertMessage(worker).orElse(null); + model.addAttribute("warningBannerMessage", warningBannerMessage); + if (remainingDays > 0 && warningBannerMessage != null) { + model.addAttribute("toastMessage", warningBannerMessage); + model.addAttribute("toastType", "warning"); + } model.addAttribute("workerCode", workerCodeOrAuth); model.addAttribute("currentYear", now().getYear()); model.addAttribute( diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index 7068f7a8..d4fb28e0 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -10,7 +10,6 @@ import school.hei.asa.endpoint.rest.model.th.ThDailyExecutionForm; import school.hei.asa.endpoint.rest.security.WorkerFromAuthentication; import school.hei.asa.endpoint.rest.service.ThMissionService; -import school.hei.asa.service.ContractService; import school.hei.asa.service.DailyExecutionService; import school.hei.asa.service.LowRemainingDaysAlertService; @@ -22,7 +21,6 @@ public class DailyExecutionController { private final WorkerFromAuthentication workerFromAuthentication; private final ThMissionService thMissionService; private final LowRemainingDaysAlertService lowRemainingDaysAlertService; - private final ContractService contractService; @GetMapping("/daily-execution") public String getDailyExecutionForm(Model model, Authentication authentication) { @@ -30,14 +28,9 @@ public String getDailyExecutionForm(Model model, Authentication authentication) var sortedMissions = thMissionService.sortedMissionsWithoutMissionExecution(); model.addAttribute("missions", sortedMissions); - var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); - var hasUsableContract = - contractService.findActiveContractByWorker(worker).isPresent() && remainingDays > 0; - model.addAttribute("hasUsableContract", hasUsableContract); - - lowRemainingDaysAlertService - .verifyRemainingDaysAndBuildAlertMessage(worker) - .ifPresent(message -> model.addAttribute("warningBannerMessage", message)); + var warningBannerMessage = + lowRemainingDaysAlertService.verifyRemainingDaysAndBuildAlertMessage(worker).orElse(null); + model.addAttribute("warningBannerMessage", warningBannerMessage); return "daily-execution"; } diff --git a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java index 8d406c08..4eaa335f 100644 --- a/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java +++ b/src/main/java/school/hei/asa/service/LowRemainingDaysAlertService.java @@ -31,6 +31,11 @@ public LowRemainingDaysAlertService( public Optional verifyRemainingDaysAndBuildAlertMessage(Worker worker) { var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); + if (remainingDays <= 0) { + return Optional.of( + "Please note : You do not have an active contract. Please contact your administrator."); + } + if (!isBelowThreshold(remainingDays)) { return Optional.empty(); } diff --git a/src/main/resources/templates/calendar.html b/src/main/resources/templates/calendar.html index b8e76258..d34b94d3 100644 --- a/src/main/resources/templates/calendar.html +++ b/src/main/resources/templates/calendar.html @@ -12,12 +12,11 @@
-
- Please note : You do not have an active contract. Please contact your administrator. -
- -
- +
diff --git a/src/main/resources/templates/daily-execution.html b/src/main/resources/templates/daily-execution.html index d3a1fbeb..a59a52d7 100644 --- a/src/main/resources/templates/daily-execution.html +++ b/src/main/resources/templates/daily-execution.html @@ -56,12 +56,11 @@
-
- Please note : You do not have an active contract. Please contact your administrator. -
- -
- +
diff --git a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java index 6ce6035a..55064d99 100644 --- a/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java +++ b/src/test/java/school/hei/asa/service/LowRemainingDaysAlertServiceIT.java @@ -55,12 +55,13 @@ void no_alert_when_remaining_days_above_threshold() { } @Test - void no_alert_when_no_active_contract() { + void alert_message_when_no_active_contract() { var result = lowRemainingDaysAlertService.verifyRemainingDaysAndBuildAlertMessage( workerWithoutContract()); - assertTrue(result.isEmpty()); + assertTrue(result.isPresent()); + assertTrue(result.get().contains("do not have an active contract")); verify(eventProducer, never()).accept(any()); } From e65f003a6a57bba3874dfe0993487c292449b9fa Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Wed, 29 Jul 2026 00:54:04 +0300 Subject: [PATCH 27/28] Simplify calendar alert to message-only banner and remove toast. (#83) --- .../rest/controller/CalendarController.java | 13 ++------ .../controller/DailyExecutionController.java | 4 +-- src/main/resources/templates/calendar.html | 6 ---- .../resources/templates/fragments/toast.html | 30 ------------------- 4 files changed, 4 insertions(+), 49 deletions(-) delete mode 100644 src/main/resources/templates/fragments/toast.html diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java index 6fd89c97..e3cc5b2f 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/CalendarController.java @@ -27,7 +27,6 @@ import school.hei.asa.model.Mission; import school.hei.asa.model.Worker; import school.hei.asa.service.CalendarService; -import school.hei.asa.service.ContractService; import school.hei.asa.service.LowRemainingDaysAlertService; @AllArgsConstructor @@ -37,7 +36,6 @@ public class CalendarController { private final CalendarService calendarService; private final WorkerFromAuthentication workerFromAuthentication; private final WorkerToModelAdder workerToModelAdder; - private final ContractService contractService; private final LowRemainingDaysAlertService lowRemainingDaysAlertService; @GetMapping("/work-and-care-calendar") @@ -47,7 +45,6 @@ public String getCalendar( @RequestParam(required = false) String workerCode, @RequestParam(required = false) Integer year) { year = year == null ? now().getYear() : year; - model.addAttribute("year", year); var workerCodeOrAuth = workerCode == null || workerCode.isBlank() @@ -70,17 +67,11 @@ public String getCalendar( missionCounts.put(month, typeCounts); }); var lateReportedDaysByMonth = calendarService.lateReportedDaysByMonth(worker, year); - - var remainingDays = contractService.getRemainingDaysOnActiveContractOrZero(worker); - model.addAttribute("remainingDays", remainingDays > 0 ? remainingDays : null); - var warningBannerMessage = lowRemainingDaysAlertService.verifyRemainingDaysAndBuildAlertMessage(worker).orElse(null); + + model.addAttribute("year", year); model.addAttribute("warningBannerMessage", warningBannerMessage); - if (remainingDays > 0 && warningBannerMessage != null) { - model.addAttribute("toastMessage", warningBannerMessage); - model.addAttribute("toastType", "warning"); - } model.addAttribute("workerCode", workerCodeOrAuth); model.addAttribute("currentYear", now().getYear()); model.addAttribute( diff --git a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java index d4fb28e0..05ae6dea 100644 --- a/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java +++ b/src/main/java/school/hei/asa/endpoint/rest/controller/DailyExecutionController.java @@ -26,10 +26,10 @@ public class DailyExecutionController { public String getDailyExecutionForm(Model model, Authentication authentication) { var worker = workerFromAuthentication.apply(authentication).get(); var sortedMissions = thMissionService.sortedMissionsWithoutMissionExecution(); - model.addAttribute("missions", sortedMissions); - var warningBannerMessage = lowRemainingDaysAlertService.verifyRemainingDaysAndBuildAlertMessage(worker).orElse(null); + + model.addAttribute("missions", sortedMissions); model.addAttribute("warningBannerMessage", warningBannerMessage); return "daily-execution"; diff --git a/src/main/resources/templates/calendar.html b/src/main/resources/templates/calendar.html index d34b94d3..787c0b0a 100644 --- a/src/main/resources/templates/calendar.html +++ b/src/main/resources/templates/calendar.html @@ -10,7 +10,6 @@
-
-
- - -
diff --git a/src/main/resources/templates/fragments/toast.html b/src/main/resources/templates/fragments/toast.html deleted file mode 100644 index 2edc8647..00000000 --- a/src/main/resources/templates/fragments/toast.html +++ /dev/null @@ -1,30 +0,0 @@ - - - -
- - - -
- - From 8777251271b5752bf72a213fc763bf55ea032a0a Mon Sep 17 00:00:00 2001 From: yoandiny <63061998+yoandiny@users.noreply.github.com> Date: Wed, 29 Jul 2026 01:21:17 +0300 Subject: [PATCH 28/28] Remove unnecessary empty accountants check per review. (#84) --- .../service/event/LowRemainingDaysAlertRequestedService.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java b/src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java index e790186c..d7a07fee 100644 --- a/src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java +++ b/src/main/java/school/hei/asa/service/event/LowRemainingDaysAlertRequestedService.java @@ -35,11 +35,6 @@ public void accept(LowRemainingDaysAlertRequested event) { internetAddressMapper.toInternetAddresses( Arrays.stream(this.accountants.split(",")).toList()); - if (accountantAddresses.isEmpty()) { - log.info("No accountant address found. Skipping alert email."); - return; - } - var workerCode = event.getWorkerCode(); var remainingDays = event.getRemainingDays(); var subject = String.format("ASA - ALERT: low remaining days - Worker %s", workerCode);