Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lisätään sijoitusehdotuksen hylkäyksen syy hakemuksen muistiinpanoihin #6000

Merged
merged 5 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,12 @@ export default React.memo(function PlacementProposals({
<MutateButton
data-qa="placement-proposals-accept-button"
mutation={acceptPlacementProposalMutation}
onClick={() => ({ unitId })}
onClick={() => ({
unitId,
body: {
rejectReasons: i18n.unit.placementProposals.rejectReasons
}
})}
onSuccess={() => undefined}
onFailure={onAcceptFailure}
disabled={acceptDisabled}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// GENERATED FILE: no manual modifications

import { AcceptDecisionRequest } from 'lib-common/generated/api-types/application'
import { AcceptPlacementProposalRequest } from 'lib-common/generated/api-types/application'
import { ApplicationNote } from 'lib-common/generated/api-types/application'
import { ApplicationNoteResponse } from 'lib-common/generated/api-types/application'
import { ApplicationResponse } from 'lib-common/generated/api-types/application'
Expand Down Expand Up @@ -62,12 +63,14 @@ export async function acceptDecision(
*/
export async function acceptPlacementProposal(
request: {
unitId: UUID
unitId: UUID,
body: AcceptPlacementProposalRequest
}
): Promise<void> {
const { data: json } = await client.request<JsonOf<void>>({
url: uri`/employee/applications/placement-proposals/${request.unitId}/accept`.toString(),
method: 'POST'
method: 'POST',
data: request.body satisfies JsonCompatible<AcceptPlacementProposalRequest>
})
return json
}
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/lib-common/generated/api-types/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ export interface AcceptDecisionRequest {
requestedStartDate: LocalDate
}

/**
* Generated from fi.espoo.evaka.application.AcceptPlacementProposalRequest
*/
export interface AcceptPlacementProposalRequest {
rejectReasons: Record<PlacementPlanRejectReason, string>
}

/**
* Generated from fi.espoo.evaka.application.Address
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package fi.espoo.evaka.application

import fi.espoo.evaka.FullApplicationTest
import fi.espoo.evaka.application.notes.getApplicationNotes
import fi.espoo.evaka.attachment.AttachmentType
import fi.espoo.evaka.daycare.getChild
import fi.espoo.evaka.decision.Decision
Expand All @@ -22,6 +23,7 @@ import fi.espoo.evaka.pis.getPersonById
import fi.espoo.evaka.pis.service.insertGuardian
import fi.espoo.evaka.placement.PlacementPlan
import fi.espoo.evaka.placement.PlacementPlanConfirmationStatus
import fi.espoo.evaka.placement.PlacementPlanRejectReason
import fi.espoo.evaka.placement.PlacementType
import fi.espoo.evaka.placement.getPlacementPlan
import fi.espoo.evaka.placement.getPlacementsForChild
Expand Down Expand Up @@ -78,12 +80,14 @@ import fi.espoo.evaka.vtjclient.service.persondetails.legacyMockVtjDataset
import java.time.LocalDate
import java.time.LocalTime
import java.util.UUID
import kotlin.enums.enumEntries
import kotlin.test.assertEquals
import kotlin.test.assertFalse
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
Expand Down Expand Up @@ -1388,14 +1392,24 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor
applicationId,
PlacementPlanConfirmationStatus.ACCEPTED,
)
service.confirmPlacementProposalChanges(tx, serviceWorker, clock, testDaycare.id)
service.confirmPlacementProposalChanges(
tx,
serviceWorker,
clock,
testDaycare.id,
rejectReasons =
enumEntries<PlacementPlanRejectReason>().associateBy({ it }, { it.name }),
)
}
asyncJobRunner.runPendingJobsSync(clock)
db.read { tx ->
// then
val application = tx.fetchApplicationDetails(applicationId)!!
assertEquals(ApplicationStatus.WAITING_CONFIRMATION, application.status)

val notes = tx.getApplicationNotes(applicationId)
assertEquals(emptyList(), notes)

val decisionsByApplication =
tx.getDecisionsByApplication(applicationId, AccessControlFilter.PermitAll)
assertEquals(2, decisionsByApplication.size)
Expand Down Expand Up @@ -1455,14 +1469,24 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor
applicationId,
PlacementPlanConfirmationStatus.ACCEPTED,
)
service.confirmPlacementProposalChanges(tx, serviceWorker, clock, testDaycare.id)
service.confirmPlacementProposalChanges(
tx,
serviceWorker,
clock,
testDaycare.id,
rejectReasons =
enumEntries<PlacementPlanRejectReason>().associateBy({ it }, { it.name }),
)
}
asyncJobRunner.runPendingJobsSync(clock)
db.read { tx ->
// then
val application = tx.fetchApplicationDetails(applicationId)!!
assertEquals(ApplicationStatus.WAITING_UNIT_CONFIRMATION, application.status)

val notes = tx.getApplicationNotes(applicationId)
assertEquals(emptyList(), notes)

val decisionDrafts = tx.fetchDecisionDrafts(applicationId)
assertEquals(2, decisionDrafts.size)

Expand All @@ -1484,6 +1508,143 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor
}
}

@Test
fun `confirmPlacementProposalChanges - reject reason is copied to application notes`() {
val rejectReason = "päiväkoti täynnä"
db.transaction { tx ->
// given
tx.insertApplication(
appliedType = PlacementType.PRESCHOOL_DAYCARE,
child = testChild_2,
guardian = testAdult_1,
applicationId = applicationId,
preferredStartDate = LocalDate.of(2020, 8, 1),
)
service.sendApplication(tx, serviceWorker, clock, applicationId)
service.moveToWaitingPlacement(tx, serviceWorker, clock, applicationId)
service.createPlacementPlan(
tx,
serviceWorker,
clock,
applicationId,
DaycarePlacementPlan(
unitId = testDaycare.id,
period = mainPeriod,
preschoolDaycarePeriod = connectedPeriod,
),
)
service.sendPlacementProposal(tx, serviceWorker, clock, applicationId)
}
db.transaction { tx ->
// when
service.respondToPlacementProposal(
tx,
serviceWorker,
clock,
applicationId,
PlacementPlanConfirmationStatus.REJECTED_NOT_CONFIRMED,
PlacementPlanRejectReason.REASON_1,
)
service.confirmPlacementProposalChanges(
tx,
serviceWorker,
clock,
testDaycare.id,
rejectReasons = mapOf(PlacementPlanRejectReason.REASON_1 to rejectReason),
)
}
asyncJobRunner.runPendingJobsSync(clock)
db.read { tx ->
// then
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, rejectReason, serviceWorker.evakaUserId))

val decisionsByApplication =
tx.getDecisionsByApplication(applicationId, AccessControlFilter.PermitAll)
assertEquals(2, decisionsByApplication.size)
decisionsByApplication.forEach { decision ->
assertNull(decision.sentDate)
assertNull(decision.documentKey)
}
val messages = MockSfiMessagesClient.getMessages()
assertEquals(0, messages.size)
}
}

@Test
fun `confirmPlacementProposalChanges - reject other reason is copied to application notes`() {
val rejectReason = "päiväkoti täynnä"
db.transaction { tx ->
// given
tx.insertApplication(
appliedType = PlacementType.PRESCHOOL_DAYCARE,
child = testChild_2,
guardian = testAdult_1,
applicationId = applicationId,
preferredStartDate = LocalDate.of(2020, 8, 1),
)
service.sendApplication(tx, serviceWorker, clock, applicationId)
service.moveToWaitingPlacement(tx, serviceWorker, clock, applicationId)
service.createPlacementPlan(
tx,
serviceWorker,
clock,
applicationId,
DaycarePlacementPlan(
unitId = testDaycare.id,
period = mainPeriod,
preschoolDaycarePeriod = connectedPeriod,
),
)
service.sendPlacementProposal(tx, serviceWorker, clock, applicationId)
}
db.transaction { tx ->
// when
service.respondToPlacementProposal(
tx,
serviceWorker,
clock,
applicationId,
PlacementPlanConfirmationStatus.REJECTED_NOT_CONFIRMED,
PlacementPlanRejectReason.OTHER,
rejectReason,
)
service.confirmPlacementProposalChanges(
tx,
serviceWorker,
clock,
testDaycare.id,
rejectReasons = emptyMap(),
)
}
asyncJobRunner.runPendingJobsSync(clock)
db.read { tx ->
// then
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, rejectReason, serviceWorker.evakaUserId))

val decisionsByApplication =
tx.getDecisionsByApplication(applicationId, AccessControlFilter.PermitAll)
assertEquals(2, decisionsByApplication.size)
decisionsByApplication.forEach { decision ->
assertNull(decision.sentDate)
assertNull(decision.documentKey)
}
val messages = MockSfiMessagesClient.getMessages()
assertEquals(0, messages.size)
}
}

@Test
fun `acceptPlacementProposal - if partner already has a fridge family, child is added to that`() {
val partnershipId = PartnershipId(UUID.randomUUID())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -528,10 +528,17 @@ class ApplicationControllerV2(
user: AuthenticatedUser.Employee,
clock: EvakaClock,
@PathVariable unitId: DaycareId,
@RequestBody body: AcceptPlacementProposalRequest,
) {
db.connect { dbc ->
dbc.transaction {
applicationStateService.confirmPlacementProposalChanges(it, user, clock, unitId)
applicationStateService.confirmPlacementProposalChanges(
it,
user,
clock,
unitId,
body.rejectReasons,
)
}
}
}
Expand Down Expand Up @@ -786,6 +793,10 @@ data class PlacementProposalConfirmationUpdate(
val otherReason: String?,
)

data class AcceptPlacementProposalRequest(
val rejectReasons: Map<PlacementPlanRejectReason, String>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tämän voisi nimetä uudelleen:

Suggested change
val rejectReasons: Map<PlacementPlanRejectReason, String>
val rejectReasonTranslations: Map<PlacementPlanRejectReason, String>

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

)

data class DaycarePlacementPlan(
val unitId: DaycareId,
val period: FiniteDateRange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ 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.persistence.club.ClubFormV0
import fi.espoo.evaka.application.persistence.daycare.DaycareFormV0
import fi.espoo.evaka.attachment.AttachmentType
Expand Down Expand Up @@ -694,6 +695,7 @@ class ApplicationStateService(
user: AuthenticatedUser,
clock: EvakaClock,
unitId: DaycareId,
rejectReasons: Map<PlacementPlanRejectReason, String>,
) {
accessControl.requirePermissionFor(
tx,
Expand All @@ -703,17 +705,35 @@ class ApplicationStateService(
unitId,
)

tx.execute {
sql(
"""
data class PlacementPlanReject(
val applicationId: ApplicationId,
val unitRejectReason: PlacementPlanRejectReason,
val unitRejectOtherReason: String?,
)
tx.createUpdate {
sql(
"""
UPDATE placement_plan
SET unit_confirmation_status = 'REJECTED'
WHERE
unit_id = ${bind(unitId)} AND
unit_confirmation_status = 'REJECTED_NOT_CONFIRMED'
RETURNING application_id, unit_reject_reason, unit_reject_other_reason
"""
)
}
)
}
.executeAndReturnGeneratedKeys()
.toList<PlacementPlanReject>()
.forEach {
val reason =
when (it.unitRejectReason) {
PlacementPlanRejectReason.OTHER -> it.unitRejectOtherReason
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pitäiskö tässä prefixata muistiinpanon sisältöä hieman, jotta se on yksiselitteisempi:

Niin, että muistiinpanon sisällöksi tulee esim.
"Sijoitusehdotus hylätty - Päiväkoti täynnä" (Tampere; REASON_1)
"Sijoitusehdotus hylätty - Sisäilma tai muu rakenteellinen syy" (Tampere; REASON_2)
"Sijoitusehdotus hylätty - Muu syy: Emme hyväksy maanantaisin lähetettyjä hakemuksia lainkaan" (OTHER)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

else -> rejectReasons[it.unitRejectReason]
}
if (reason != null) {
tx.createApplicationNote(it.applicationId, reason, user.evakaUserId)
}
}

val validIds =
tx.createQuery {
Expand Down
Loading