diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/application/ApplicationStateServiceIntegrationTests.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/application/ApplicationStateServiceIntegrationTests.kt index c02ef8e661a..87037e4bf14 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/application/ApplicationStateServiceIntegrationTests.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/application/ApplicationStateServiceIntegrationTests.kt @@ -6,6 +6,8 @@ package fi.espoo.evaka.application import fi.espoo.evaka.FullApplicationTest import fi.espoo.evaka.application.notes.getApplicationNotes +import fi.espoo.evaka.application.notes.getServiceWorkerApplicationNote +import fi.espoo.evaka.application.notes.updateServiceWorkerApplicationNote import fi.espoo.evaka.attachment.AttachmentType import fi.espoo.evaka.daycare.getChild import fi.espoo.evaka.decision.Decision @@ -79,6 +81,9 @@ import fi.espoo.evaka.vtjclient.service.persondetails.MockPersonDetailsService import fi.espoo.evaka.vtjclient.service.persondetails.legacyMockVtjDataset import java.time.LocalDate import java.time.LocalTime +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle +import java.util.Locale import java.util.UUID import kotlin.enums.enumEntries import kotlin.test.assertEquals @@ -87,7 +92,6 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.groups.Tuple import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -1509,8 +1513,9 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor } @Test - fun `confirmPlacementProposalChanges - reject reason is copied to application notes`() { + fun `confirmPlacementProposalChanges - reject reason is copied to service worker's application notes`() { val rejectReason = "päiväkoti täynnä" + val previousNoteContent = "Aiempi muistilapun sisältö." db.transaction { tx -> // given tx.insertApplication( @@ -1534,6 +1539,7 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor ), ) service.sendPlacementProposal(tx, serviceWorker, clock, applicationId) + tx.updateServiceWorkerApplicationNote(applicationId, previousNoteContent) } db.transaction { tx -> // when @@ -1559,16 +1565,18 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor val application = tx.fetchApplicationDetails(applicationId)!! assertEquals(ApplicationStatus.WAITING_UNIT_CONFIRMATION, application.status) - val notes = tx.getApplicationNotes(applicationId) - assertThat(notes) - .extracting({ it.applicationId }, { it.content }, { it.createdBy }) - .containsExactly( - Tuple( - applicationId, - "Sijoitusehdotus hylätty (${testDaycare.name}) - $rejectReason", - serviceWorker.evakaUserId, + val dateTimeString = + clock + .now() + .toZonedDateTime() + .format( + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) + .withLocale(Locale.of("fi", "FI")) ) - ) + val expectedNote = + "$previousNoteContent\nSijoitusehdotus hylätty (${testDaycare.name}) - $rejectReason - ${testDecisionMaker_1.firstName} ${testDecisionMaker_1.lastName} $dateTimeString" + val note = tx.getServiceWorkerApplicationNote(applicationId) + assertEquals(expectedNote, note) val decisionsByApplication = tx.getDecisionsByApplication(applicationId, AccessControlFilter.PermitAll) @@ -1583,8 +1591,9 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor } @Test - fun `confirmPlacementProposalChanges - reject other reason is copied to application notes`() { + fun `confirmPlacementProposalChanges - reject other reason is copied to service worker's application notes`() { val rejectReason = "päiväkoti täynnä" + val previousNoteContent = "Aiempi muistilapun sisältö." db.transaction { tx -> // given tx.insertApplication( @@ -1608,6 +1617,7 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor ), ) service.sendPlacementProposal(tx, serviceWorker, clock, applicationId) + tx.updateServiceWorkerApplicationNote(applicationId, previousNoteContent) } db.transaction { tx -> // when @@ -1634,16 +1644,19 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor val application = tx.fetchApplicationDetails(applicationId)!! assertEquals(ApplicationStatus.WAITING_UNIT_CONFIRMATION, application.status) - val notes = tx.getApplicationNotes(applicationId) - assertThat(notes) - .extracting({ it.applicationId }, { it.content }, { it.createdBy }) - .containsExactly( - Tuple( - applicationId, - "Sijoitusehdotus hylätty (${testDaycare.name}) - Muu syy: $rejectReason", - serviceWorker.evakaUserId, + val dateTimeString = + clock + .now() + .toZonedDateTime() + .format( + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) + .withLocale(Locale.of("fi", "FI")) ) - ) + + val expectedNote = + "$previousNoteContent\nSijoitusehdotus hylätty (${testDaycare.name}) - Muu syy: $rejectReason - ${testDecisionMaker_1.firstName} ${testDecisionMaker_1.lastName} $dateTimeString" + val note = tx.getServiceWorkerApplicationNote(applicationId) + assertEquals(expectedNote, note) val decisionsByApplication = tx.getDecisionsByApplication(applicationId, AccessControlFilter.PermitAll) diff --git a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt index 6af54d7ba08..980c2536e03 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt @@ -16,7 +16,8 @@ import fi.espoo.evaka.application.ApplicationStatus.WAITING_DECISION import fi.espoo.evaka.application.ApplicationStatus.WAITING_MAILING import fi.espoo.evaka.application.ApplicationStatus.WAITING_PLACEMENT import fi.espoo.evaka.application.ApplicationStatus.WAITING_UNIT_CONFIRMATION -import fi.espoo.evaka.application.notes.createApplicationNote +import fi.espoo.evaka.application.notes.getServiceWorkerApplicationNote +import fi.espoo.evaka.application.notes.updateServiceWorkerApplicationNote import fi.espoo.evaka.application.persistence.club.ClubFormV0 import fi.espoo.evaka.application.persistence.daycare.DaycareFormV0 import fi.espoo.evaka.attachment.AttachmentType @@ -47,6 +48,7 @@ import fi.espoo.evaka.messaging.MessageService import fi.espoo.evaka.messaging.MessageType import fi.espoo.evaka.messaging.NewMessageStub import fi.espoo.evaka.messaging.getServiceWorkerAccountId +import fi.espoo.evaka.pis.getEmployeeUser import fi.espoo.evaka.pis.getPersonById import fi.espoo.evaka.pis.service.PersonDTO import fi.espoo.evaka.pis.service.PersonService @@ -87,6 +89,9 @@ import fi.espoo.evaka.shared.security.AccessControl import fi.espoo.evaka.shared.security.Action import fi.espoo.evaka.shared.security.actionrule.AccessControlFilter import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle +import java.util.Locale import org.springframework.stereotype.Service @Service @@ -692,7 +697,7 @@ class ApplicationStateService( fun confirmPlacementProposalChanges( tx: Database.Transaction, - user: AuthenticatedUser, + user: AuthenticatedUser.Employee, clock: EvakaClock, unitId: DaycareId, rejectReasonTranslations: Map, @@ -706,6 +711,11 @@ class ApplicationStateService( ) val unit = tx.getDaycare(unitId) ?: throw NotFound("Unit $unitId not found") + val userInfo = tx.getEmployeeUser(user.id) ?: throw IllegalStateException("User not found") + val formatter = + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) + .withLocale(Locale.of("fi", "FI")) + data class PlacementPlanReject( val applicationId: ApplicationId, val unitRejectReason: PlacementPlanRejectReason, @@ -733,10 +743,17 @@ class ApplicationStateService( placementPlan.unitRejectReason == PlacementPlanRejectReason.OTHER && it.isNotBlank() } - "Sijoitusehdotus hylätty (${unit.name}) - $translation${if (otherReason != null) ": $otherReason" else ""}" + val modifierStamp = + " - ${userInfo.firstName} ${userInfo.lastName} ${(clock.now().toZonedDateTime().format(formatter))}" + "Sijoitusehdotus hylätty (${unit.name}) - $translation${if (otherReason != null) ": $otherReason" else ""}$modifierStamp" } if (reason != null) { - tx.createApplicationNote(placementPlan.applicationId, reason, user.evakaUserId) + val currentNote = + tx.getServiceWorkerApplicationNote(placementPlan.applicationId) + tx.updateServiceWorkerApplicationNote( + placementPlan.applicationId, + "${if (currentNote.isNotEmpty()) "$currentNote\n" else ""}$reason", + ) } } diff --git a/service/src/main/kotlin/fi/espoo/evaka/application/notes/ApplicationNoteQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/application/notes/ApplicationNoteQueries.kt index 0620c2d12cb..6fab280a326 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/notes/ApplicationNoteQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/notes/ApplicationNoteQueries.kt @@ -100,6 +100,12 @@ FROM updated_note n } .exactlyOne() +fun Database.Read.getServiceWorkerApplicationNote(id: ApplicationId) = + createQuery { + sql("SELECT application.service_worker_note FROM application WHERE id = ${bind(id)}") + } + .exactlyOne() + fun Database.Transaction.updateServiceWorkerApplicationNote(id: ApplicationId, content: String) = createUpdate { sql(