From d91c482ccf690f8a44dd6e3a18513b2ded4534e3 Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Wed, 11 Dec 2024 13:01:03 +0200 Subject: [PATCH] =?UTF-8?q?N=C3=A4ytet=C3=A4=C3=A4n=20hakemuksen=20muokkaa?= =?UTF-8?q?jan=20nimi=20hakemusn=C3=A4kym=C3=A4ss=C3=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor/verification/BasicsSection.tsx | 4 +- frontend/src/e2e-test/dev-api/fixtures.ts | 9 +- frontend/src/e2e-test/generated/api-types.ts | 12 +- .../ApplicationStatusSection.tsx | 7 +- .../generated/api-types/application.ts | 10 +- .../defaults/employee/i18n/fi.tsx | 1 + .../kotlin/fi/espoo/evaka/TestFixtures.kt | 6 +- .../GetApplicationIntegrationTests.kt | 2 +- .../evaka/shared/db/SchemaConventionsTest.kt | 3 - .../evaka/shared/job/ScheduledJobsTest.kt | 2 +- .../kotlin/fi/espoo/evaka/test/TestData.kt | 20 ++- .../fi/espoo/evaka/application/Application.kt | 6 +- .../evaka/application/ApplicationQueries.kt | 117 ++++++++++++++---- .../application/ApplicationStateService.kt | 76 +++++++++--- .../kotlin/fi/espoo/evaka/espoo/bi/EspooBi.kt | 2 +- .../fi/espoo/evaka/espoo/bi/EspooBiModels.kt | 4 +- .../fi/espoo/evaka/pis/ParentshipQueries.kt | 2 +- .../fi/espoo/evaka/pis/PartnershipQueries.kt | 8 +- .../process/ProcessMetadataController.kt | 2 +- .../evaka/shared/dev/DataInitializers.kt | 17 ++- .../fi/espoo/evaka/shared/dev/DevApi.kt | 7 +- .../db/migration/R__application_view.sql | 8 +- .../V474__application_modified_metadata.sql | 20 +++ service/src/main/resources/migrations.txt | 1 + 24 files changed, 251 insertions(+), 95 deletions(-) create mode 100644 service/src/main/resources/db/migration/V474__application_modified_metadata.sql diff --git a/frontend/src/citizen-frontend/applications/editor/verification/BasicsSection.tsx b/frontend/src/citizen-frontend/applications/editor/verification/BasicsSection.tsx index 6f37f3d5ed1..fd89d0d6613 100644 --- a/frontend/src/citizen-frontend/applications/editor/verification/BasicsSection.tsx +++ b/frontend/src/citizen-frontend/applications/editor/verification/BasicsSection.tsx @@ -24,8 +24,8 @@ export default React.memo(function BasicsSection({ }: BasicsSectionProps) { const t = useTranslation() - const created = application.createdDate?.toLocalDate() - const modified = application.modifiedDate?.toLocalDate() + const created = application.createdAt?.toLocalDate() + const modified = application.modifiedAt?.toLocalDate() return (
diff --git a/frontend/src/e2e-test/dev-api/fixtures.ts b/frontend/src/e2e-test/dev-api/fixtures.ts index 577efc63b47..c0a095e1292 100644 --- a/frontend/src/e2e-test/dev-api/fixtures.ts +++ b/frontend/src/e2e-test/dev-api/fixtures.ts @@ -2948,11 +2948,12 @@ export const applicationFixture = ( status, transferApplication, allowOtherGuardianAccess: true, - createdDate: null, + createdAt: HelsinkiDateTime.now(), + createdBy: systemInternalUser.id, + modifiedAt: HelsinkiDateTime.now(), + modifiedBy: systemInternalUser.id, dueDate: null, - modifiedDate: null, - sentDate: null, - formModified: HelsinkiDateTime.now() + sentDate: null }) const feeThresholds = { diff --git a/frontend/src/e2e-test/generated/api-types.ts b/frontend/src/e2e-test/generated/api-types.ts index b4911ac487d..97d0428ed31 100644 --- a/frontend/src/e2e-test/generated/api-types.ts +++ b/frontend/src/e2e-test/generated/api-types.ts @@ -199,14 +199,15 @@ export interface DevApplicationWithForm { checkedByAdmin: boolean childId: PersonId confidential: boolean | null - createdDate: HelsinkiDateTime | null + createdAt: HelsinkiDateTime + createdBy: EvakaUserId dueDate: LocalDate | null form: ApplicationForm - formModified: HelsinkiDateTime guardianId: PersonId hideFromGuardian: boolean id: ApplicationId - modifiedDate: HelsinkiDateTime | null + modifiedAt: HelsinkiDateTime + modifiedBy: EvakaUserId origin: ApplicationOrigin otherGuardians: PersonId[] sentDate: LocalDate | null @@ -1180,11 +1181,10 @@ export function deserializeJsonDevAbsence(json: JsonOf): DevAbsence export function deserializeJsonDevApplicationWithForm(json: JsonOf): DevApplicationWithForm { return { ...json, - createdDate: (json.createdDate != null) ? HelsinkiDateTime.parseIso(json.createdDate) : null, + createdAt: HelsinkiDateTime.parseIso(json.createdAt), dueDate: (json.dueDate != null) ? LocalDate.parseIso(json.dueDate) : null, form: deserializeJsonApplicationForm(json.form), - formModified: HelsinkiDateTime.parseIso(json.formModified), - modifiedDate: (json.modifiedDate != null) ? HelsinkiDateTime.parseIso(json.modifiedDate) : null, + modifiedAt: HelsinkiDateTime.parseIso(json.modifiedAt), sentDate: (json.sentDate != null) ? LocalDate.parseIso(json.sentDate) : null } } diff --git a/frontend/src/employee-frontend/components/application-page/ApplicationStatusSection.tsx b/frontend/src/employee-frontend/components/application-page/ApplicationStatusSection.tsx index 9af6a50b39a..5cde554fc3c 100755 --- a/frontend/src/employee-frontend/components/application-page/ApplicationStatusSection.tsx +++ b/frontend/src/employee-frontend/components/application-page/ApplicationStatusSection.tsx @@ -48,7 +48,12 @@ export default React.memo(function ApplicationStatusSection({ - {application.modifiedDate?.format() ?? ''} + {application.modifiedAt?.format() ?? ''} + + + + + {application.modifiedBy?.name ?? ''} diff --git a/frontend/src/lib-common/generated/api-types/application.ts b/frontend/src/lib-common/generated/api-types/application.ts index 683cd502712..d765792ebec 100644 --- a/frontend/src/lib-common/generated/api-types/application.ts +++ b/frontend/src/lib-common/generated/api-types/application.ts @@ -122,7 +122,8 @@ export interface ApplicationDetails { childId: PersonId childRestricted: boolean confidential: boolean | null - createdDate: HelsinkiDateTime | null + createdAt: HelsinkiDateTime | null + createdBy: EvakaUser | null dueDate: LocalDate | null dueDateSetManuallyAt: HelsinkiDateTime | null form: ApplicationForm @@ -132,7 +133,8 @@ export interface ApplicationDetails { hasOtherGuardian: boolean hideFromGuardian: boolean id: ApplicationId - modifiedDate: HelsinkiDateTime | null + modifiedAt: HelsinkiDateTime | null + modifiedBy: EvakaUser | null origin: ApplicationOrigin otherGuardianLivesInSameAddress: boolean | null sentDate: LocalDate | null @@ -831,12 +833,12 @@ export function deserializeJsonApplicationDetails(json: JsonOf deserializeJsonApplicationAttachment(e)), - createdDate: (json.createdDate != null) ? HelsinkiDateTime.parseIso(json.createdDate) : null, + createdAt: (json.createdAt != null) ? HelsinkiDateTime.parseIso(json.createdAt) : null, dueDate: (json.dueDate != null) ? LocalDate.parseIso(json.dueDate) : null, dueDateSetManuallyAt: (json.dueDateSetManuallyAt != null) ? HelsinkiDateTime.parseIso(json.dueDateSetManuallyAt) : null, form: deserializeJsonApplicationForm(json.form), guardianDateOfDeath: (json.guardianDateOfDeath != null) ? LocalDate.parseIso(json.guardianDateOfDeath) : null, - modifiedDate: (json.modifiedDate != null) ? HelsinkiDateTime.parseIso(json.modifiedDate) : null, + modifiedAt: (json.modifiedAt != null) ? HelsinkiDateTime.parseIso(json.modifiedAt) : null, sentDate: (json.sentDate != null) ? LocalDate.parseIso(json.sentDate) : null } } diff --git a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx index 8887b09790c..4062f894f1b 100755 --- a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx +++ b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx @@ -579,6 +579,7 @@ export const fi = { origin: 'Hakemuksen lähetysmuoto', sent: 'Saapunut', modified: 'Muokattu viimeksi', + modifiedBy: 'Muokkaaja', due: 'Käsiteltävä viimeistään' }, date: { diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt index 619051f505d..70b76e01744 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt @@ -672,8 +672,10 @@ fun Database.Transaction.insertApplication( guardianId = guardian.id, guardianRestricted = false, guardianDateOfDeath = null, - createdDate = HelsinkiDateTime.now(), - modifiedDate = HelsinkiDateTime.now(), + createdAt = HelsinkiDateTime.now(), + createdBy = testDecisionMaker_1.toEvakaUser(), + modifiedAt = HelsinkiDateTime.now(), + modifiedBy = testDecisionMaker_1.toEvakaUser(), sentDate = null, dueDate = null, dueDateSetManuallyAt = null, 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 9d0be6cb542..24fbd671e90 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/application/GetApplicationIntegrationTests.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/application/GetApplicationIntegrationTests.kt @@ -229,7 +229,7 @@ class GetApplicationIntegrationTests : FullApplicationTest(resetDbBeforeEach = t db.transaction { tx -> tx.execute { sql( - "UPDATE application SET created = '2020-01-01T00:00:00Z' WHERE id = ${bind(old)}" + "UPDATE application SET created_at = '2020-01-01T00:00:00Z' WHERE id = ${bind(old)}" ) } } diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/db/SchemaConventionsTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/db/SchemaConventionsTest.kt index af50d1a3416..d007fa5d69b 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/db/SchemaConventionsTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/db/SchemaConventionsTest.kt @@ -29,7 +29,6 @@ class SchemaConventionsTest : PureJdbiTest(resetDbBeforeEach = false) { fun `creation timestamp should be called 'created_at' instead of 'created'`() { val permittedViolations = setOf( - "application", "application_form", "application_note", "application_other_guardian", @@ -125,7 +124,6 @@ class SchemaConventionsTest : PureJdbiTest(resetDbBeforeEach = false) { fun `update timestamp should be called 'updated_at' instead of 'updated'`() { val permittedViolations = setOf( - "application", "application_form", "application_note", "application_other_guardian", @@ -395,7 +393,6 @@ class SchemaConventionsTest : PureJdbiTest(resetDbBeforeEach = false) { fun `'created_by' column should be 'uuid' and NOT NULL`() { val permittedViolations = setOf( - Column(ColumnRef("application", "created_by"), "uuid", nullable = true), Column( ColumnRef("assistance_need_decision", "created_by"), "uuid", 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 bf051fbb87d..e757be31243 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 @@ -121,7 +121,7 @@ class ScheduledJobsTest : FullApplicationTest(resetDbBeforeEach = true) { ) = tx.execute { sql( - "UPDATE application SET created = ${bind(created)} WHERE id = ${bind(applicationId)}" + "UPDATE application SET created_at = ${bind(created)} WHERE id = ${bind(applicationId)}" ) } 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 b41de83b4e2..2995046112d 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/test/TestData.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/test/TestData.kt @@ -23,6 +23,8 @@ import fi.espoo.evaka.shared.domain.HelsinkiDateTime import fi.espoo.evaka.testAdult_1 import fi.espoo.evaka.testChild_1 import fi.espoo.evaka.testDaycare +import fi.espoo.evaka.testDecisionMaker_1 +import fi.espoo.evaka.toEvakaUser import java.time.LocalDate import java.time.LocalTime import java.util.UUID @@ -50,8 +52,10 @@ private fun applicationDetails(vararg preferredUnits: PreferredUnit, shiftCare: 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)), + createdAt = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), + createdBy = testDecisionMaker_1.toEvakaUser(), + modifiedAt = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), + modifiedBy = testDecisionMaker_1.toEvakaUser(), sentDate = LocalDate.of(2021, 1, 15), dueDate = null, dueDateSetManuallyAt = null, @@ -144,8 +148,10 @@ val validPreschoolApplication = 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)), + createdAt = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), + createdBy = testDecisionMaker_1.toEvakaUser(), + modifiedAt = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), + modifiedBy = testDecisionMaker_1.toEvakaUser(), sentDate = LocalDate.of(2021, 1, 15), dueDate = null, dueDateSetManuallyAt = null, @@ -239,8 +245,10 @@ fun validClubApplication(preferredUnit: DevDaycare, preferredStartDate: LocalDat 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)), + createdAt = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), + createdBy = testDecisionMaker_1.toEvakaUser(), + modifiedAt = HelsinkiDateTime.of(LocalDate.of(2021, 8, 15), LocalTime.of(12, 0)), + modifiedBy = testDecisionMaker_1.toEvakaUser(), sentDate = LocalDate.of(2021, 1, 15), dueDate = null, dueDateSetManuallyAt = null, 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 b5939d2a427..6ffbb16c00a 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/Application.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/Application.kt @@ -112,8 +112,10 @@ data class ApplicationDetails( val guardianDateOfDeath: LocalDate?, val checkedByAdmin: Boolean, val confidential: Boolean?, - val createdDate: HelsinkiDateTime?, - val modifiedDate: HelsinkiDateTime?, + val createdAt: HelsinkiDateTime?, + val createdBy: EvakaUser?, + val modifiedAt: HelsinkiDateTime?, + val modifiedBy: EvakaUser?, val sentDate: LocalDate?, val dueDate: LocalDate?, val dueDateSetManuallyAt: HelsinkiDateTime?, 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 fd299ac7ae2..2f550257e51 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, 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) +INSERT INTO application (type, status, guardian_id, child_id, origin, created_at, created_by, hidefromguardian, sentdate, allow_other_guardian_access, document, modified_at, modified_by, status_modified_at, status_modified_by, confidential) +VALUES (${bind(type)}, 'CREATED', ${bind(guardianId)}, ${bind(childId)}, ${bind(origin)}, ${bind(now)}, ${bind(createdBy)}, ${bind(hideFromGuardian)}, ${bind(sentDate)}, ${bind(allowOtherGuardianAccess)}, ${bindJson(document)}, ${bind(now)}, ${bind(createdBy)}, ${bind(now)}, ${bind(createdBy)}, NULL) RETURNING id """ ) @@ -707,8 +707,8 @@ SELECT ) AS start_date, a.sentDate, a.status AS application_status, - a.created AS created_date, - a.form_modified AS modified_date, + a.created_at AS created_date, + a.modified_at AS modified_date, a.transferapplication FROM application a WHERE (a.guardian_id = ${bind(citizenId)} OR EXISTS ( @@ -775,8 +775,14 @@ fun Database.Read.fetchApplicationDetails( a.transferapplication, a.additionaldaycareapplication, a.hidefromguardian, - a.created, - a.form_modified, + a.created_at, + e_created.id AS created_by_id, + e_created.name AS created_by_name, + e_created.type AS created_by_type, + a.modified_at, + e_modified.id AS modified_by_id, + e_modified.name AS modified_by_name, + e_modified.type AS modified_by_type, a.sentdate, a.duedate, a.duedate_set_manually_at, @@ -788,6 +794,8 @@ fun Database.Read.fetchApplicationDetails( FROM application a LEFT JOIN person c ON c.id = a.child_id LEFT JOIN person g1 ON g1.id = a.guardian_id + LEFT JOIN evaka_user e_created ON a.created_by = e_created.id + LEFT JOIN evaka_user e_modified ON a.modified_by = e_modified.id LEFT JOIN ( SELECT application_id, jsonb_agg(jsonb_build_object( 'id', attachment.id, @@ -835,8 +843,20 @@ fun Database.Read.fetchApplicationDetails( guardianDateOfDeath = column("guardian_date_of_death"), transferApplication = column("transferapplication"), additionalDaycareApplication = column("additionaldaycareapplication"), - createdDate = column("created"), - modifiedDate = column("form_modified"), + createdAt = column("created_at"), + createdBy = + EvakaUser( + id = column("created_by_id"), + name = column("created_by_name"), + type = column("created_by_type"), + ), + modifiedAt = column("modified_at"), + modifiedBy = + EvakaUser( + id = column("modified_by_id"), + name = column("modified_by_name"), + type = column("modified_by_type"), + ), sentDate = column("sentdate"), dueDate = column("duedate"), dueDateSetManuallyAt = column("duedate_set_manually_at"), @@ -994,6 +1014,7 @@ fun Database.Transaction.updateForm( childRestricted: Boolean, guardianRestricted: Boolean, now: HelsinkiDateTime, + modifiedBy: EvakaUserId, ) { check(getApplicationType(id) == formType) { "Invalid form type for the application" } val transformedForm = @@ -1005,7 +1026,7 @@ fun Database.Transaction.updateForm( execute { sql( - "UPDATE application SET document = ${bindJson(transformedForm)}, form_modified = ${bind(now)} WHERE id = ${bind(id)}" + "UPDATE application SET document = ${bindJson(transformedForm)}, modified_at = ${bind(now)}, modified_by = ${bind(modifiedBy)} WHERE id = ${bind(id)}" ) } } @@ -1013,6 +1034,8 @@ fun Database.Transaction.updateForm( fun Database.Transaction.resetCheckedByAdminAndConfidentiality( id: ApplicationId, form: ApplicationForm, + now: HelsinkiDateTime, + modifiedBy: EvakaUserId, ) { val confidential = when { @@ -1035,10 +1058,13 @@ fun Database.Transaction.resetCheckedByAdminAndConfidentiality( execute { sql( """ - UPDATE application - SET checkedbyadmin = ${bind(!requiresManualChecking)}, confidential = ${bind(confidential)} + UPDATE application SET + checkedbyadmin = ${bind(!requiresManualChecking)}, + modified_at = ${bind(now)}, + modified_by = ${bind(modifiedBy)}, + confidential = ${bind(confidential)} WHERE id = ${bind(id)} - """ + """ ) } } @@ -1054,7 +1080,9 @@ fun Database.Transaction.updateApplicationStatus( UPDATE application SET status = ${bind(status)}, status_modified_by = ${bind(modifiedBy)}, - status_modified_at = ${bind(now)} + status_modified_at = ${bind(now)}, + modified_by = ${bind(modifiedBy)}, + modified_at = ${bind(now)} WHERE id = ${bind(id)} """ ) @@ -1064,10 +1092,12 @@ fun Database.Transaction.updateApplicationDates( id: ApplicationId, sentDate: LocalDate, dueDate: LocalDate?, + now: HelsinkiDateTime, + modifiedBy: EvakaUserId, ) { execute { sql( - "UPDATE application SET sentdate = ${bind(sentDate)}, duedate = ${bind(dueDate)} WHERE id = ${bind(id)}" + "UPDATE application SET sentdate = ${bind(sentDate)}, duedate = ${bind(dueDate)}, modified_at = ${bind(now)}, modified_by = ${bind(modifiedBy)} WHERE id = ${bind(id)}" ) } } @@ -1075,13 +1105,17 @@ fun Database.Transaction.updateApplicationDates( fun Database.Transaction.updateApplicationFlags( id: ApplicationId, applicationFlags: ApplicationFlags, + now: HelsinkiDateTime, + modifiedBy: EvakaUserId, ) = execute { sql( """ UPDATE application SET transferapplication = ${bind(applicationFlags.isTransferApplication)}, - additionaldaycareapplication = ${bind(applicationFlags.isAdditionalDaycareApplication)} + additionaldaycareapplication = ${bind(applicationFlags.isAdditionalDaycareApplication)}, + modified_at = ${bind(now)}, + modified_by = ${bind(modifiedBy)} WHERE id = ${bind(id)} """ ) @@ -1090,11 +1124,15 @@ fun Database.Transaction.updateApplicationFlags( fun Database.Transaction.updateApplicationAllowOtherGuardianAccess( id: ApplicationId, allowOtherGuardianAccess: Boolean, + now: HelsinkiDateTime, + modifiedBy: EvakaUserId, ) = execute { sql( """ - UPDATE application - SET allow_other_guardian_access = ${bind(allowOtherGuardianAccess)} + UPDATE application SET + allow_other_guardian_access = ${bind(allowOtherGuardianAccess)}, + modified_at = ${bind(now)}, + modified_by = ${bind(modifiedBy)} WHERE id = ${bind(id)} """ ) @@ -1139,20 +1177,43 @@ AND other_citizen.id != application.guardian_id } } -fun Database.Transaction.setApplicationVerified(id: ApplicationId, verified: Boolean) { +fun Database.Transaction.setApplicationVerified( + id: ApplicationId, + verified: Boolean, + now: HelsinkiDateTime, + modifiedBy: EvakaUserId, +) { execute { - sql("UPDATE application SET checkedByAdmin = ${bind(verified)} WHERE id = ${bind(id)}") + sql( + "UPDATE application SET checkedByAdmin = ${bind(verified)}, modified_at = ${bind(now)}, modified_by = ${bind(modifiedBy)} WHERE id = ${bind(id)}" + ) } } -fun Database.Transaction.setApplicationConfidentiality(id: ApplicationId, confidential: Boolean?) { +fun Database.Transaction.setApplicationConfidentiality( + id: ApplicationId, + confidential: Boolean?, + now: HelsinkiDateTime, + modifiedBy: EvakaUserId, +) { execute { - sql("UPDATE application SET confidential = ${bind(confidential)} WHERE id = ${bind(id)}") + sql( + "UPDATE application SET confidential = ${bind(confidential)}, modified_at = ${bind(now)}, modified_by = ${bind(modifiedBy)} 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)}") } +fun Database.Transaction.setApplicationProcessId( + id: ApplicationId, + processId: ArchivedProcessId, + now: HelsinkiDateTime, + modifiedBy: EvakaUserId, +) { + execute { + sql( + "UPDATE application SET process_id = ${bind(processId)}, modified_at = ${bind(now)}, modified_by = ${bind(modifiedBy)} WHERE id = ${bind(id)}" + ) + } } fun Database.Transaction.deleteApplication(id: ApplicationId) = execute { @@ -1166,7 +1227,7 @@ fun Database.Transaction.removeOldDrafts(clock: EvakaClock) { val applicationIds = createQuery { sql( - "SELECT id FROM application WHERE status = 'CREATED' AND created < ${bind(clock.today())} - ${bind(thresholdDays)}" + "SELECT id FROM application WHERE status = 'CREATED' AND created_at < ${bind(clock.today())} - ${bind(thresholdDays)}" ) } .toList() @@ -1238,7 +1299,9 @@ fun Database.Transaction.cancelOutdatedSentTransferApplications( UPDATE application SET status = ${bind(ApplicationStatus.CANCELLED)}, status_modified_by = ${bind(evakaUserId)}, - status_modified_at = ${bind(clock.now())} + status_modified_at = ${bind(clock.now())}, + modified_by = ${bind(evakaUserId)}, + modified_at = ${bind(clock.now())} WHERE transferapplication AND status = ANY('{SENT}') AND NOT EXISTS ( @@ -1290,7 +1353,9 @@ fun Database.Transaction.cancelAllActiveTransferApplications( UPDATE application SET status = 'CANCELLED', status_modified_by = ${bind(evakaUserId)}, - status_modified_at = ${bind(clock.now())} + status_modified_at = ${bind(clock.now())}, + modified_by = ${bind(evakaUserId)}, + modified_at = ${bind(clock.now())} WHERE transferapplication AND child_id = ${bind(childId)} AND status = ANY(${bind(arrayOf(ApplicationStatus.SENT))}::application_status_type[]) 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 4acfa230f53..9449282e217 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/application/ApplicationStateService.kt @@ -274,9 +274,14 @@ class ApplicationStateService( personService.getGuardians(tx, user, application.childId) val applicationFlags = tx.applicationFlags(application, currentDate) - tx.updateApplicationFlags(application.id, applicationFlags) + tx.updateApplicationFlags(application.id, applicationFlags, clock.now(), user.evakaUserId) - tx.resetCheckedByAdminAndConfidentiality(applicationId, application.form) + tx.resetCheckedByAdminAndConfidentiality( + applicationId, + application.form, + clock.now(), + user.evakaUserId, + ) val sentDate = application.sentDate ?: currentDate val dueDate = @@ -289,7 +294,7 @@ class ApplicationStateService( applicationFlags.isTransferApplication, application.attachments, ) - tx.updateApplicationDates(application.id, sentDate, dueDate) + tx.updateApplicationDates(application.id, sentDate, dueDate, clock.now(), user.evakaUserId) tx.getPersonById(application.guardianId)?.let { val email = @@ -374,7 +379,7 @@ class ApplicationStateService( now = clock.now(), userId = user.evakaUserId, ) - tx.setApplicationProcessId(applicationId, processId) + tx.setApplicationProcessId(applicationId, processId, clock.now(), user.evakaUserId) } Audit.ApplicationSend.log(targetId = AuditId(applicationId)) @@ -387,11 +392,11 @@ class ApplicationStateService( application: ApplicationDetails, ) { val applicationFlags = tx.applicationFlags(application, clock.today()) - tx.updateApplicationFlags(application.id, applicationFlags) + tx.updateApplicationFlags(application.id, applicationFlags, clock.now(), user.evakaUserId) val sentDate = application.sentDate!! val dueDate = application.sentDate - tx.updateApplicationDates(application.id, sentDate, dueDate) + tx.updateApplicationDates(application.id, sentDate, dueDate, clock.now(), user.evakaUserId) if (!application.hideFromGuardian) { messageService.sendMessageAsEmployee( @@ -421,7 +426,12 @@ class ApplicationStateService( ) } - tx.resetCheckedByAdminAndConfidentiality(application.id, application.form) + tx.resetCheckedByAdminAndConfidentiality( + application.id, + application.form, + clock.now(), + user.evakaUserId, + ) tx.updateApplicationStatus(application.id, SENT, user.evakaUserId, clock.now()) } @@ -454,7 +464,12 @@ class ApplicationStateService( ) ) - tx.resetCheckedByAdminAndConfidentiality(applicationId, application.form) + tx.resetCheckedByAdminAndConfidentiality( + applicationId, + application.form, + clock.now(), + user.evakaUserId, + ) asyncJobRunner.plan( tx, @@ -495,7 +510,12 @@ class ApplicationStateService( val application = getApplication(tx, applicationId) verifyStatus(application, setOf(WAITING_PLACEMENT, CANCELLED)) - tx.resetCheckedByAdminAndConfidentiality(applicationId, application.form) + tx.resetCheckedByAdminAndConfidentiality( + applicationId, + application.form, + clock.now(), + user.evakaUserId, + ) if (application.status == CANCELLED) { tx.getArchiveProcessByApplicationId(applicationId)?.also { process -> @@ -531,9 +551,19 @@ class ApplicationStateService( if (application.confidential == null) { when { user is AuthenticatedUser.Citizen -> - tx.setApplicationConfidentiality(applicationId, true) + tx.setApplicationConfidentiality( + applicationId, + true, + clock.now(), + user.evakaUserId, + ) confidential != null -> - tx.setApplicationConfidentiality(applicationId, confidential) + tx.setApplicationConfidentiality( + applicationId, + confidential, + clock.now(), + user.evakaUserId, + ) else -> throw BadRequest("Confidentiality must be set") } } else if (confidential != null) throw BadRequest("Confidentiality is already set") @@ -574,11 +604,16 @@ class ApplicationStateService( if (application.confidential == null) { if (confidential != null) { - tx.setApplicationConfidentiality(applicationId, confidential) + tx.setApplicationConfidentiality( + applicationId, + confidential, + clock.now(), + user.evakaUserId, + ) } else throw BadRequest("Confidentiality must be set") } else if (confidential != null) throw BadRequest("Confidentiality is already set") - tx.setApplicationVerified(applicationId, true) + tx.setApplicationVerified(applicationId, true, clock.now(), user.evakaUserId) Audit.ApplicationAdminDetailsUpdate.log(targetId = AuditId(applicationId)) } @@ -1099,8 +1134,13 @@ class ApplicationStateService( } } - tx.updateApplicationAllowOtherGuardianAccess(applicationId, update.allowOtherGuardianAccess) - tx.updateApplicationContents(now, original, updatedForm) + tx.updateApplicationAllowOtherGuardianAccess( + applicationId, + update.allowOtherGuardianAccess, + now, + user.evakaUserId, + ) + tx.updateApplicationContents(now, user.evakaUserId, original, updatedForm) return getApplication(tx, applicationId) } @@ -1145,6 +1185,7 @@ class ApplicationStateService( tx.updateApplicationContents( now, + user.evakaUserId, original, updatedForm, manuallySetDueDate = update.dueDate, @@ -1161,6 +1202,7 @@ class ApplicationStateService( private fun Database.Transaction.updateApplicationContents( now: HelsinkiDateTime, + modifiedBy: EvakaUserId, original: ApplicationDetails, updatedForm: ApplicationForm, manuallySetDueDate: LocalDate? = null, @@ -1176,8 +1218,10 @@ class ApplicationStateService( original.childRestricted, original.guardianRestricted, now, + modifiedBy, ) - resetCheckedByAdminAndConfidentiality(original.id, updatedForm) + + resetCheckedByAdminAndConfidentiality(original.id, updatedForm, now, modifiedBy) 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/espoo/bi/EspooBi.kt b/service/src/main/kotlin/fi/espoo/evaka/espoo/bi/EspooBi.kt index 29e3d4a6a49..05d0ea45794 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/espoo/bi/EspooBi.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/espoo/bi/EspooBi.kt @@ -115,7 +115,7 @@ FROM daycare_caretaker sql( """ SELECT - id, created, updated, type, transferapplication, origin, status, additionaldaycareapplication, sentdate, duedate, + id, created_at, updated_at, type, transferapplication, origin, status, additionaldaycareapplication, sentdate, duedate, ( SELECT array_agg(e::UUID) FROM jsonb_array_elements_text(document -> 'apply' -> 'preferredUnits') e diff --git a/service/src/main/kotlin/fi/espoo/evaka/espoo/bi/EspooBiModels.kt b/service/src/main/kotlin/fi/espoo/evaka/espoo/bi/EspooBiModels.kt index 6ed0fe8b2b3..be15d7c3f4f 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/espoo/bi/EspooBiModels.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/espoo/bi/EspooBiModels.kt @@ -121,8 +121,8 @@ data class BiGroupCaretakerAllocation( data class BiApplication( val id: UUID, - val created: HelsinkiDateTime, - val updated: HelsinkiDateTime, + val createdAt: HelsinkiDateTime, + val updatedAt: HelsinkiDateTime, val type: ApplicationType, val transferApplication: Boolean, val origin: ApplicationOrigin, diff --git a/service/src/main/kotlin/fi/espoo/evaka/pis/ParentshipQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/pis/ParentshipQueries.kt index c4cb0cf4eb0..01d9860bafb 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/pis/ParentshipQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/pis/ParentshipQueries.kt @@ -56,7 +56,7 @@ SELECT created_by_user.name AS created_by_user_name, modified_by_user.name AS modified_by_user_name, created_by_application.type AS created_by_application_type, - created_by_application.created AS created_by_application_created + created_by_application.created_at AS created_by_application_created FROM fridge_child fc JOIN person child ON fc.child_id = child.id JOIN person head ON fc.head_of_child = head.id diff --git a/service/src/main/kotlin/fi/espoo/evaka/pis/PartnershipQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/pis/PartnershipQueries.kt index 6cd5a3754c3..1de2e59db0d 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/pis/PartnershipQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/pis/PartnershipQueries.kt @@ -37,7 +37,7 @@ fun Database.Read.getPartnership(id: PartnershipId): Partnership? { (SELECT name FROM evaka_user WHERE id = fp1.modified_by) AS modified_by_name, fp1.created_from_application, a.type AS created_from_application_type, - a.created AS created_from_application_created + a.created_at AS created_from_application_created FROM fridge_partner fp1 JOIN fridge_partner fp2 ON fp1.partnership_id = fp2.partnership_id AND fp1.indx = 1 AND fp2.indx = 2 JOIN person p1 ON fp1.person_id = p1.id @@ -74,7 +74,7 @@ SELECT (SELECT name FROM evaka_user WHERE id = fp1.modified_by) AS modified_by_name, fp1.created_from_application, a.type AS created_from_application_type, - a.created AS created_from_application_created + a.created_at AS created_from_application_created FROM fridge_partner fp1 JOIN fridge_partner fp2 ON fp1.partnership_id = fp2.partnership_id AND fp1.indx = 1 AND fp2.indx = 2 JOIN person p1 ON fp1.person_id = p1.id @@ -102,7 +102,7 @@ SELECT (SELECT name FROM evaka_user WHERE id = fp.created_by) AS created_by_name, (SELECT name FROM evaka_user WHERE id = fp.modified_by) AS modified_by_name, a.type AS created_from_application_type, - a.created AS created_from_application_created + a.created_at AS created_from_application_created FROM fridge_partner fp JOIN fridge_partner partner ON fp.partnership_id = partner.partnership_id AND fp.indx != partner.indx JOIN person p ON partner.person_id = p.id @@ -161,7 +161,7 @@ fun Database.Transaction.createPartnership( (SELECT name FROM evaka_user WHERE id = fp1.modified_by) AS modified_by_name, fp1.created_from_application, a.type AS created_from_application_type, - a.created AS created_from_application_created + a.created_at AS created_from_application_created FROM new_fridge_partner fp1 JOIN new_fridge_partner fp2 ON fp1.partnership_id = fp2.partnership_id AND fp1.indx = 1 AND fp2.indx = 2 JOIN person p1 ON fp1.person_id = p1.id diff --git a/service/src/main/kotlin/fi/espoo/evaka/process/ProcessMetadataController.kt b/service/src/main/kotlin/fi/espoo/evaka/process/ProcessMetadataController.kt index 17b870596ac..c09ee06892b 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/process/ProcessMetadataController.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/process/ProcessMetadataController.kt @@ -340,7 +340,7 @@ class ProcessMetadataController(private val accessControl: AccessControl) { WHEN a.type = 'CLUB' THEN 'Kerhohakemus' END AS name, - coalesce(a.sentdate at time zone 'europe/helsinki', a.created) AS created_at, + coalesce(a.sentdate at time zone 'europe/helsinki', a.created_at) AS created_at, e.id AS created_by_id, e.name AS created_by_name, e.type AS created_by_type, 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 4bc0127820d..5861b9cc7be 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 @@ -426,13 +426,14 @@ fun Database.Transaction.insertTestApplication( transferApplication: Boolean = false, allowOtherGuardianAccess: Boolean = true, document: DatabaseForm, - formModified: HelsinkiDateTime = HelsinkiDateTime.now(), + modifiedAt: HelsinkiDateTime = HelsinkiDateTime.now(), + modifiedBy: EvakaUserId = AuthenticatedUser.SystemInternalUser.evakaUserId, ): ApplicationId { 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, 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) +INSERT INTO application (type, id, sentdate, duedate, status, guardian_id, child_id, origin, hidefromguardian, additionalDaycareApplication, transferApplication, allow_other_guardian_access, document, modified_at, modified_by, created_at, created_by, 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(modifiedAt)}, ${bind(modifiedBy)}, ${bind(modifiedAt)}, ${bind(modifiedBy)}, NULL) """ ) } @@ -947,7 +948,10 @@ INSERT INTO application( transferapplication, allow_other_guardian_access, document, - form_modified + created_at, + created_by, + modified_at, + modified_by ) VALUES ( ${bind(application.id)}, @@ -964,7 +968,10 @@ VALUES ( ${bind(application.transferApplication)}, ${bind(application.allowOtherGuardianAccess)}, ${bindJson(document)}, - ${bind(application.formModified)} + ${bind(application.createdAt)}, + ${bind(application.createdBy)}, + ${bind(application.modifiedAt)}, + ${bind(application.modifiedBy)} ) """ ) 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 723f2d44330..7053c3a6db3 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 @@ -2113,8 +2113,10 @@ data class PlacementPlan( data class DevApplicationWithForm( val id: ApplicationId, val type: ApplicationType, - val createdDate: HelsinkiDateTime?, - val modifiedDate: HelsinkiDateTime?, + val createdAt: HelsinkiDateTime, + val createdBy: EvakaUserId, + val modifiedAt: HelsinkiDateTime, + val modifiedBy: EvakaUserId, var sentDate: LocalDate?, var dueDate: LocalDate?, val status: ApplicationStatus, @@ -2128,7 +2130,6 @@ data class DevApplicationWithForm( val allowOtherGuardianAccess: Boolean = true, val otherGuardians: List, val form: ApplicationForm, - val formModified: HelsinkiDateTime, ) data class DevDaycareGroupAcl( diff --git a/service/src/main/resources/db/migration/R__application_view.sql b/service/src/main/resources/db/migration/R__application_view.sql index a0fdcb8d4c8..9ae0c9984dd 100755 --- a/service/src/main/resources/db/migration/R__application_view.sql +++ b/service/src/main/resources/db/migration/R__application_view.sql @@ -4,7 +4,7 @@ CREATE OR REPLACE VIEW application_view ( id, document, docVersion, - created, + created_at, formModified, sentDate, dueDate, @@ -49,7 +49,7 @@ SELECT id, document, docVersion, - created, + created_at, formModified, sentDate, dueDate, @@ -108,8 +108,8 @@ SELECT appl.id, appl.document, appl.document -> 'docVersion' AS docVersion, - appl.form_modified AS formModified, - appl.created, + appl.modified_at AS formModified, + appl.created_at, appl.sentDate, appl.dueDate, appl.status AS status, diff --git a/service/src/main/resources/db/migration/V474__application_modified_metadata.sql b/service/src/main/resources/db/migration/V474__application_modified_metadata.sql new file mode 100644 index 00000000000..bc61dfe225a --- /dev/null +++ b/service/src/main/resources/db/migration/V474__application_modified_metadata.sql @@ -0,0 +1,20 @@ +ALTER TABLE application RENAME COLUMN form_modified TO modified_at; + +ALTER TABLE application ADD COLUMN modified_by UUID REFERENCES evaka_user; + +UPDATE application SET + created_by = COALESCE(created_by, '00000000-0000-0000-0000-000000000000'::UUID), + modified_by = COALESCE(modified_by, created_by, '00000000-0000-0000-0000-000000000000'::UUID); + +ALTER TABLE application + ALTER COLUMN created_by SET NOT NULL, + ALTER COLUMN modified_by SET NOT NULL; + +CREATE INDEX fk$application_modified_by ON application(modified_by); + + +ALTER TABLE application RENAME COLUMN created TO created_at; + +DROP TRIGGER set_timestamp ON application; +ALTER TABLE application RENAME COLUMN updated TO updated_at; +CREATE TRIGGER set_timestamp BEFORE UPDATE ON application FOR EACH ROW EXECUTE PROCEDURE trigger_refresh_updated_at(); diff --git a/service/src/main/resources/migrations.txt b/service/src/main/resources/migrations.txt index 94b274431dd..ae26ac38393 100644 --- a/service/src/main/resources/migrations.txt +++ b/service/src/main/resources/migrations.txt @@ -469,3 +469,4 @@ V470__cascade_citizen_user_delete.sql V471__application_confidentiality.sql V472__employee_ssn.sql V473__person_municipality_of_residence.sql +V474__application_modified_metadata.sql