From 65ba1e92e95dba30bf405855827f637d404bea4b Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Wed, 27 Nov 2024 16:23:26 +0200 Subject: [PATCH 01/13] add confidentiality flag and defaulting logic to backend --- .../generated/api-clients/application.ts | 22 +++++++++ .../generated/api-types/application.ts | 2 + ...ApplicationStateServiceIntegrationTests.kt | 12 +---- .../fi/espoo/evaka/application/Application.kt | 2 + .../application/ApplicationControllerV2.kt | 18 ++++++- .../evaka/application/ApplicationQueries.kt | 49 +++++++++++++++---- .../application/ApplicationStateService.kt | 39 +++++++-------- .../fi/espoo/evaka/shared/dev/DevApi.kt | 2 - .../V471__application_confidentiality.sql | 11 +++++ service/src/main/resources/migrations.txt | 1 + 10 files changed, 113 insertions(+), 45 deletions(-) create mode 100644 service/src/main/resources/db/migration/V471__application_confidentiality.sql diff --git a/frontend/src/employee-frontend/generated/api-clients/application.ts b/frontend/src/employee-frontend/generated/api-clients/application.ts index 9d584cf1390..fd23eeebe2b 100644 --- a/frontend/src/employee-frontend/generated/api-clients/application.ts +++ b/frontend/src/employee-frontend/generated/api-clients/application.ts @@ -28,6 +28,7 @@ import { SimpleBatchRequest } from 'lib-common/generated/api-types/application' import { UUID } from 'lib-common/types' import { UnitApplications } from 'lib-common/generated/api-types/application' import { client } from '../../api/client' +import { createUrlSearchParams } from 'lib-common/api' import { deserializeJsonApplicationNote } from 'lib-common/generated/api-types/application' import { deserializeJsonApplicationNoteResponse } from 'lib-common/generated/api-types/application' import { deserializeJsonApplicationResponse } from 'lib-common/generated/api-types/application' @@ -276,6 +277,27 @@ export async function sendApplication( } +/** +* Generated from fi.espoo.evaka.application.ApplicationControllerV2.setApplicationVerified +*/ +export async function setApplicationVerified( + request: { + applicationId: UUID, + confidential?: boolean | null + } +): Promise { + const params = createUrlSearchParams( + ['confidential', request.confidential?.toString()] + ) + const { data: json } = await client.request>({ + url: uri`/employee/applications/${request.applicationId}/actions/set-verified`.toString(), + method: 'POST', + params + }) + return json +} + + /** * Generated from fi.espoo.evaka.application.ApplicationControllerV2.simpleApplicationAction */ diff --git a/frontend/src/lib-common/generated/api-types/application.ts b/frontend/src/lib-common/generated/api-types/application.ts index 0bd3954ceb6..80781624554 100644 --- a/frontend/src/lib-common/generated/api-types/application.ts +++ b/frontend/src/lib-common/generated/api-types/application.ts @@ -109,6 +109,7 @@ export interface ApplicationDetails { checkedByAdmin: boolean childId: UUID childRestricted: boolean + confidential: boolean | null createdDate: HelsinkiDateTime | null dueDate: LocalDate | null dueDateSetManuallyAt: HelsinkiDateTime | null @@ -277,6 +278,7 @@ export interface ApplicationSummary { assistanceNeed: boolean attachmentCount: number checkedByAdmin: boolean + confidential: boolean | null currentPlacementUnit: PreferredUnit | null dateOfBirth: LocalDate | null dueDate: LocalDate | null 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 87037e4bf14..28c59262989 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/application/ApplicationStateServiceIntegrationTests.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/application/ApplicationStateServiceIntegrationTests.kt @@ -641,7 +641,7 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor } @Test - fun `setVerified and setUnverified - changes checkedByAdmin`() { + fun `setVerified - changes checkedByAdmin`() { db.transaction { tx -> // given tx.insertApplication( @@ -662,16 +662,6 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor assertEquals(ApplicationStatus.WAITING_PLACEMENT, application.status) assertEquals(true, application.checkedByAdmin) } - db.transaction { tx -> - // when - service.setUnverified(tx, serviceWorker, clock, applicationId) - } - db.read { tx -> - // then - val application = tx.fetchApplicationDetails(applicationId)!! - assertEquals(ApplicationStatus.WAITING_PLACEMENT, application.status) - assertEquals(false, application.checkedByAdmin) - } } @Test diff --git a/service/src/main/kotlin/fi/espoo/evaka/application/Application.kt b/service/src/main/kotlin/fi/espoo/evaka/application/Application.kt index 57d3d6ecf8b..b5939d2a427 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/Application.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/Application.kt @@ -52,6 +52,7 @@ data class ApplicationSummary( val preferredUnits: List, val origin: ApplicationOrigin, val checkedByAdmin: Boolean, + val confidential: Boolean?, val status: ApplicationStatus, val additionalInfo: Boolean, val serviceWorkerNote: String, @@ -110,6 +111,7 @@ data class ApplicationDetails( val guardianRestricted: Boolean, val guardianDateOfDeath: LocalDate?, val checkedByAdmin: Boolean, + val confidential: Boolean?, val createdDate: HelsinkiDateTime?, val modifiedDate: HelsinkiDateTime?, val sentDate: LocalDate?, diff --git a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt index ba9ceb4c55c..f98384f8b9f 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt @@ -51,6 +51,7 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController enum class ApplicationTypeToggle { @@ -383,6 +384,21 @@ class ApplicationControllerV2( } } + @PostMapping("/{applicationId}/actions/set-verified") + fun setApplicationVerified( + db: Database, + user: AuthenticatedUser.Employee, + clock: EvakaClock, + @PathVariable applicationId: ApplicationId, + @RequestParam confidential: Boolean?, + ) { + db.connect { dbc -> + dbc.transaction { + applicationStateService.setVerified(it, user, clock, applicationId, confidential) + } + } + } + @GetMapping("/{applicationId}/placement-draft") fun getPlacementPlanDraft( db: Database, @@ -685,8 +701,6 @@ class ApplicationControllerV2( "move-to-waiting-placement" to applicationStateService::moveToWaitingPlacement, "return-to-sent" to applicationStateService::returnToSent, "cancel-application" to applicationStateService::cancelApplication, - "set-verified" to applicationStateService::setVerified, - "set-unverified" to applicationStateService::setUnverified, "cancel-placement-plan" to applicationStateService::cancelPlacementPlan, "send-decisions-without-proposal" to applicationStateService::sendDecisionsWithoutProposal, diff --git a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationQueries.kt index 463589f577a..ffb5968837e 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationQueries.kt @@ -81,8 +81,8 @@ fun Database.Transaction.insertApplication( createUpdate { sql( """ -INSERT INTO application (type, status, guardian_id, child_id, origin, created_by, hidefromguardian, sentdate, allow_other_guardian_access, document, form_modified, status_modified_at, status_modified_by) -VALUES (${bind(type)}, 'CREATED', ${bind(guardianId)}, ${bind(childId)}, ${bind(origin)}, ${bind(createdBy)}, ${bind(hideFromGuardian)}, ${bind(sentDate)}, ${bind(allowOtherGuardianAccess)}, ${bindJson(document)}, ${bind(now)}, ${bind(now)}, ${bind(createdBy)}) +INSERT INTO application (type, status, guardian_id, child_id, origin, created_by, hidefromguardian, sentdate, allow_other_guardian_access, document, form_modified, status_modified_at, status_modified_by, confidential) +VALUES (${bind(type)}, 'CREATED', ${bind(guardianId)}, ${bind(childId)}, ${bind(origin)}, ${bind(createdBy)}, ${bind(hideFromGuardian)}, ${bind(sentDate)}, ${bind(allowOtherGuardianAccess)}, ${bindJson(document)}, ${bind(now)}, ${bind(now)}, ${bind(createdBy)}, NULL) RETURNING id """ ) @@ -436,6 +436,7 @@ fun Database.Read.fetchApplicationSummaries( a.transferapplication, a.additionaldaycareapplication, a.checkedbyadmin, + a.confidential, a.service_worker_note, ( COALESCE((a.document -> 'additionalDetails' ->> 'dietType'), '') != '' OR @@ -544,6 +545,7 @@ fun Database.Read.fetchApplicationSummaries( }, origin = column("origin"), checkedByAdmin = column("checkedbyadmin"), + confidential = column("confidential"), status = status, additionalInfo = column("additionalInfo"), serviceWorkerNote = @@ -779,6 +781,7 @@ fun Database.Read.fetchApplicationDetails( a.duedate, a.duedate_set_manually_at, a.checkedbyadmin, + a.confidential, a.allow_other_guardian_access, EXISTS (SELECT FROM application_other_guardian WHERE application_id = a.id) AS has_other_guardian, coalesce(att.json, '[]'::jsonb) attachments @@ -838,6 +841,7 @@ fun Database.Read.fetchApplicationDetails( dueDate = column("duedate"), dueDateSetManuallyAt = column("duedate_set_manually_at"), checkedByAdmin = column("checkedbyadmin"), + confidential = column("confidential"), hideFromGuardian = column("hidefromguardian"), allowOtherGuardianAccess = column("allow_other_guardian_access"), attachments = jsonColumn("attachments"), @@ -1006,15 +1010,36 @@ fun Database.Transaction.updateForm( } } -fun Database.Transaction.setCheckedByAdminToDefault(id: ApplicationId, form: ApplicationForm) { - val default = - !form.child.assistanceNeeded && - form.child.allergies.isBlank() && - form.child.diet.isBlank() && - form.otherInfo.isBlank() +fun Database.Transaction.resetCheckedByAdminAndConfidentiality( + id: ApplicationId, + form: ApplicationForm, +) { + val confidential = + when { + form.child.assistanceNeeded || + form.child.allergies.isNotBlank() || + form.child.diet.isNotBlank() -> + true // If any of these fields are filled, the application is always confidential + form.otherInfo.isNotBlank() -> + null // else if other info is filled, the confidentiality is not known + else -> false // else the application is not confidential + } + + val requiresManualChecking = + confidential == null || + form.child.assistanceNeeded || + form.child.allergies.isNotBlank() || + form.child.diet.isNotBlank() || + form.otherInfo.isNotBlank() execute { - sql("UPDATE application SET checkedbyadmin = ${bind(default)} WHERE id = ${bind(id)}") + sql( + """ + UPDATE application + SET checkedbyadmin = ${bind(!requiresManualChecking)}, confidential = ${bind(confidential)} + WHERE id = ${bind(id)} + """ + ) } } @@ -1120,6 +1145,12 @@ fun Database.Transaction.setApplicationVerified(id: ApplicationId, verified: Boo } } +fun Database.Transaction.setApplicationConfidentiality(id: ApplicationId, confidential: Boolean?) { + execute { + sql("UPDATE application SET confidential = ${bind(confidential)} WHERE id = ${bind(id)}") + } +} + fun Database.Transaction.setApplicationProcessId(id: ApplicationId, processId: ArchivedProcessId) { execute { sql("UPDATE application SET process_id = ${bind(processId)} WHERE id = ${bind(id)}") } } 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 980c2536e03..939e2072396 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt @@ -234,6 +234,8 @@ class ApplicationStateService( val applicationFlags = tx.applicationFlags(application, currentDate) tx.updateApplicationFlags(application.id, applicationFlags) + tx.resetCheckedByAdminAndConfidentiality(applicationId, application.form) + val sentDate = application.sentDate ?: currentDate val dueDate = application.dueDate @@ -377,6 +379,8 @@ class ApplicationStateService( ) } + tx.resetCheckedByAdminAndConfidentiality(application.id, application.form) + tx.updateApplicationStatus(application.id, SENT, user.evakaUserId, clock.now()) } @@ -408,7 +412,7 @@ class ApplicationStateService( ) ) - tx.setCheckedByAdminToDefault(applicationId, application.form) + tx.resetCheckedByAdminAndConfidentiality(applicationId, application.form) asyncJobRunner.plan( tx, @@ -449,6 +453,8 @@ class ApplicationStateService( val application = getApplication(tx, applicationId) verifyStatus(application, setOf(WAITING_PLACEMENT, CANCELLED)) + tx.resetCheckedByAdminAndConfidentiality(applicationId, application.form) + if (application.status == CANCELLED) { tx.getArchiveProcessByApplicationId(applicationId)?.also { process -> if (process.history.any { it.state == ArchivedProcessState.COMPLETED }) { @@ -499,6 +505,7 @@ class ApplicationStateService( user: AuthenticatedUser, clock: EvakaClock, applicationId: ApplicationId, + confidential: Boolean?, ) { accessControl.requirePermissionFor( tx, @@ -510,27 +517,14 @@ class ApplicationStateService( val application = getApplication(tx, applicationId) verifyStatus(application, WAITING_PLACEMENT) - tx.setApplicationVerified(applicationId, true) - Audit.ApplicationAdminDetailsUpdate.log(targetId = AuditId(applicationId)) - } - fun setUnverified( - tx: Database.Transaction, - user: AuthenticatedUser, - clock: EvakaClock, - applicationId: ApplicationId, - ) { - accessControl.requirePermissionFor( - tx, - user, - clock, - Action.Application.VERIFY, - applicationId, - ) + if (application.confidential == null) { + if (confidential != null) { + tx.setApplicationConfidentiality(applicationId, confidential) + } else throw BadRequest("Confidentiality must be set") + } else if (confidential != null) throw BadRequest("Confidentiality is already set") - val application = getApplication(tx, applicationId) - verifyStatus(application, WAITING_PLACEMENT) - tx.setApplicationVerified(applicationId, false) + tx.setApplicationVerified(applicationId, true) Audit.ApplicationAdminDetailsUpdate.log(targetId = AuditId(applicationId)) } @@ -544,6 +538,9 @@ class ApplicationStateService( val application = getApplication(tx, applicationId) verifyStatus(application, WAITING_PLACEMENT) + if (!application.checkedByAdmin || application.confidential == null) + throw BadRequest("Application has not been verified or confidentiality is not set") + val placementPlanId = placementPlanService.createPlacementPlan(tx, application, placementPlan) createDecisionDrafts(tx, user, application) @@ -1126,7 +1123,7 @@ class ApplicationStateService( original.guardianRestricted, now, ) - setCheckedByAdminToDefault(original.id, updatedForm) + resetCheckedByAdminAndConfidentiality(original.id, updatedForm) when (manuallySetDueDate) { null -> // We don't want to calculate the due date for applications in the CREATED state. diff --git a/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DevApi.kt b/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DevApi.kt index 16a04dcddc8..8e51a239e6d 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DevApi.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DevApi.kt @@ -798,8 +798,6 @@ UPDATE placement SET end_date = ${bind(req.endDate)}, termination_requested_date mapOf( "move-to-waiting-placement" to applicationStateService::moveToWaitingPlacement, "cancel-application" to applicationStateService::cancelApplication, - "set-verified" to applicationStateService::setVerified, - "set-unverified" to applicationStateService::setUnverified, "send-decisions-without-proposal" to applicationStateService::sendDecisionsWithoutProposal, "send-placement-proposal" to applicationStateService::sendPlacementProposal, diff --git a/service/src/main/resources/db/migration/V471__application_confidentiality.sql b/service/src/main/resources/db/migration/V471__application_confidentiality.sql new file mode 100644 index 00000000000..d3e61510029 --- /dev/null +++ b/service/src/main/resources/db/migration/V471__application_confidentiality.sql @@ -0,0 +1,11 @@ +ALTER TABLE application ADD COLUMN confidential bool; + +UPDATE application +SET confidential = ( + (document -> 'careDetails' ->> 'assistanceNeeded')::bool = true OR + document -> 'careDetails' ->> 'assistanceDescription' <> '' OR + document -> 'additionalDetails' ->> 'dietType' <> '' OR + document -> 'additionalDetails' ->> 'allergyType' <> '' OR + document -> 'additionalDetails' ->> 'otherInfo' <> '' +) +WHERE status NOT IN ('CREATED', 'SENT'); diff --git a/service/src/main/resources/migrations.txt b/service/src/main/resources/migrations.txt index 8fbf5941793..ff89aea65fc 100644 --- a/service/src/main/resources/migrations.txt +++ b/service/src/main/resources/migrations.txt @@ -466,3 +466,4 @@ V467__push_notification_calendar_event_reservation_defaults.sql V468__citizen_user.sql V469__income_statement_status.sql V470__cascade_citizen_user_delete.sql +V471__application_confidentiality.sql From ac6cc12663336456a92360fdf68a66bf67c8b7df Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Mon, 2 Dec 2024 09:01:34 +0200 Subject: [PATCH 02/13] Use enum for SimpleApplicationAction --- .../components/applications/ActionBar.tsx | 12 ++-- .../applications/ApplicationActions.tsx | 50 +++----------- .../generated/api-clients/application.ts | 5 +- .../generated/api-types/application.ts | 13 ++++ .../defaults/employee/i18n/fi.tsx | 1 - .../DecisionCreationIntegrationTest.kt | 3 +- .../placement/PlacementPlanIntegrationTest.kt | 3 +- .../application/ApplicationControllerV2.kt | 65 ++++++------------- .../application/ApplicationStateService.kt | 45 +++++++++++++ 9 files changed, 99 insertions(+), 98 deletions(-) diff --git a/frontend/src/employee-frontend/components/applications/ActionBar.tsx b/frontend/src/employee-frontend/components/applications/ActionBar.tsx index f01e30f0453..411dd1e64c8 100755 --- a/frontend/src/employee-frontend/components/applications/ActionBar.tsx +++ b/frontend/src/employee-frontend/components/applications/ActionBar.tsx @@ -79,7 +79,7 @@ export default React.memo(function ActionBar({ reloadApplications }: Props) { onClick: () => handlePromise( simpleBatchAction({ - action: 'move-to-waiting-placement', + action: 'MOVE_TO_WAITING_PLACEMENT', body: { applicationIds: checkedIds } }) ) @@ -93,7 +93,7 @@ export default React.memo(function ActionBar({ reloadApplications }: Props) { onClick: () => handlePromise( simpleBatchAction({ - action: 'return-to-sent', + action: 'RETURN_TO_SENT', body: { applicationIds: checkedIds } }) ) @@ -107,7 +107,7 @@ export default React.memo(function ActionBar({ reloadApplications }: Props) { onClick: () => handlePromise( simpleBatchAction({ - action: 'cancel-placement-plan', + action: 'CANCEL_PLACEMENT_PLAN', body: { applicationIds: checkedIds } }) ) @@ -121,7 +121,7 @@ export default React.memo(function ActionBar({ reloadApplications }: Props) { onClick: () => handlePromise( simpleBatchAction({ - action: 'send-decisions-without-proposal', + action: 'SEND_DECISIONS_WITHOUT_PROPOSAL', body: { applicationIds: checkedIds } }) ) @@ -135,7 +135,7 @@ export default React.memo(function ActionBar({ reloadApplications }: Props) { onClick: () => handlePromise( simpleBatchAction({ - action: 'send-placement-proposal', + action: 'SEND_PLACEMENT_PROPOSAL', body: { applicationIds: checkedIds } }) ) @@ -149,7 +149,7 @@ export default React.memo(function ActionBar({ reloadApplications }: Props) { onClick: () => handlePromise( simpleBatchAction({ - action: 'withdraw-placement-proposal', + action: 'WITHDRAW_PLACEMENT_PROPOSAL', body: { applicationIds: checkedIds } }) ) diff --git a/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx b/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx index 92023a84428..732bbc12849 100755 --- a/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx +++ b/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx @@ -70,7 +70,7 @@ export default React.memo(function ApplicationActions({ handlePromise( simpleApplicationAction({ applicationId: application.id, - action: 'move-to-waiting-placement' + action: 'MOVE_TO_WAITING_PLACEMENT' }) ) } @@ -87,7 +87,7 @@ export default React.memo(function ApplicationActions({ handlePromise( simpleApplicationAction({ applicationId: application.id, - action: 'return-to-sent' + action: 'RETURN_TO_SENT' }) ) } @@ -103,40 +103,6 @@ export default React.memo(function ApplicationActions({ setConfirmingApplicationCancel(true) } }, - { - id: 'set-verified', - label: i18n.applications.actions.setVerified, - enabled: - application.status === 'WAITING_PLACEMENT' && - !application.checkedByAdmin, - disabled: actionInFlight, - onClick: () => { - setActionInFlight(true) - handlePromise( - simpleApplicationAction({ - applicationId: application.id, - action: 'set-verified' - }) - ) - } - }, - { - id: 'set-unverified', - label: i18n.applications.actions.setUnverified, - enabled: - application.status === 'WAITING_PLACEMENT' && - application.checkedByAdmin, - disabled: actionInFlight, - onClick: () => { - setActionInFlight(true) - handlePromise( - simpleApplicationAction({ - applicationId: application.id, - action: 'set-unverified' - }) - ) - } - }, { id: 'create-placement-plan', label: i18n.applications.actions.createPlacementPlan, @@ -160,7 +126,7 @@ export default React.memo(function ApplicationActions({ handlePromise( simpleApplicationAction({ applicationId: application.id, - action: 'cancel-placement-plan' + action: 'CANCEL_PLACEMENT_PLAN' }) ) } @@ -186,7 +152,7 @@ export default React.memo(function ApplicationActions({ handlePromise( simpleApplicationAction({ applicationId: application.id, - action: 'send-decisions-without-proposal' + action: 'SEND_DECISIONS_WITHOUT_PROPOSAL' }) ) } @@ -201,7 +167,7 @@ export default React.memo(function ApplicationActions({ handlePromise( simpleApplicationAction({ applicationId: application.id, - action: 'send-placement-proposal' + action: 'SEND_PLACEMENT_PROPOSAL' }) ) } @@ -216,7 +182,7 @@ export default React.memo(function ApplicationActions({ handlePromise( simpleApplicationAction({ applicationId: application.id, - action: 'withdraw-placement-proposal' + action: 'WITHDRAW_PLACEMENT_PROPOSAL' }) ) } @@ -231,7 +197,7 @@ export default React.memo(function ApplicationActions({ handlePromise( simpleApplicationAction({ applicationId: application.id, - action: 'confirm-decision-mailed' + action: 'CONFIRM_DECISION_MAILED' }) ) } @@ -266,7 +232,7 @@ export default React.memo(function ApplicationActions({ handlePromise( simpleApplicationAction({ applicationId: application.id, - action: 'cancel-application' + action: 'CANCEL_APPLICATION' }) ) }, diff --git a/frontend/src/employee-frontend/generated/api-clients/application.ts b/frontend/src/employee-frontend/generated/api-clients/application.ts index fd23eeebe2b..204298dfe50 100644 --- a/frontend/src/employee-frontend/generated/api-clients/application.ts +++ b/frontend/src/employee-frontend/generated/api-clients/application.ts @@ -24,6 +24,7 @@ import { PlacementProposalConfirmationUpdate } from 'lib-common/generated/api-ty import { PreschoolTerm } from 'lib-common/generated/api-types/daycare' import { RejectDecisionRequest } from 'lib-common/generated/api-types/application' import { SearchApplicationRequest } from 'lib-common/generated/api-types/application' +import { SimpleApplicationAction } from 'lib-common/generated/api-types/application' import { SimpleBatchRequest } from 'lib-common/generated/api-types/application' import { UUID } from 'lib-common/types' import { UnitApplications } from 'lib-common/generated/api-types/application' @@ -304,7 +305,7 @@ export async function setApplicationVerified( export async function simpleApplicationAction( request: { applicationId: UUID, - action: string + action: SimpleApplicationAction } ): Promise { const { data: json } = await client.request>({ @@ -320,7 +321,7 @@ export async function simpleApplicationAction( */ export async function simpleBatchAction( request: { - action: string, + action: SimpleApplicationAction, body: SimpleBatchRequest } ): Promise { diff --git a/frontend/src/lib-common/generated/api-types/application.ts b/frontend/src/lib-common/generated/api-types/application.ts index 80781624554..15016ec22ef 100644 --- a/frontend/src/lib-common/generated/api-types/application.ts +++ b/frontend/src/lib-common/generated/api-types/application.ts @@ -745,6 +745,19 @@ export interface SiblingBasis { siblingUnit: string } +/** +* Generated from fi.espoo.evaka.application.SimpleApplicationAction +*/ +export type SimpleApplicationAction = + | 'MOVE_TO_WAITING_PLACEMENT' + | 'RETURN_TO_SENT' + | 'CANCEL_APPLICATION' + | 'CANCEL_PLACEMENT_PLAN' + | 'SEND_DECISIONS_WITHOUT_PROPOSAL' + | 'SEND_PLACEMENT_PROPOSAL' + | 'WITHDRAW_PLACEMENT_PROPOSAL' + | 'CONFIRM_DECISION_MAILED' + /** * Generated from fi.espoo.evaka.application.SimpleBatchRequest */ diff --git a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx index abb5f37af7e..70fbb8991b4 100755 --- a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx +++ b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx @@ -343,7 +343,6 @@ export const fi = { 'Haluatko varmasti poistaa hakemuksen käsittelystä?', check: 'Tarkista', setVerified: 'Merkitse tarkistetuksi', - setUnverified: 'Merkitse tarkistamattomaksi', createPlacementPlan: 'Sijoita', cancelPlacementPlan: 'Palauta sijoitettaviin', editDecisions: 'Päätökset', diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionCreationIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionCreationIntegrationTest.kt index 1f4cd893546..1babff7b492 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionCreationIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionCreationIntegrationTest.kt @@ -15,6 +15,7 @@ import fi.espoo.evaka.application.DaycarePlacementPlan import fi.espoo.evaka.application.DecisionDraftGroup import fi.espoo.evaka.application.DecisionSummary import fi.espoo.evaka.application.GuardianInfo +import fi.espoo.evaka.application.SimpleApplicationAction import fi.espoo.evaka.application.persistence.daycare.Apply import fi.espoo.evaka.application.persistence.daycare.CareDetails import fi.espoo.evaka.application.persistence.daycare.DaycareFormV0 @@ -563,7 +564,7 @@ WHERE id = ${bind(testDaycare.id)} serviceWorker, RealEvakaClock(), applicationId, - "send-decisions-without-proposal", + SimpleApplicationAction.SEND_DECISIONS_WITHOUT_PROPOSAL, ) asyncJobRunner.runPendingJobsSync(RealEvakaClock()) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/placement/PlacementPlanIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/placement/PlacementPlanIntegrationTest.kt index 4b122393a1e..ba8272c9a47 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/placement/PlacementPlanIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/placement/PlacementPlanIntegrationTest.kt @@ -10,6 +10,7 @@ import fi.espoo.evaka.application.ApplicationStatus import fi.espoo.evaka.application.ApplicationType import fi.espoo.evaka.application.DaycarePlacementPlan import fi.espoo.evaka.application.ServiceNeedOption +import fi.espoo.evaka.application.SimpleApplicationAction import fi.espoo.evaka.application.persistence.daycare.Apply import fi.espoo.evaka.application.persistence.daycare.CareDetails import fi.espoo.evaka.application.persistence.daycare.DaycareFormV0 @@ -632,7 +633,7 @@ class PlacementPlanIntegrationTest : FullApplicationTest(resetDbBeforeEach = tru serviceWorker, clock, applicationId, - "send-placement-proposal", + SimpleApplicationAction.SEND_PLACEMENT_PROPOSAL, ) return PlacementPlanDetails( id = placementPlanId, diff --git a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt index f98384f8b9f..08c4e0eeb40 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt @@ -559,36 +559,6 @@ class ApplicationControllerV2( } } - @PostMapping("/batch/actions/{action}") - fun simpleBatchAction( - db: Database, - user: AuthenticatedUser.Employee, - clock: EvakaClock, - @PathVariable action: String, - @RequestBody body: SimpleBatchRequest, - ) { - val simpleBatchActions = - mapOf( - "move-to-waiting-placement" to applicationStateService::moveToWaitingPlacement, - "return-to-sent" to applicationStateService::returnToSent, - "cancel-placement-plan" to applicationStateService::cancelPlacementPlan, - "send-decisions-without-proposal" to - applicationStateService::sendDecisionsWithoutProposal, - "send-placement-proposal" to applicationStateService::sendPlacementProposal, - "withdraw-placement-proposal" to applicationStateService::withdrawPlacementProposal, - "confirm-decision-mailed" to applicationStateService::confirmDecisionMailed, - ) - - val actionFn = simpleBatchActions[action] ?: throw NotFound("Batch action not recognized") - db.connect { dbc -> - dbc.transaction { tx -> - body.applicationIds.forEach { applicationId -> - actionFn.invoke(tx, user, clock, applicationId) - } - } - } - } - @PostMapping("/{applicationId}/actions/create-placement-plan") fun createPlacementPlan( db: Database, @@ -694,23 +664,28 @@ class ApplicationControllerV2( user: AuthenticatedUser.Employee, clock: EvakaClock, @PathVariable applicationId: ApplicationId, - @PathVariable action: String, + @PathVariable action: SimpleApplicationAction, ) { - val simpleActions = - mapOf( - "move-to-waiting-placement" to applicationStateService::moveToWaitingPlacement, - "return-to-sent" to applicationStateService::returnToSent, - "cancel-application" to applicationStateService::cancelApplication, - "cancel-placement-plan" to applicationStateService::cancelPlacementPlan, - "send-decisions-without-proposal" to - applicationStateService::sendDecisionsWithoutProposal, - "send-placement-proposal" to applicationStateService::sendPlacementProposal, - "withdraw-placement-proposal" to applicationStateService::withdrawPlacementProposal, - "confirm-decision-mailed" to applicationStateService::confirmDecisionMailed, - ) + db.connect { dbc -> + dbc.transaction { tx -> + applicationStateService.doSimpleAction(tx, user, clock, action, applicationId) + } + } + } - val actionFn = simpleActions[action] ?: throw NotFound("Action not recognized") - db.connect { dbc -> dbc.transaction { actionFn.invoke(it, user, clock, applicationId) } } + @PostMapping("/batch/actions/{action}") + fun simpleBatchAction( + db: Database, + user: AuthenticatedUser.Employee, + clock: EvakaClock, + @PathVariable action: SimpleApplicationAction, + @RequestBody body: SimpleBatchRequest, + ) { + db.connect { dbc -> + dbc.transaction { tx -> + applicationStateService.doSimpleAction(tx, user, clock, action, body.applicationIds) + } + } } @GetMapping("/units/{unitId}") 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 939e2072396..d33fb8b86fd 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt @@ -94,6 +94,17 @@ import java.time.format.FormatStyle import java.util.Locale import org.springframework.stereotype.Service +enum class SimpleApplicationAction { + MOVE_TO_WAITING_PLACEMENT, + RETURN_TO_SENT, + CANCEL_APPLICATION, + CANCEL_PLACEMENT_PLAN, + SEND_DECISIONS_WITHOUT_PROPOSAL, + SEND_PLACEMENT_PROPOSAL, + WITHDRAW_PLACEMENT_PROPOSAL, + CONFIRM_DECISION_MAILED, +} + @Service class ApplicationStateService( private val accessControl: AccessControl, @@ -105,6 +116,40 @@ class ApplicationStateService( private val messageProvider: IMessageProvider, private val messageService: MessageService, ) { + fun doSimpleAction( + tx: Database.Transaction, + user: AuthenticatedUser, + clock: EvakaClock, + action: SimpleApplicationAction, + applicationId: ApplicationId, + ) { + when (action) { + SimpleApplicationAction.MOVE_TO_WAITING_PLACEMENT -> + moveToWaitingPlacement(tx, user, clock, applicationId) + SimpleApplicationAction.RETURN_TO_SENT -> returnToSent(tx, user, clock, applicationId) + SimpleApplicationAction.CANCEL_APPLICATION -> + cancelApplication(tx, user, clock, applicationId) + SimpleApplicationAction.CANCEL_PLACEMENT_PLAN -> + cancelPlacementPlan(tx, user, clock, applicationId) + SimpleApplicationAction.SEND_DECISIONS_WITHOUT_PROPOSAL -> + sendDecisionsWithoutProposal(tx, user, clock, applicationId) + SimpleApplicationAction.SEND_PLACEMENT_PROPOSAL -> + sendPlacementProposal(tx, user, clock, applicationId) + SimpleApplicationAction.WITHDRAW_PLACEMENT_PROPOSAL -> + withdrawPlacementProposal(tx, user, clock, applicationId) + SimpleApplicationAction.CONFIRM_DECISION_MAILED -> + confirmDecisionMailed(tx, user, clock, applicationId) + } + } + + fun doSimpleAction( + tx: Database.Transaction, + user: AuthenticatedUser, + clock: EvakaClock, + action: SimpleApplicationAction, + applicationIds: Set, + ) = applicationIds.forEach { doSimpleAction(tx, user, clock, action, it) } + fun createApplication( tx: Database.Transaction, user: AuthenticatedUser, From 9b0a57272c78a1993ca1f0f4de81df13c6ed9adc Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Mon, 2 Dec 2024 09:05:00 +0200 Subject: [PATCH 03/13] Fix some integration tests --- .../kotlin/fi/espoo/evaka/TestFixtures.kt | 11 +++++++---- .../ApplicationStateServiceIntegrationTests.kt | 14 +++++++------- .../application/GetApplicationIntegrationTests.kt | 1 + .../decision/DecisionControllerIntegrationTest.kt | 7 +++++++ .../decision/DecisionCreationIntegrationTest.kt | 7 +++++++ .../placement/PlacementPlanIntegrationTest.kt | 7 +++++++ .../fi/espoo/evaka/shared/job/ScheduledJobsTest.kt | 7 +++++++ .../kotlin/fi/espoo/evaka/test/TestData.kt | 3 +++ .../fi/espoo/evaka/shared/dev/DataInitializers.kt | 6 ++++-- 9 files changed, 50 insertions(+), 13 deletions(-) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt index 3cff2d722ae..619051f505d 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt @@ -578,7 +578,9 @@ fun Database.Transaction.insertApplication( child: DevPerson = testChild_6, appliedType: PlacementType = PlacementType.PRESCHOOL_DAYCARE, urgent: Boolean = false, - hasAdditionalInfo: Boolean = false, + diet: String = "", + allergies: String = "", + otherInfo: String = "", maxFeeAccepted: Boolean = false, preferredStartDate: LocalDate? = LocalDate.now().plusMonths(4), applicationId: ApplicationId = ApplicationId(UUID.randomUUID()), @@ -608,8 +610,8 @@ fun Database.Transaction.insertApplication( futureAddress = null, nationality = "fi", language = "fi", - allergies = if (hasAdditionalInfo) "allergies" else "", - diet = if (hasAdditionalInfo) "diet" else "", + allergies = allergies, + diet = diet, assistanceNeeded = false, assistanceDescription = "", ), @@ -659,7 +661,7 @@ fun Database.Transaction.insertApplication( secondGuardian = null, otherPartner = null, otherChildren = emptyList(), - otherInfo = if (hasAdditionalInfo) "foobar" else "", + otherInfo = otherInfo, maxFeeAccepted = maxFeeAccepted, clubDetails = null, ), @@ -679,6 +681,7 @@ fun Database.Transaction.insertApplication( additionalDaycareApplication = false, otherGuardianLivesInSameAddress = null, checkedByAdmin = false, + confidential = null, hideFromGuardian = false, allowOtherGuardianAccess = true, attachments = listOf(), 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 28c59262989..cb40b6e3f09 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/application/ApplicationStateServiceIntegrationTests.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/application/ApplicationStateServiceIntegrationTests.kt @@ -529,7 +529,6 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor db.transaction { tx -> // given tx.insertApplication( - hasAdditionalInfo = false, applicationId = applicationId, preferredStartDate = LocalDate.of(2020, 8, 1), ) @@ -552,7 +551,7 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor db.transaction { tx -> // given tx.insertApplication( - hasAdditionalInfo = true, + otherInfo = "something", applicationId = applicationId, preferredStartDate = LocalDate.of(2020, 8, 1), ) @@ -623,7 +622,8 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor // given tx.insertApplication( appliedType = PlacementType.DAYCARE, - hasAdditionalInfo = true, + diet = "vegaani", + allergies = "pähkinät", applicationId = applicationId, ) service.sendApplication(tx, serviceWorker, clock, applicationId) @@ -635,8 +635,8 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor db.read { tx -> // then val childDetails = tx.getChild(testChild_6.id)!!.additionalInformation - assertEquals("diet", childDetails.diet) - assertEquals("allergies", childDetails.allergies) + assertEquals("vegaani", childDetails.diet) + assertEquals("pähkinät", childDetails.allergies) } } @@ -645,7 +645,7 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor db.transaction { tx -> // given tx.insertApplication( - hasAdditionalInfo = true, + diet = "vegaani", applicationId = applicationId, preferredStartDate = LocalDate.of(2020, 8, 1), ) @@ -654,7 +654,7 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor } db.transaction { tx -> // when - service.setVerified(tx, serviceWorker, clock, applicationId) + service.setVerified(tx, serviceWorker, clock, applicationId, confidential = null) } db.read { tx -> // then diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/application/GetApplicationIntegrationTests.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/application/GetApplicationIntegrationTests.kt index 414125d15ae..9d0be6cb542 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/application/GetApplicationIntegrationTests.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/application/GetApplicationIntegrationTests.kt @@ -406,6 +406,7 @@ class GetApplicationIntegrationTests : FullApplicationTest(resetDbBeforeEach = t db.transaction { tx -> stateService.sendApplication(tx, serviceWorker, clock, applicationId) stateService.moveToWaitingPlacement(tx, serviceWorker, clock, applicationId) + stateService.setVerified(tx, serviceWorker, clock, applicationId, null) stateService.createPlacementPlan( tx, serviceWorker, diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionControllerIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionControllerIntegrationTest.kt index ebea54c7146..384e29d3d6c 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionControllerIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionControllerIntegrationTest.kt @@ -229,6 +229,13 @@ class DecisionControllerIntegrationTest : FullApplicationTest(resetDbBeforeEach preferredStartDate = clock.today().plusMonths(5), ), ) + applicationStateService.setVerified( + tx = tx, + user = AuthenticatedUser.Employee(serviceWorker, setOf(UserRole.SERVICE_WORKER)), + clock = clock, + applicationId = applicationId, + confidential = false, + ) applicationStateService.createPlacementPlan( tx = tx, user = AuthenticatedUser.Employee(serviceWorker, setOf(UserRole.SERVICE_WORKER)), diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionCreationIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionCreationIntegrationTest.kt index 1babff7b492..4c6116f5224 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionCreationIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/decision/DecisionCreationIntegrationTest.kt @@ -624,6 +624,13 @@ WHERE id = ${bind(testDaycare.id)} ), ) + applicationStateService.setVerified( + tx, + serviceWorker, + RealEvakaClock(), + applicationId, + false, + ) applicationStateService.createPlacementPlan( tx, serviceWorker, diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/placement/PlacementPlanIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/placement/PlacementPlanIntegrationTest.kt index ba8272c9a47..b6f939570a3 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/placement/PlacementPlanIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/placement/PlacementPlanIntegrationTest.kt @@ -664,6 +664,13 @@ class PlacementPlanIntegrationTest : FullApplicationTest(resetDbBeforeEach = tru type: PlacementType, proposal: DaycarePlacementPlan, ): PlacementPlanId { + applicationController.setApplicationVerified( + dbInstance(), + serviceWorker, + clock, + applicationId, + false, + ) applicationController.createPlacementPlan( dbInstance(), serviceWorker, diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/job/ScheduledJobsTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/job/ScheduledJobsTest.kt index 78c440208ad..bf051fbb87d 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/job/ScheduledJobsTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/job/ScheduledJobsTest.kt @@ -314,6 +314,13 @@ class ScheduledJobsTest : FullApplicationTest(resetDbBeforeEach = true) { status = ApplicationStatus.WAITING_PLACEMENT, ) db.transaction { + applicationStateService.setVerified( + it, + serviceWorker, + RealEvakaClock(), + applicationId, + false, + ) applicationStateService.createPlacementPlan( it, serviceWorker, diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/test/TestData.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/test/TestData.kt index 90a6d16414c..b41de83b4e2 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/test/TestData.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/test/TestData.kt @@ -49,6 +49,7 @@ private fun applicationDetails(vararg preferredUnits: PreferredUnit, shiftCare: guardianRestricted = false, guardianDateOfDeath = null, checkedByAdmin = true, + confidential = false, createdDate = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), modifiedDate = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), sentDate = LocalDate.of(2021, 1, 15), @@ -142,6 +143,7 @@ val validPreschoolApplication = guardianRestricted = false, guardianDateOfDeath = null, checkedByAdmin = true, + confidential = false, createdDate = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), modifiedDate = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), sentDate = LocalDate.of(2021, 1, 15), @@ -236,6 +238,7 @@ fun validClubApplication(preferredUnit: DevDaycare, preferredStartDate: LocalDat guardianRestricted = false, guardianDateOfDeath = null, checkedByAdmin = true, + confidential = false, createdDate = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), modifiedDate = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), sentDate = LocalDate.of(2021, 1, 15), diff --git a/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DataInitializers.kt b/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DataInitializers.kt index f00900b72cd..4bc0127820d 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DataInitializers.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DataInitializers.kt @@ -431,8 +431,8 @@ fun Database.Transaction.insertTestApplication( createUpdate { sql( """ -INSERT INTO application (type, id, sentdate, duedate, status, guardian_id, child_id, origin, hidefromguardian, additionalDaycareApplication, transferApplication, allow_other_guardian_access, document, form_modified) -VALUES (${bind(type)}, ${bind(id)}, ${bind(sentDate)}, ${bind(dueDate)}, ${bind(status)}::application_status_type, ${bind(guardianId)}, ${bind(childId)}, 'ELECTRONIC'::application_origin_type, ${bind(hideFromGuardian)}, ${bind(additionalDaycareApplication)}, ${bind(transferApplication)}, ${bind(allowOtherGuardianAccess)}, ${bindJson(document)}, ${bind(formModified)}) +INSERT INTO application (type, id, sentdate, duedate, status, guardian_id, child_id, origin, hidefromguardian, additionalDaycareApplication, transferApplication, allow_other_guardian_access, document, form_modified, confidential) +VALUES (${bind(type)}, ${bind(id)}, ${bind(sentDate)}, ${bind(dueDate)}, ${bind(status)}::application_status_type, ${bind(guardianId)}, ${bind(childId)}, 'ELECTRONIC'::application_origin_type, ${bind(hideFromGuardian)}, ${bind(additionalDaycareApplication)}, ${bind(transferApplication)}, ${bind(allowOtherGuardianAccess)}, ${bindJson(document)}, ${bind(formModified)}, NULL) """ ) } @@ -942,6 +942,7 @@ INSERT INTO application( child_id, origin, checkedbyadmin, + confidential, hidefromguardian, transferapplication, allow_other_guardian_access, @@ -958,6 +959,7 @@ VALUES ( ${bind(application.childId)}, ${bind(application.origin)}::application_origin_type, ${bind(application.checkedByAdmin)}, + ${bind(application.confidential)}, ${bind(application.hideFromGuardian)}, ${bind(application.transferApplication)}, ${bind(application.allowOtherGuardianAccess)}, From da884bce5761fad49f41183a4798936ea911334a Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Mon, 2 Dec 2024 09:10:45 +0200 Subject: [PATCH 04/13] Fix some e2e tests --- frontend/src/e2e-test/dev-api/fixtures.ts | 7 +++++-- frontend/src/e2e-test/generated/api-types.ts | 1 + .../src/e2e-test/specs/5_employee/search-person.spec.ts | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/src/e2e-test/dev-api/fixtures.ts b/frontend/src/e2e-test/dev-api/fixtures.ts index 1f83875b523..f9a7d74e887 100644 --- a/frontend/src/e2e-test/dev-api/fixtures.ts +++ b/frontend/src/e2e-test/dev-api/fixtures.ts @@ -2912,7 +2912,9 @@ export const applicationFixture = ( status: ApplicationStatus = 'SENT', preferredStartDate: LocalDate = LocalDate.of(2021, 8, 16), transferApplication = false, - assistanceNeeded = false + assistanceNeeded = false, + checkedByAdmin = false, + confidential: boolean | null = null ): DevApplicationWithForm => ({ id: applicationFixtureId, type: type, @@ -2931,7 +2933,8 @@ export const applicationFixture = ( connectedDaycare, assistanceNeeded ), - checkedByAdmin: false, + checkedByAdmin, + confidential, hideFromGuardian: false, origin: 'ELECTRONIC', status, diff --git a/frontend/src/e2e-test/generated/api-types.ts b/frontend/src/e2e-test/generated/api-types.ts index 229abd8960b..fc41716f03d 100644 --- a/frontend/src/e2e-test/generated/api-types.ts +++ b/frontend/src/e2e-test/generated/api-types.ts @@ -146,6 +146,7 @@ export interface DevApplicationWithForm { allowOtherGuardianAccess: boolean checkedByAdmin: boolean childId: UUID + confidential: boolean | null createdDate: HelsinkiDateTime | null dueDate: LocalDate | null form: ApplicationForm diff --git a/frontend/src/e2e-test/specs/5_employee/search-person.spec.ts b/frontend/src/e2e-test/specs/5_employee/search-person.spec.ts index 2cf74845d0e..bf26f3a413a 100644 --- a/frontend/src/e2e-test/specs/5_employee/search-person.spec.ts +++ b/frontend/src/e2e-test/specs/5_employee/search-person.spec.ts @@ -73,6 +73,8 @@ describe('Search person', () => { 'WAITING_PLACEMENT', preferredStartDate, false, + true, + true, true ), id: uuidv4() @@ -90,6 +92,8 @@ describe('Search person', () => { 'WAITING_PLACEMENT', preferredStartDate, false, + false, + true, false ), id: uuidv4() From 9a49a47b96e5935f34fb4966f1170799367df536 Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Mon, 2 Dec 2024 12:48:33 +0200 Subject: [PATCH 05/13] Use type enum also in dev-api's application action --- frontend/src/e2e-test/dev-api/index.ts | 30 ++++++----- .../src/e2e-test/generated/api-clients.ts | 3 +- .../citizen-applications-list.spec.ts | 6 +-- .../citizen-daycare-application.spec.ts | 2 +- .../specs/0_citizen/citizen-decisions.spec.ts | 18 +++---- .../specs/0_citizen/foster-parents.spec.ts | 16 +++--- .../application-attachments.spec.ts | 12 ++--- .../5_employee/application-details.spec.ts | 8 +-- .../application-transitions.spec.ts | 54 +++++++++---------- .../specs/5_employee/applications.spec.ts | 6 +-- ...flag-decision-draft-multiple-units.spec.ts | 8 +-- .../fi/espoo/evaka/shared/dev/DevApi.kt | 17 ++---- 12 files changed, 87 insertions(+), 93 deletions(-) diff --git a/frontend/src/e2e-test/dev-api/index.ts b/frontend/src/e2e-test/dev-api/index.ts index 12a02c9dd8b..832f203de72 100644 --- a/frontend/src/e2e-test/dev-api/index.ts +++ b/frontend/src/e2e-test/dev-api/index.ts @@ -8,10 +8,15 @@ import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios' import FormData from 'form-data' import { BaseError } from 'make-error-cause' +import { SimpleApplicationAction } from 'lib-common/generated/api-types/application' import HelsinkiDateTime from 'lib-common/helsinki-date-time' import config from '../config' -import { runJobs, simpleAction } from '../generated/api-clients' +import { + createDefaultPlacementPlan, + runJobs, + simpleAction +} from '../generated/api-clients' import { DevPerson, MockVtjDataset } from '../generated/api-types' export class DevApiError extends BaseError { @@ -46,24 +51,21 @@ export const devClient = axios.create({ baseURL: config.devApiGwUrl }) -type ApplicationActionSimple = - | 'move-to-waiting-placement' - | 'cancel-application' - | 'set-verified' - | 'set-unverified' - | 'create-default-placement-plan' - | 'confirm-placement-without-decision' - | 'send-decisions-without-proposal' - | 'send-placement-proposal' - | 'confirm-decision-mailed' +type DevApplicationAction = + | SimpleApplicationAction + | 'CREATE_DEFAULT_PLACEMENT_PLAN' export async function execSimpleApplicationAction( applicationId: string, - action: ApplicationActionSimple, + action: DevApplicationAction, mockedTime: HelsinkiDateTime ): Promise { try { - await simpleAction({ applicationId, action }, { mockedTime }) + if (action === 'CREATE_DEFAULT_PLACEMENT_PLAN') { + await createDefaultPlacementPlan({ applicationId }, { mockedTime }) + } else { + await simpleAction({ applicationId, action }, { mockedTime }) + } } catch (e) { throw new DevApiError(e) } @@ -71,7 +73,7 @@ export async function execSimpleApplicationAction( export async function execSimpleApplicationActions( applicationId: string, - actions: ApplicationActionSimple[], + actions: DevApplicationAction[], mockedTime: HelsinkiDateTime ): Promise { for (const action of actions) { diff --git a/frontend/src/e2e-test/generated/api-clients.ts b/frontend/src/e2e-test/generated/api-clients.ts index 695abcb9704..ce61a770f03 100644 --- a/frontend/src/e2e-test/generated/api-clients.ts +++ b/frontend/src/e2e-test/generated/api-clients.ts @@ -92,6 +92,7 @@ import { PostPairingResponseReq } from 'lib-common/generated/api-types/pairing' import { ReservationInsert } from './api-types' import { ServiceNeedOption } from 'lib-common/generated/api-types/serviceneed' import { SfiMessage } from './api-types' +import { SimpleApplicationAction } from 'lib-common/generated/api-types/application' import { SpecialDiet } from 'lib-common/generated/api-types/specialdiet' import { StaffMemberAttendance } from 'lib-common/generated/api-types/attendance' import { UUID } from 'lib-common/types' @@ -2370,7 +2371,7 @@ export async function setTestMode( export async function simpleAction( request: { applicationId: UUID, - action: string + action: SimpleApplicationAction }, options?: { mockedTime?: HelsinkiDateTime } ): Promise { diff --git a/frontend/src/e2e-test/specs/0_citizen/citizen-applications-list.spec.ts b/frontend/src/e2e-test/specs/0_citizen/citizen-applications-list.spec.ts index d479a8b1542..71f14ef7d23 100644 --- a/frontend/src/e2e-test/specs/0_citizen/citizen-applications-list.spec.ts +++ b/frontend/src/e2e-test/specs/0_citizen/citizen-applications-list.spec.ts @@ -123,9 +123,9 @@ describe('Citizen applications list', () => { await execSimpleApplicationActions( application.id, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-decisions-without-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_DECISIONS_WITHOUT_PROPOSAL' ], now ) diff --git a/frontend/src/e2e-test/specs/0_citizen/citizen-daycare-application.spec.ts b/frontend/src/e2e-test/specs/0_citizen/citizen-daycare-application.spec.ts index a769dd187a4..f050fc323b6 100644 --- a/frontend/src/e2e-test/specs/0_citizen/citizen-daycare-application.spec.ts +++ b/frontend/src/e2e-test/specs/0_citizen/citizen-daycare-application.spec.ts @@ -163,7 +163,7 @@ describe('Citizen daycare applications', () => { await createApplications({ body: [application] }) await execSimpleApplicationActions( application.id, - ['move-to-waiting-placement'], + ['MOVE_TO_WAITING_PLACEMENT'], mockedNow ) diff --git a/frontend/src/e2e-test/specs/0_citizen/citizen-decisions.spec.ts b/frontend/src/e2e-test/specs/0_citizen/citizen-decisions.spec.ts index f95d03b2c4f..ab6ee520e89 100644 --- a/frontend/src/e2e-test/specs/0_citizen/citizen-decisions.spec.ts +++ b/frontend/src/e2e-test/specs/0_citizen/citizen-decisions.spec.ts @@ -88,9 +88,9 @@ describe('Citizen application decisions', () => { await execSimpleApplicationActions( applicationId, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-decisions-without-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_DECISIONS_WITHOUT_PROPOSAL' ], now ) @@ -177,9 +177,9 @@ describe('Citizen application decisions', () => { await execSimpleApplicationActions( applicationId, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-decisions-without-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_DECISIONS_WITHOUT_PROPOSAL' ], now ) @@ -239,9 +239,9 @@ describe('Citizen application decisions', () => { await execSimpleApplicationActions( application.id, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-decisions-without-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_DECISIONS_WITHOUT_PROPOSAL' ], now ) diff --git a/frontend/src/e2e-test/specs/0_citizen/foster-parents.spec.ts b/frontend/src/e2e-test/specs/0_citizen/foster-parents.spec.ts index 06bc770493e..0222f9acc98 100644 --- a/frontend/src/e2e-test/specs/0_citizen/foster-parents.spec.ts +++ b/frontend/src/e2e-test/specs/0_citizen/foster-parents.spec.ts @@ -118,10 +118,10 @@ test('Foster parent can create a daycare application and accept a daycare decisi await execSimpleApplicationActions( applicationId, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-decisions-without-proposal', - 'confirm-decision-mailed' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_DECISIONS_WITHOUT_PROPOSAL', + 'CONFIRM_DECISION_MAILED' ], mockedNow ) @@ -193,10 +193,10 @@ test('Foster parent can create a daycare application and accept a daycare decisi await execSimpleApplicationActions( applicationId, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-decisions-without-proposal', - 'confirm-decision-mailed' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_DECISIONS_WITHOUT_PROPOSAL', + 'CONFIRM_DECISION_MAILED' ], HelsinkiDateTime.now() // TODO: use mock clock ) diff --git a/frontend/src/e2e-test/specs/5_employee/application-attachments.spec.ts b/frontend/src/e2e-test/specs/5_employee/application-attachments.spec.ts index ea295defc4a..4fdf4164883 100644 --- a/frontend/src/e2e-test/specs/5_employee/application-attachments.spec.ts +++ b/frontend/src/e2e-test/specs/5_employee/application-attachments.spec.ts @@ -97,9 +97,9 @@ describe('Employee application attachments', () => { await execSimpleApplicationActions( applicationFixtureId, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-placement-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_PLACEMENT_PROPOSAL' ], HelsinkiDateTime.now() // TODO: use mock clock ) @@ -152,9 +152,9 @@ describe('Employee application attachments', () => { await execSimpleApplicationActions( applicationId, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-placement-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_PLACEMENT_PROPOSAL' ], HelsinkiDateTime.now() // TODO: use mock clock ) diff --git a/frontend/src/e2e-test/specs/5_employee/application-details.spec.ts b/frontend/src/e2e-test/specs/5_employee/application-details.spec.ts index dc6c0ee1764..18c7af95cee 100644 --- a/frontend/src/e2e-test/specs/5_employee/application-details.spec.ts +++ b/frontend/src/e2e-test/specs/5_employee/application-details.spec.ts @@ -157,7 +157,7 @@ describe('Application details', () => { await execSimpleApplicationAction( restrictedDetailsGuardianApplication.id, - 'move-to-waiting-placement', + 'MOVE_TO_WAITING_PLACEMENT', HelsinkiDateTime.now() // TODO: use mock clock ) const preferredStartDate = @@ -173,7 +173,7 @@ describe('Application details', () => { }) await execSimpleApplicationAction( restrictedDetailsGuardianApplication.id, - 'send-decisions-without-proposal', + 'SEND_DECISIONS_WITHOUT_PROPOSAL', HelsinkiDateTime.now() // TODO: use mock clock ) @@ -202,7 +202,7 @@ describe('Application details', () => { await execSimpleApplicationAction( singleParentApplication.id, - 'move-to-waiting-placement', + 'MOVE_TO_WAITING_PLACEMENT', HelsinkiDateTime.now() // TODO: use mock clock ) const preferredStartDate = @@ -218,7 +218,7 @@ describe('Application details', () => { }) await execSimpleApplicationAction( singleParentApplication.id, - 'send-decisions-without-proposal', + 'SEND_DECISIONS_WITHOUT_PROPOSAL', HelsinkiDateTime.now() // TODO: use mock clock ) diff --git a/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts b/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts index b9992ecab27..16870203f9d 100755 --- a/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts +++ b/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts @@ -87,9 +87,9 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-decisions-without-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_DECISIONS_WITHOUT_PROPOSAL' ], mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) ) @@ -113,9 +113,9 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-decisions-without-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_DECISIONS_WITHOUT_PROPOSAL' ], mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) ) @@ -142,7 +142,7 @@ describe('Application transitions', () => { await createApplications({ body: [fixture] }) await execSimpleApplicationActions( applicationId, - ['move-to-waiting-placement', 'create-default-placement-plan'], + ['MOVE_TO_WAITING_PLACEMENT', 'CREATE_DEFAULT_PLACEMENT_PLAN'], mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) ) @@ -170,7 +170,7 @@ describe('Application transitions', () => { await createApplications({ body: [fixture] }) await execSimpleApplicationActions( applicationId, - ['move-to-waiting-placement', 'create-default-placement-plan'], + ['MOVE_TO_WAITING_PLACEMENT', 'CREATE_DEFAULT_PLACEMENT_PLAN'], mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) ) @@ -239,7 +239,7 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, - ['move-to-waiting-placement'], + ['MOVE_TO_WAITING_PLACEMENT'], mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) ) @@ -295,7 +295,7 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, - ['move-to-waiting-placement'], + ['MOVE_TO_WAITING_PLACEMENT'], mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) ) @@ -331,7 +331,7 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, - ['move-to-waiting-placement'], + ['MOVE_TO_WAITING_PLACEMENT'], HelsinkiDateTime.fromLocal(mockedTime, LocalTime.of(13, 40)) ) @@ -358,7 +358,7 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, - ['send-decisions-without-proposal'], + ['SEND_DECISIONS_WITHOUT_PROPOSAL'], HelsinkiDateTime.fromLocal(mockedTime, LocalTime.of(13, 41)) ) @@ -393,7 +393,7 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, - ['move-to-waiting-placement'], + ['MOVE_TO_WAITING_PLACEMENT'], HelsinkiDateTime.fromLocal(mockedTime, LocalTime.of(13, 40)) ) @@ -421,7 +421,7 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, - ['send-decisions-without-proposal'], + ['SEND_DECISIONS_WITHOUT_PROPOSAL'], HelsinkiDateTime.fromLocal(mockedTime, LocalTime.of(13, 41)) ) @@ -454,18 +454,18 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-placement-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_PLACEMENT_PROPOSAL' ], mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) ) await execSimpleApplicationActions( applicationId2, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-placement-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_PLACEMENT_PROPOSAL' ], mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) ) @@ -512,7 +512,7 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, - ['confirm-decision-mailed'], + ['CONFIRM_DECISION_MAILED'], mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) ) @@ -535,9 +535,9 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-placement-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_PLACEMENT_PROPOSAL' ], now ) @@ -583,9 +583,9 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-placement-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_PLACEMENT_PROPOSAL' ], mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) ) diff --git a/frontend/src/e2e-test/specs/5_employee/applications.spec.ts b/frontend/src/e2e-test/specs/5_employee/applications.spec.ts index 2cea047a987..c709977419c 100644 --- a/frontend/src/e2e-test/specs/5_employee/applications.spec.ts +++ b/frontend/src/e2e-test/specs/5_employee/applications.spec.ts @@ -52,9 +52,9 @@ describe('Applications', () => { await execSimpleApplicationActions( application.id, [ - 'move-to-waiting-placement', - 'create-default-placement-plan', - 'send-decisions-without-proposal' + 'MOVE_TO_WAITING_PLACEMENT', + 'CREATE_DEFAULT_PLACEMENT_PLAN', + 'SEND_DECISIONS_WITHOUT_PROPOSAL' ], HelsinkiDateTime.now() // TODO: use mock clock ) diff --git a/frontend/src/e2e-test/specs/5_employee/feature-flag-decision-draft-multiple-units.spec.ts b/frontend/src/e2e-test/specs/5_employee/feature-flag-decision-draft-multiple-units.spec.ts index f0f26967fbd..3bb92ccdda3 100755 --- a/frontend/src/e2e-test/specs/5_employee/feature-flag-decision-draft-multiple-units.spec.ts +++ b/frontend/src/e2e-test/specs/5_employee/feature-flag-decision-draft-multiple-units.spec.ts @@ -80,7 +80,7 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, - ['move-to-waiting-placement'], + ['MOVE_TO_WAITING_PLACEMENT'], HelsinkiDateTime.fromLocal(mockedTime, LocalTime.of(13, 40)) ) @@ -107,7 +107,7 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, - ['send-decisions-without-proposal'], + ['SEND_DECISIONS_WITHOUT_PROPOSAL'], HelsinkiDateTime.fromLocal(mockedTime, LocalTime.of(13, 41)) ) @@ -142,7 +142,7 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, - ['move-to-waiting-placement'], + ['MOVE_TO_WAITING_PLACEMENT'], HelsinkiDateTime.fromLocal(mockedTime, LocalTime.of(13, 40)) ) @@ -170,7 +170,7 @@ describe('Application transitions', () => { await execSimpleApplicationActions( applicationId, - ['send-decisions-without-proposal'], + ['SEND_DECISIONS_WITHOUT_PROPOSAL'], HelsinkiDateTime.fromLocal(mockedTime, LocalTime.of(13, 41)) ) diff --git a/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DevApi.kt b/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DevApi.kt index 8e51a239e6d..89b4cff01cd 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DevApi.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/shared/dev/DevApi.kt @@ -16,6 +16,7 @@ import fi.espoo.evaka.application.ApplicationStateService import fi.espoo.evaka.application.ApplicationStatus import fi.espoo.evaka.application.ApplicationType import fi.espoo.evaka.application.DaycarePlacementPlan +import fi.espoo.evaka.application.SimpleApplicationAction import fi.espoo.evaka.application.fetchApplicationDetails import fi.espoo.evaka.assistance.DaycareAssistanceLevel import fi.espoo.evaka.assistance.OtherAssistanceMeasureType @@ -792,23 +793,12 @@ UPDATE placement SET end_date = ${bind(req.endDate)}, termination_requested_date db: Database, clock: EvakaClock, @PathVariable applicationId: ApplicationId, - @PathVariable action: String, + @PathVariable action: SimpleApplicationAction, ) { - val simpleActions = - mapOf( - "move-to-waiting-placement" to applicationStateService::moveToWaitingPlacement, - "cancel-application" to applicationStateService::cancelApplication, - "send-decisions-without-proposal" to - applicationStateService::sendDecisionsWithoutProposal, - "send-placement-proposal" to applicationStateService::sendPlacementProposal, - "confirm-decision-mailed" to applicationStateService::confirmDecisionMailed, - ) - - val actionFn = simpleActions[action] ?: throw NotFound("Action not recognized") db.connect { dbc -> dbc.transaction { tx -> tx.ensureFakeAdminExists() - actionFn.invoke(tx, fakeAdmin, clock, applicationId) + applicationStateService.doSimpleAction(tx, fakeAdmin, clock, action, applicationId) } } runAllAsyncJobs(clock) @@ -2130,6 +2120,7 @@ data class DevApplicationWithForm( val childId: ChildId, val origin: ApplicationOrigin, val checkedByAdmin: Boolean, + val confidential: Boolean?, val hideFromGuardian: Boolean, val transferApplication: Boolean, val allowOtherGuardianAccess: Boolean = true, From 690b2c1f857bcb8463610d4e844781abb5cd5cb5 Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Mon, 2 Dec 2024 16:08:58 +0200 Subject: [PATCH 06/13] Add verify action to application view --- .../ApplicationActionsBar.tsx | 57 ++++++++++++++++++- .../defaults/employee/i18n/fi.tsx | 1 + 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/frontend/src/employee-frontend/components/application-page/ApplicationActionsBar.tsx b/frontend/src/employee-frontend/components/application-page/ApplicationActionsBar.tsx index 87e8c1612cf..b516cbea491 100755 --- a/frontend/src/employee-frontend/components/application-page/ApplicationActionsBar.tsx +++ b/frontend/src/employee-frontend/components/application-page/ApplicationActionsBar.tsx @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: LGPL-2.1-or-later -import React from 'react' +import React, { useState } from 'react' import { useNavigate } from 'react-router' import styled from 'styled-components' @@ -13,17 +13,25 @@ import { } from 'lib-common/generated/api-types/application' import { AsyncButton } from 'lib-components/atoms/buttons/AsyncButton' import { LegacyButton } from 'lib-components/atoms/buttons/LegacyButton' +import Radio from 'lib-components/atoms/form/Radio' import StickyFooter from 'lib-components/layout/StickyFooter' +import { + FixedSpaceColumn, + FixedSpaceRow +} from 'lib-components/layout/flex-helpers' +import { Label } from 'lib-components/typography' import { Gap } from 'lib-components/white-space' import { sendApplication, + setApplicationVerified, updateApplication } from '../../generated/api-clients/application' import { useTranslation } from '../../state/i18n' const updateApplicationResult = wrapResult(updateApplication) const sendApplicationResult = wrapResult(sendApplication) +const setApplicationVerifiedResult = wrapResult(setApplicationVerified) type Props = { applicationStatus: ApplicationStatus @@ -45,7 +53,54 @@ export default React.memo(function ApplicationActionsBar({ const navigate = useNavigate() const { i18n } = useTranslation() + const [confidential, setConfidential] = useState(null) + const actions = [ + { + id: 'set-verified', + enabled: + !editing && + !editedApplication.checkedByAdmin && + applicationStatus === 'WAITING_PLACEMENT', + component: ( + + {editedApplication.confidential === null && ( + + + + setConfidential(true)} + data-qa="confidential-yes" + /> + setConfidential(false)} + data-qa="confidential-no" + /> + + + )} + + setApplicationVerifiedResult({ + applicationId: editedApplication.id, + confidential: confidential + }) + } + disabled={ + editedApplication.confidential === null && confidential === null + } + onSuccess={reloadApplication} + text={i18n.applications.actions.setVerified} + primary + data-qa="set-verified" + /> + + ) + }, { id: 'start-editing', enabled: diff --git a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx index 70fbb8991b4..f945d257218 100755 --- a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx +++ b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx @@ -409,6 +409,7 @@ export const fi = { CANCELLED: 'Poistettu käsittelystä', ALL: 'Kaikki' }, + selectConfidentialityLabel: 'Onko hakemus salassapidettävä?', selectAll: 'Valitse kaikki', unselectAll: 'Poista valinnat', transfer: 'Siirtohakemus', From d4dd3c0c707cf73954a9346cf443a666b95052d9 Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Mon, 2 Dec 2024 16:09:49 +0200 Subject: [PATCH 07/13] Reload applications in search view when returning to that browser tab --- .../components/applications/ApplicationsPage.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frontend/src/employee-frontend/components/applications/ApplicationsPage.tsx b/frontend/src/employee-frontend/components/applications/ApplicationsPage.tsx index 793c24d0268..1556110ef21 100755 --- a/frontend/src/employee-frontend/components/applications/ApplicationsPage.tsx +++ b/frontend/src/employee-frontend/components/applications/ApplicationsPage.tsx @@ -131,6 +131,19 @@ export default React.memo(function ApplicationsPage() { loadApplications() }, [loadApplications]) + useEffect(() => { + const onVisibilityChange = () => { + if (document.visibilityState === 'visible') { + void loadApplications() + } + } + + document.addEventListener('visibilitychange', onVisibilityChange) + + return () => + document.removeEventListener('visibilitychange', onVisibilityChange) + }, [loadApplications]) + // when changing filters, sorting, etc, set page to 1 and reload useEffect(() => { setPage(1) From 43c27e39ab9d2286ec75318d984f61fabb2acc62 Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Mon, 2 Dec 2024 16:22:53 +0200 Subject: [PATCH 08/13] check action on application list --- .../applications/ApplicationActions.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx b/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx index 732bbc12849..c7ce88fc8b6 100755 --- a/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx +++ b/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx @@ -104,15 +104,19 @@ export default React.memo(function ApplicationActions({ } }, { - id: 'create-placement-plan', - label: i18n.applications.actions.createPlacementPlan, - enabled: - application.checkedByAdmin && - application.status === 'WAITING_PLACEMENT', + id: application.checkedByAdmin ? 'create-placement-plan' : 'check', + label: application.checkedByAdmin + ? i18n.applications.actions.createPlacementPlan + : i18n.applications.actions.check, + enabled: application.status === 'WAITING_PLACEMENT', disabled: actionInFlight, onClick: () => { setActionInFlight(true) - void navigate(`/applications/${application.id}/placement`) + if (application.checkedByAdmin) { + void navigate(`/applications/${application.id}/placement`) + } else { + void navigate(`/applications/${application.id}`) + } }, primaryStatus: 'WAITING_PLACEMENT' }, From abd93dab14acd6423392879944d7a60dc99699d7 Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Mon, 2 Dec 2024 17:06:16 +0200 Subject: [PATCH 09/13] add e2e tests --- .../pages/admin/application-workbench-page.ts | 21 +-- .../applications/application-read-view.ts | 10 ++ .../application-transitions.spec.ts | 126 ++++++++++++++++++ frontend/src/e2e-test/utils/page.ts | 4 + .../ApplicationActionsBar.tsx | 2 +- 5 files changed, 152 insertions(+), 11 deletions(-) diff --git a/frontend/src/e2e-test/pages/admin/application-workbench-page.ts b/frontend/src/e2e-test/pages/admin/application-workbench-page.ts index 3f9cf870403..7c05b9b9b28 100644 --- a/frontend/src/e2e-test/pages/admin/application-workbench-page.ts +++ b/frontend/src/e2e-test/pages/admin/application-workbench-page.ts @@ -9,7 +9,6 @@ import { UUID } from 'lib-common/types' import { waitUntilTrue } from '../../utils' import { Checkbox, Combobox, Page, Element } from '../../utils/page' -import ApplicationListView from '../employee/applications/application-list-view' import { PlacementDraftPage } from '../employee/placement-draft-page' import ApplicationDetailsPage from './application-details-page' @@ -115,17 +114,19 @@ export class ApplicationWorkbenchPage { return new ApplicationDetailsPage(popup) } - async openDaycarePlacementDialogById(id: string) { - await this.getApplicationById(id) - .findByDataQa('primary-action-create-placement-plan') - .click() - return new PlacementDraftPage(this.page) + getPrimaryActionCheck(id: string) { + return this.getApplicationById(id).findByDataQa('primary-action-check') + } + + getPrimaryActionCreatePlacementPlan(id: string) { + return this.getApplicationById(id).findByDataQa( + 'primary-action-create-placement-plan' + ) } - async verifyApplication(applicationId: string) { - const list = new ApplicationListView(this.page) - await list.actionsMenu(applicationId).click() - await list.actionsMenuItems.setVerified.click() + async openDaycarePlacementDialogById(id: string) { + await this.getPrimaryActionCreatePlacementPlan(id).click() + return new PlacementDraftPage(this.page) } async clickApplicationCheckbox(applicationId: string) { diff --git a/frontend/src/e2e-test/pages/employee/applications/application-read-view.ts b/frontend/src/e2e-test/pages/employee/applications/application-read-view.ts index 4cfc735fbd1..959d8a7ae8a 100644 --- a/frontend/src/e2e-test/pages/employee/applications/application-read-view.ts +++ b/frontend/src/e2e-test/pages/employee/applications/application-read-view.ts @@ -30,6 +30,9 @@ export default class ApplicationReadView { #sendMessageButton: Element notesList: Element #title: Element + confidentialRadioYes: Radio + confidentialRadioNo: Radio + setVerifiedButton: Element private notes: ElementCollection constructor(private page: Page) { this.#editButton = page.findByDataQa('edit-application') @@ -43,6 +46,13 @@ export default class ApplicationReadView { this.notesList = page.findByDataQa('application-notes-list') this.#title = this.page.findByDataQa('application-title').find('h1') this.notes = this.notesList.findAllByDataQa('note-container') + this.confidentialRadioYes = new Radio( + this.page.findByDataQa('confidential-yes') + ) + this.confidentialRadioNo = new Radio( + this.page.findByDataQa('confidential-no') + ) + this.setVerifiedButton = this.page.findByDataQa('set-verified-btn') } async waitUntilLoaded() { diff --git a/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts b/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts index 16870203f9d..7915f3c55f3 100755 --- a/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts +++ b/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts @@ -186,6 +186,132 @@ describe('Application transitions', () => { await applicationReadView.assertApplicationStatus('Odottaa postitusta') }) + test('Application with e.g. diet must be checked before placing', async () => { + const preferredStartDate = mockedTime + const fixture: DevApplicationWithForm = { + ...applicationFixture( + testChild2, + familyWithTwoGuardians.guardian, + undefined, + 'DAYCARE', + null, + [testDaycare.id], + true, + 'SENT', + preferredStartDate + ) + } + fixture.form.child.diet = 'Vegaani' + + const applicationId = fixture.id + + await createApplications({ body: [fixture] }) + + await execSimpleApplicationActions( + applicationId, + ['MOVE_TO_WAITING_PLACEMENT'], + mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) + ) + + await employeeLogin(page, serviceWorker) + await page.goto(ApplicationListView.url) + await applicationWorkbench.waitUntilLoaded() + await applicationWorkbench.openPlacementQueue() + + await applicationWorkbench + .getPrimaryActionCheck(applicationId) + .waitUntilVisible() + await applicationWorkbench + .getPrimaryActionCreatePlacementPlan(applicationId) + .waitUntilHidden() + + await applicationWorkbench.getPrimaryActionCheck(applicationId).click() + + const applicationReadView = new ApplicationReadView(page) + await applicationReadView.setVerifiedButton.waitUntilVisible() + // confidentiality has been set automatically + await applicationReadView.confidentialRadioYes.waitUntilHidden() + await applicationReadView.confidentialRadioNo.waitUntilHidden() + + await applicationReadView.setVerifiedButton.click() + await page.goBack() + + await applicationWorkbench + .getPrimaryActionCreatePlacementPlan(applicationId) + .waitUntilVisible() + await applicationWorkbench + .getPrimaryActionCheck(applicationId) + .waitUntilHidden() + + const placementDraftPage = + await applicationWorkbench.openDaycarePlacementDialogById(applicationId) + await placementDraftPage.waitUntilLoaded() + }) + + test('Confidentiality must be set on an application before placing if other info is the only potential source of confidentiality', async () => { + const preferredStartDate = mockedTime + const fixture: DevApplicationWithForm = { + ...applicationFixture( + testChild2, + familyWithTwoGuardians.guardian, + undefined, + 'DAYCARE', + null, + [testDaycare.id], + true, + 'SENT', + preferredStartDate + ) + } + fixture.form.otherInfo = 'Eipä ihmeempiä' + + const applicationId = fixture.id + + await createApplications({ body: [fixture] }) + + await execSimpleApplicationActions( + applicationId, + ['MOVE_TO_WAITING_PLACEMENT'], + mockedTime.toHelsinkiDateTime(LocalTime.of(12, 0)) + ) + + await employeeLogin(page, serviceWorker) + await page.goto(ApplicationListView.url) + await applicationWorkbench.waitUntilLoaded() + await applicationWorkbench.openPlacementQueue() + + await applicationWorkbench + .getPrimaryActionCheck(applicationId) + .waitUntilVisible() + await applicationWorkbench + .getPrimaryActionCreatePlacementPlan(applicationId) + .waitUntilHidden() + + await applicationWorkbench.getPrimaryActionCheck(applicationId).click() + + const applicationReadView = new ApplicationReadView(page) + await applicationReadView.setVerifiedButton.waitUntilVisible() + await applicationReadView.setVerifiedButton.assertDisabled(true) + + await applicationReadView.confidentialRadioYes.click() + await applicationReadView.confidentialRadioNo.click() + + await applicationReadView.setVerifiedButton.assertDisabled(false) + await applicationReadView.setVerifiedButton.click() + await page.goBack() + + await applicationWorkbench + .getPrimaryActionCreatePlacementPlan(applicationId) + .waitUntilVisible() + await applicationWorkbench + .getPrimaryActionCheck(applicationId) + .waitUntilHidden() + + const placementDraftPage = + await applicationWorkbench.openDaycarePlacementDialogById(applicationId) + await placementDraftPage.waitUntilLoaded() + }) + test('Placement dialog works', async () => { const preferredStartDate = mockedTime diff --git a/frontend/src/e2e-test/utils/page.ts b/frontend/src/e2e-test/utils/page.ts index 7cb475b63d6..d73adee79c7 100644 --- a/frontend/src/e2e-test/utils/page.ts +++ b/frontend/src/e2e-test/utils/page.ts @@ -43,6 +43,10 @@ export class Page { return this.page.reload() } + async goBack() { + return this.page.goBack() + } + async close() { return this.page.close() } diff --git a/frontend/src/employee-frontend/components/application-page/ApplicationActionsBar.tsx b/frontend/src/employee-frontend/components/application-page/ApplicationActionsBar.tsx index b516cbea491..19f0f09d4a1 100755 --- a/frontend/src/employee-frontend/components/application-page/ApplicationActionsBar.tsx +++ b/frontend/src/employee-frontend/components/application-page/ApplicationActionsBar.tsx @@ -96,7 +96,7 @@ export default React.memo(function ApplicationActionsBar({ onSuccess={reloadApplication} text={i18n.applications.actions.setVerified} primary - data-qa="set-verified" + data-qa="set-verified-btn" /> ) From 54047092101ff3edc2f824fa98e951c9ba2d0297 Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Tue, 3 Dec 2024 13:12:08 +0200 Subject: [PATCH 10/13] setting confidentiality when cancelling application --- .../applications/ApplicationActions.tsx | 97 +++++++++++++++---- .../generated/api-clients/application.ts | 21 ++++ .../generated/api-types/application.ts | 1 - .../defaults/employee/i18n/fi.tsx | 1 + ...ApplicationStateServiceIntegrationTests.kt | 6 +- .../ApplicationControllerCitizen.kt | 8 +- .../application/ApplicationControllerV2.kt | 21 ++++ .../application/ApplicationStateService.kt | 15 ++- 8 files changed, 142 insertions(+), 28 deletions(-) diff --git a/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx b/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx index c7ce88fc8b6..98656574402 100755 --- a/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx +++ b/frontend/src/employee-frontend/components/applications/ApplicationActions.tsx @@ -7,11 +7,20 @@ import { useNavigate } from 'react-router' import styled from 'styled-components' import { ApplicationSummary } from 'lib-common/generated/api-types/application' +import Radio from 'lib-components/atoms/form/Radio' +import { + FixedSpaceColumn, + FixedSpaceRow +} from 'lib-components/layout/flex-helpers' import InfoModal from 'lib-components/molecules/modals/InfoModal' +import { Label } from 'lib-components/typography' import ActionCheckbox from '../../components/applications/ActionCheckbox' import PrimaryAction from '../../components/applications/PrimaryAction' -import { simpleApplicationAction } from '../../generated/api-clients/application' +import { + cancelApplication, + simpleApplicationAction +} from '../../generated/api-clients/application' import { useTranslation } from '../../state/i18n' import { UIContext } from '../../state/ui' import EllipsisMenu, { MenuItem } from '../common/EllipsisMenu' @@ -227,31 +236,79 @@ export default React.memo(function ApplicationActions({ {confirmingApplicationCancel && ( - { - setActionInFlight(true) - handlePromise( - simpleApplicationAction({ - applicationId: application.id, - action: 'CANCEL_APPLICATION' - }) - ) - }, - label: i18n.common.confirm - }} - reject={{ - action: () => setConfirmingApplicationCancel(false), - label: i18n.common.cancel - }} + setActionInFlight(true)} + handlePromise={handlePromise} + onClose={() => setConfirmingApplicationCancel(false)} /> )} ) }) +const ConfirmCancelApplicationModal = React.memo( + function ConfirmCancelApplicationModal({ + application, + onSubmit, + handlePromise, + onClose + }: { + application: ApplicationSummary + onSubmit: () => void + handlePromise: (promise: Promise) => void + onClose: () => void + }) { + const { i18n } = useTranslation() + const [confidential, setConfidential] = useState(null) + return ( + { + onSubmit() + handlePromise( + cancelApplication({ + applicationId: application.id, + confidential + }) + ) + }, + label: i18n.common.confirm, + disabled: application.confidential === null && confidential === null + }} + reject={{ + action: onClose, + label: i18n.common.cancel + }} + > + {application.confidential === null && ( + + + + setConfidential(true)} + data-qa="confidential-yes" + /> + setConfidential(false)} + data-qa="confidential-no" + /> + + + )} + + ) + } +) + const ActionsContainer = styled.div` display: flex; flex-direction: row; diff --git a/frontend/src/employee-frontend/generated/api-clients/application.ts b/frontend/src/employee-frontend/generated/api-clients/application.ts index 204298dfe50..7c37a5b3365 100644 --- a/frontend/src/employee-frontend/generated/api-clients/application.ts +++ b/frontend/src/employee-frontend/generated/api-clients/application.ts @@ -78,6 +78,27 @@ export async function acceptPlacementProposal( } +/** +* Generated from fi.espoo.evaka.application.ApplicationControllerV2.cancelApplication +*/ +export async function cancelApplication( + request: { + applicationId: UUID, + confidential?: boolean | null + } +): Promise { + const params = createUrlSearchParams( + ['confidential', request.confidential?.toString()] + ) + const { data: json } = await client.request>({ + url: uri`/employee/applications/${request.applicationId}/actions/cancel-application`.toString(), + method: 'POST', + params + }) + return json +} + + /** * Generated from fi.espoo.evaka.application.ApplicationControllerV2.createPaperApplication */ diff --git a/frontend/src/lib-common/generated/api-types/application.ts b/frontend/src/lib-common/generated/api-types/application.ts index 15016ec22ef..2bb29dcd6e1 100644 --- a/frontend/src/lib-common/generated/api-types/application.ts +++ b/frontend/src/lib-common/generated/api-types/application.ts @@ -751,7 +751,6 @@ export interface SiblingBasis { export type SimpleApplicationAction = | 'MOVE_TO_WAITING_PLACEMENT' | 'RETURN_TO_SENT' - | 'CANCEL_APPLICATION' | 'CANCEL_PLACEMENT_PLAN' | 'SEND_DECISIONS_WITHOUT_PROPOSAL' | 'SEND_PLACEMENT_PROPOSAL' diff --git a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx index f945d257218..fcb696e7c5f 100755 --- a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx +++ b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx @@ -341,6 +341,7 @@ export const fi = { cancelApplication: 'Poista käsittelystä', cancelApplicationConfirm: 'Haluatko varmasti poistaa hakemuksen käsittelystä?', + cancelApplicationConfidentiality: 'Onko hakemus salassapidettävä?', check: 'Tarkista', setVerified: 'Merkitse tarkistetuksi', createPlacementPlan: 'Sijoita', 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 cb40b6e3f09..2939cbab98b 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/application/ApplicationStateServiceIntegrationTests.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/application/ApplicationStateServiceIntegrationTests.kt @@ -676,7 +676,7 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor } db.transaction { tx -> // when - service.cancelApplication(tx, serviceWorker, clock, applicationId) + service.cancelApplication(tx, serviceWorker, clock, applicationId, null) } db.read { tx -> // then @@ -698,7 +698,7 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor } db.transaction { tx -> // when - service.cancelApplication(tx, serviceWorker, clock, applicationId) + service.cancelApplication(tx, serviceWorker, clock, applicationId, null) } db.read { tx -> // then @@ -738,7 +738,7 @@ class ApplicationStateServiceIntegrationTests : FullApplicationTest(resetDbBefor preferredStartDate = LocalDate.of(2020, 8, 1), ) service.sendApplication(tx, serviceWorker, clock, applicationId) - service.cancelApplication(tx, serviceWorker, clock, applicationId) + service.cancelApplication(tx, serviceWorker, clock, applicationId, null) val process = tx.getArchiveProcessByApplicationId(applicationId) assertNotNull(process) diff --git a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerCitizen.kt b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerCitizen.kt index 82a262844a7..b02cc70de5e 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerCitizen.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerCitizen.kt @@ -398,7 +398,13 @@ class ApplicationControllerCitizen( tx.deleteApplication(applicationId) } ApplicationStatus.SENT -> - applicationStateService.cancelApplication(tx, user, clock, applicationId) + applicationStateService.cancelApplication( + tx, + user, + clock, + applicationId, + null, + ) else -> throw BadRequest( "Only applications which are not yet being processed can be cancelled" diff --git a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt index 08c4e0eeb40..96653032002 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationControllerV2.kt @@ -658,6 +658,27 @@ class ApplicationControllerV2( } } + @PostMapping("/{applicationId}/actions/cancel-application") + fun cancelApplication( + db: Database, + user: AuthenticatedUser.Employee, + clock: EvakaClock, + @PathVariable applicationId: ApplicationId, + @RequestParam confidential: Boolean?, + ) { + db.connect { dbc -> + dbc.transaction { tx -> + applicationStateService.cancelApplication( + tx, + user, + clock, + applicationId, + confidential, + ) + } + } + } + @PostMapping("/{applicationId}/actions/{action}") fun simpleApplicationAction( db: Database, 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 d33fb8b86fd..4acfa230f53 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt @@ -97,7 +97,6 @@ import org.springframework.stereotype.Service enum class SimpleApplicationAction { MOVE_TO_WAITING_PLACEMENT, RETURN_TO_SENT, - CANCEL_APPLICATION, CANCEL_PLACEMENT_PLAN, SEND_DECISIONS_WITHOUT_PROPOSAL, SEND_PLACEMENT_PROPOSAL, @@ -127,8 +126,6 @@ class ApplicationStateService( SimpleApplicationAction.MOVE_TO_WAITING_PLACEMENT -> moveToWaitingPlacement(tx, user, clock, applicationId) SimpleApplicationAction.RETURN_TO_SENT -> returnToSent(tx, user, clock, applicationId) - SimpleApplicationAction.CANCEL_APPLICATION -> - cancelApplication(tx, user, clock, applicationId) SimpleApplicationAction.CANCEL_PLACEMENT_PLAN -> cancelPlacementPlan(tx, user, clock, applicationId) SimpleApplicationAction.SEND_DECISIONS_WITHOUT_PROPOSAL -> @@ -518,6 +515,7 @@ class ApplicationStateService( user: AuthenticatedUser, clock: EvakaClock, applicationId: ApplicationId, + confidential: Boolean?, ) { accessControl.requirePermissionFor( tx, @@ -529,6 +527,17 @@ class ApplicationStateService( val application = getApplication(tx, applicationId) verifyStatus(application, setOf(SENT, WAITING_PLACEMENT)) + + if (application.confidential == null) { + when { + user is AuthenticatedUser.Citizen -> + tx.setApplicationConfidentiality(applicationId, true) + confidential != null -> + tx.setApplicationConfidentiality(applicationId, confidential) + else -> throw BadRequest("Confidentiality must be set") + } + } else if (confidential != null) throw BadRequest("Confidentiality is already set") + tx.updateApplicationStatus(application.id, CANCELLED, user.evakaUserId, clock.now()) tx.getArchiveProcessByApplicationId(applicationId)?.also { process -> From bfcaf3519f842f96d13f9921e9aba8f0d66b2dd6 Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Tue, 3 Dec 2024 14:43:10 +0200 Subject: [PATCH 11/13] fix modal behaviour when rendered inside a clickable element --- frontend/src/lib-components/molecules/modals/BaseModal.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/lib-components/molecules/modals/BaseModal.tsx b/frontend/src/lib-components/molecules/modals/BaseModal.tsx index 405bcc9e4ca..87494727622 100644 --- a/frontend/src/lib-components/molecules/modals/BaseModal.tsx +++ b/frontend/src/lib-components/molecules/modals/BaseModal.tsx @@ -44,6 +44,7 @@ export default React.memo(function BaseModal(props: Props) { className={props.className} zIndex={props.zIndex} data-qa={props['data-qa']} + onClick={(e) => e.stopPropagation()} > ` + cursor: default; align-items: center; display: flex; flex-direction: column; From e74e7d0ab6284c6f6f9b303064015278d7c6777e Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Tue, 3 Dec 2024 16:20:51 +0200 Subject: [PATCH 12/13] add e2e test for cancellation --- .../applications/application-list-view.ts | 27 +++++++---- .../application-transitions.spec.ts | 45 ++++++++++++++++++- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/frontend/src/e2e-test/pages/employee/applications/application-list-view.ts b/frontend/src/e2e-test/pages/employee/applications/application-list-view.ts index d65644829bc..8eb4fed168f 100644 --- a/frontend/src/e2e-test/pages/employee/applications/application-list-view.ts +++ b/frontend/src/e2e-test/pages/employee/applications/application-list-view.ts @@ -10,7 +10,8 @@ import { MultiSelect, Page, Element, - ElementCollection + ElementCollection, + Radio } from '../../../utils/page' export default class ApplicationListView { @@ -20,11 +21,15 @@ export default class ApplicationListView { #unitFilter: MultiSelect #applications: ElementCollection actionsMenuItems: { - verify: Element - setVerified: Element createPlacement: Element createDecision: Element - acceptPlacementWihtoutDecision: Element + acceptPlacementWithoutDecision: Element + cancelApplication: Element + } + cancelConfirmation: { + confidentialRadioYes: Radio + confidentialRadioNo: Radio + submitButton: Element } specialFilterItems: { duplicate: Element @@ -44,13 +49,17 @@ export default class ApplicationListView { .find('[data-qa="table-of-applications"]') .findAll('[data-qa="table-application-row"]') this.actionsMenuItems = { - verify: this.#actionsMenuItemSelector('verify'), - setVerified: this.#actionsMenuItemSelector('set-verified'), createPlacement: this.#actionsMenuItemSelector('placement-draft'), createDecision: this.#actionsMenuItemSelector('decision'), - acceptPlacementWihtoutDecision: this.#actionsMenuItemSelector( + acceptPlacementWithoutDecision: this.#actionsMenuItemSelector( 'placement-without-decision' - ) + ), + cancelApplication: this.#actionsMenuItemSelector('cancel-application') + } + this.cancelConfirmation = { + confidentialRadioYes: new Radio(page.findByDataQa('confidential-yes')), + confidentialRadioNo: new Radio(page.findByDataQa('confidential-no')), + submitButton: page.findByDataQa('modal-okBtn') } this.specialFilterItems = { duplicate: page.findByDataQa('application-basis-DUPLICATE_APPLICATION') @@ -71,7 +80,7 @@ export default class ApplicationListView { .find('[data-qa="application-actions-menu"]') #actionsMenuItemSelector = (id: string) => - this.page.findByDataQa(`action-item-${id}`) + this.page.findByDataQa(`menu-item-${id}`) async toggleArea(areaName: string) { await this.#areaFilter.fillAndSelectFirst(areaName) diff --git a/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts b/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts index 7915f3c55f3..e8997f5d04f 100755 --- a/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts +++ b/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts @@ -293,8 +293,8 @@ describe('Application transitions', () => { await applicationReadView.setVerifiedButton.waitUntilVisible() await applicationReadView.setVerifiedButton.assertDisabled(true) - await applicationReadView.confidentialRadioYes.click() - await applicationReadView.confidentialRadioNo.click() + await applicationReadView.confidentialRadioYes.check() + await applicationReadView.confidentialRadioNo.check() await applicationReadView.setVerifiedButton.assertDisabled(false) await applicationReadView.setVerifiedButton.click() @@ -312,6 +312,47 @@ describe('Application transitions', () => { await placementDraftPage.waitUntilLoaded() }) + test('Confidentiality must be set on an application before cancelling if other info is the only potential source of confidentiality', async () => { + const preferredStartDate = mockedTime + const fixture: DevApplicationWithForm = { + ...applicationFixture( + testChild2, + familyWithTwoGuardians.guardian, + undefined, + 'DAYCARE', + null, + [testDaycare.id], + true, + 'SENT', + preferredStartDate + ) + } + fixture.form.otherInfo = 'Eipä ihmeempiä' + + const applicationId = fixture.id + + await createApplications({ body: [fixture] }) + + await employeeLogin(page, serviceWorker) + await page.goto(ApplicationListView.url) + await applicationWorkbench.waitUntilLoaded() + + const applicationList = new ApplicationListView(page) + await applicationList.actionsMenu(applicationId).click() + await applicationList.actionsMenuItems.cancelApplication.click() + + await applicationList.cancelConfirmation.submitButton.assertDisabled(true) + await applicationList.cancelConfirmation.confidentialRadioYes.waitUntilVisible() + await applicationList.cancelConfirmation.confidentialRadioYes.check() + await applicationList.cancelConfirmation.submitButton.assertDisabled(false) + await applicationList.cancelConfirmation.submitButton.click() + + await applicationWorkbench.applicationsAll.click() + await applicationWorkbench + .getApplicationListItem(applicationId) + .assertText((text) => text.includes('Poistettu käsittelystä')) + }) + test('Placement dialog works', async () => { const preferredStartDate = mockedTime From 9a97d6245247035e09ef110215f4f24e9d56c59f Mon Sep 17 00:00:00 2001 From: Joosa Kurvinen Date: Thu, 5 Dec 2024 16:11:48 +0200 Subject: [PATCH 13/13] remove unnecessary asserts --- .../e2e-test/specs/5_employee/application-transitions.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts b/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts index e8997f5d04f..b7f971dde9c 100755 --- a/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts +++ b/frontend/src/e2e-test/specs/5_employee/application-transitions.spec.ts @@ -342,9 +342,7 @@ describe('Application transitions', () => { await applicationList.actionsMenuItems.cancelApplication.click() await applicationList.cancelConfirmation.submitButton.assertDisabled(true) - await applicationList.cancelConfirmation.confidentialRadioYes.waitUntilVisible() await applicationList.cancelConfirmation.confidentialRadioYes.check() - await applicationList.cancelConfirmation.submitButton.assertDisabled(false) await applicationList.cancelConfirmation.submitButton.click() await applicationWorkbench.applicationsAll.click()