From b300f199601fafc4a2ba8e89972a4ae7b40fd0ad Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Wed, 13 Nov 2024 14:30:24 +0200 Subject: [PATCH 01/14] WIP child_attendance modified fields --- .../MobileUnitControllerIntegrationTest.kt | 14 +++++++++++++ .../evaka/shared/db/SchemaConventionsTest.kt | 2 -- .../attendance/ChildAttendanceController.kt | 2 ++ .../attendance/ChildAttendanceQueries.kt | 7 +++++-- .../espoo/evaka/reservations/Reservations.kt | 2 ++ .../evaka/shared/dev/DataInitializers.kt | 10 ++++++---- .../fi/espoo/evaka/shared/dev/DevApi.kt | 2 ++ ...ance_reservation_modification_metadata.sql | 20 +++++++++++++++++++ 8 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 service/src/main/resources/db/migration/V463__attendance_reservation_modification_metadata.sql diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/MobileUnitControllerIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/MobileUnitControllerIntegrationTest.kt index 68fb0b15321..f8892a9be9a 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/MobileUnitControllerIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/MobileUnitControllerIntegrationTest.kt @@ -141,36 +141,48 @@ class MobileUnitControllerIntegrationTest : FullApplicationTest(resetDbBeforeEac testDaycare.id, today, TimeInterval(LocalTime.of(8, 30), null), + now, + mobileUser.evakaUserId, ) tx.insertAttendance( testChild_2.id, testDaycare.id, today, TimeInterval(LocalTime.of(9, 0), null), + now, + mobileUser.evakaUserId, ) tx.insertAttendance( testChild_3.id, testDaycare.id, today, TimeInterval(LocalTime.of(9, 30), null), + now, + mobileUser.evakaUserId, ) tx.insertAttendance( testChild_4.id, testDaycare.id, today, TimeInterval(LocalTime.of(10, 0), null), + now, + mobileUser.evakaUserId, ) tx.insertAttendance( testChild_5.id, testDaycare.id, today, TimeInterval(LocalTime.of(10, 15), null), + now, + mobileUser.evakaUserId, ) tx.insertAttendance( testChild_6.id, testDaycare.id, today, TimeInterval(LocalTime.of(10, 30), null), + now, + mobileUser.evakaUserId, ) val employee1 = DevEmployee(firstName = "One", lastName = "in group 1") @@ -234,6 +246,8 @@ class MobileUnitControllerIntegrationTest : FullApplicationTest(resetDbBeforeEac testDaycare.id, today, TimeInterval(LocalTime.of(6, 0), null), + now, + mobileUser.evakaUserId, ) } val unitInfo = fetchUnitInfo(testDaycare.id) 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 aa82affdf49..04aabe20696 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 @@ -51,7 +51,6 @@ class SchemaConventionsTest : PureJdbiTest(resetDbBeforeEach = false) { "backup_curriculum_template", "backup_messaging_blocklist", "care_area", - "child_attendance", "child_daily_note", "child_document", "child_images", @@ -144,7 +143,6 @@ class SchemaConventionsTest : PureJdbiTest(resetDbBeforeEach = false) { "backup_curriculum_template", "backup_messaging_blocklist", "care_area", - "child_attendance", "child_daily_note", "child_document", "child_images", diff --git a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceController.kt b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceController.kt index 89eef344612..dfd105e0f10 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceController.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceController.kt @@ -217,6 +217,8 @@ class ChildAttendanceController( unitId = unitId, date = today, range = TimeInterval(body.arrived, null), + now = clock.now(), + createdById = user.evakaUserId, ) } catch (e: Exception) { throw mapPSQLException(e) diff --git a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt index 3711e437b98..fbe6060e917 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt @@ -12,6 +12,7 @@ import fi.espoo.evaka.shared.AbsenceId import fi.espoo.evaka.shared.ChildAttendanceId import fi.espoo.evaka.shared.ChildId import fi.espoo.evaka.shared.DaycareId +import fi.espoo.evaka.shared.EvakaUserId import fi.espoo.evaka.shared.GroupId import fi.espoo.evaka.shared.PersonId import fi.espoo.evaka.shared.db.Database @@ -29,12 +30,14 @@ fun Database.Transaction.insertAttendance( unitId: DaycareId, date: LocalDate, range: TimeInterval, + now: HelsinkiDateTime, + createdById: EvakaUserId, ): ChildAttendanceId { return createUpdate { sql( """ - INSERT INTO child_attendance (child_id, unit_id, date, start_time, end_time) - VALUES (${bind(childId)}, ${bind(unitId)}, ${bind(date)}, ${bind(range.start)}, ${bind(range.end)}) + INSERT INTO child_attendance (child_id, unit_id, date, start_time, end_time, modified_at, modified_by) + VALUES (${bind(childId)}, ${bind(unitId)}, ${bind(date)}, ${bind(range.start)}, ${bind(range.end)}, ${bind(now)}, ${bind(createdById)}) RETURNING id """ ) diff --git a/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt b/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt index e4f4dd466a3..f3f932afd8d 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt @@ -464,6 +464,8 @@ fun upsertChildDatePresence( input.unitId, input.date, TimeInterval(attendance.start, attendance.end), + now, + userId, ) } 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..9c8d9ee0a28 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 @@ -880,6 +880,8 @@ fun Database.Transaction.insertTestChildAttendance( unitId: DaycareId, arrived: HelsinkiDateTime, departed: HelsinkiDateTime?, + modifiedAt: HelsinkiDateTime = HelsinkiDateTime.now(), + modifiedBy: EvakaUserId = AuthenticatedUser.SystemInternalUser.evakaUserId, ) { val attendances: List> = if (departed == null) { @@ -902,8 +904,8 @@ fun Database.Transaction.insertTestChildAttendance( executeBatch(attendances) { sql( """ -INSERT INTO child_attendance (child_id, unit_id, date, start_time, end_time) -VALUES (${bind(childId)}, ${bind(unitId)}, ${bind { (date, _, _) -> date }}, ${bind { (_, startTime, _) -> startTime.withSecond(0).withNano(0) }}, ${bind { (_, _, endTime) -> endTime?.withSecond(0)?.withNano(0) }}) +INSERT INTO child_attendance (child_id, unit_id, date, start_time, end_time, modified_at, modified_by) +VALUES (${bind(childId)}, ${bind(unitId)}, ${bind { (date, _, _) -> date }}, ${bind { (_, startTime, _) -> startTime.withSecond(0).withNano(0) }}, ${bind { (_, _, endTime) -> endTime?.withSecond(0)?.withNano(0) }}, ${bind(modifiedAt)}, ${bind(modifiedBy)}) """ ) } @@ -1521,8 +1523,8 @@ fun Database.Transaction.insert(row: DevChildAttendance): ChildAttendanceId = createUpdate { sql( """ -INSERT INTO child_attendance (child_id, unit_id, date, start_time, end_time) -VALUES (${bind(row.childId)}, ${bind(row.unitId)}, ${bind(row.date)}, ${bind(row.arrived)}, ${bind(row.departed)}) +INSERT INTO child_attendance (child_id, unit_id, date, start_time, end_time, modified_at, modified_by) +VALUES (${bind(row.childId)}, ${bind(row.unitId)}, ${bind(row.date)}, ${bind(row.arrived)}, ${bind(row.departed)}, ${bind(row.modifiedAt)}, ${bind(row.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 3b014e14382..3e4a5b1785d 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 @@ -1932,6 +1932,8 @@ data class DevChildAttendance( val date: LocalDate, val arrived: LocalTime, val departed: LocalTime?, + val modifiedAt: HelsinkiDateTime = HelsinkiDateTime.now(), + val modifiedBy: EvakaUserId = AuthenticatedUser.SystemInternalUser.evakaUserId, ) data class DevAssistanceAction( diff --git a/service/src/main/resources/db/migration/V463__attendance_reservation_modification_metadata.sql b/service/src/main/resources/db/migration/V463__attendance_reservation_modification_metadata.sql new file mode 100644 index 00000000000..8ea9b65dd4e --- /dev/null +++ b/service/src/main/resources/db/migration/V463__attendance_reservation_modification_metadata.sql @@ -0,0 +1,20 @@ +ALTER TABLE child_attendance + ADD COLUMN modified_at TIMESTAMP WITH TIME ZONE, + ADD COLUMN modified_by UUID REFERENCES evaka_user; + +UPDATE child_attendance SET + modified_at = COALESCE(created, updated), + modified_by = COALESCE(modified_by, '00000000-0000-0000-0000-000000000000'::UUID); + +ALTER TABLE child_attendance + ALTER COLUMN modified_at SET NOT NULL, + ALTER COLUMN modified_by SET NOT NULL; + +CREATE INDEX fk$child_attendance_modified_by ON child_attendance(modified_by); + + +DROP TRIGGER set_timestamp ON child_attendance; +ALTER TABLE child_attendance RENAME COLUMN updated TO updated_at; +CREATE TRIGGER set_timestamp BEFORE UPDATE ON child_attendance FOR EACH ROW EXECUTE PROCEDURE trigger_refresh_updated_at(); + +ALTER TABLE child_attendance RENAME COLUMN created TO created_at; From 493b9266e65f113e6d14ec3e07aeb111e6330013 Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Wed, 13 Nov 2024 19:09:33 +0200 Subject: [PATCH 02/14] WIP: --- .../fi/espoo/evaka/attendance/ChildAttendance.kt | 4 ++++ .../espoo/evaka/attendance/ChildAttendanceQueries.kt | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt index 7f67b7b5800..643d7c4bccb 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt @@ -17,8 +17,10 @@ import fi.espoo.evaka.shared.DaycareId import fi.espoo.evaka.shared.GroupId import fi.espoo.evaka.shared.domain.HelsinkiDateTime import fi.espoo.evaka.shared.domain.TimeInterval +import fi.espoo.evaka.user.EvakaUser import java.time.LocalDate import java.time.LocalTime +import org.jdbi.v3.core.mapper.Nested data class ContactInfo( val id: String, @@ -61,6 +63,8 @@ data class ChildAttendanceRow( val date: LocalDate, val startTime: LocalTime, val endTime: LocalTime?, + val modifiedAt: HelsinkiDateTime, + @Nested("modified_by") val modifiedBy: EvakaUser, ) { fun asTimeInterval(): TimeInterval = TimeInterval(startTime, endTime) } diff --git a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt index fbe6060e917..d42e1f1de30 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt @@ -460,8 +460,18 @@ fun Database.Read.getChildAttendances(where: Predicate): List Date: Wed, 13 Nov 2024 20:31:00 +0200 Subject: [PATCH 03/14] WIP --- frontend/src/e2e-test/dev-api/fixtures.ts | 2 ++ frontend/src/e2e-test/generated/api-types.ts | 9 ++++++++- .../src/e2e-test/specs/5_employee/month-calendar.spec.ts | 6 +++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend/src/e2e-test/dev-api/fixtures.ts b/frontend/src/e2e-test/dev-api/fixtures.ts index ffcc90f72e5..27fa6c189c5 100644 --- a/frontend/src/e2e-test/dev-api/fixtures.ts +++ b/frontend/src/e2e-test/dev-api/fixtures.ts @@ -761,6 +761,8 @@ export class Fixture { date: LocalDate.todayInHelsinkiTz(), arrived: LocalTime.nowInHelsinkiTz(), departed: LocalTime.nowInHelsinkiTz(), + modifiedAt: HelsinkiDateTime.now(), + modifiedBy: systemInternalUser.id, ...initial }) } diff --git a/frontend/src/e2e-test/generated/api-types.ts b/frontend/src/e2e-test/generated/api-types.ts index b4911ac487d..3c607dac98d 100644 --- a/frontend/src/e2e-test/generated/api-types.ts +++ b/frontend/src/e2e-test/generated/api-types.ts @@ -405,7 +405,13 @@ export interface DevChildAttendance { childId: PersonId date: LocalDate departed: LocalTime | null +<<<<<<< HEAD unitId: DaycareId +======= + modifiedAt: HelsinkiDateTime + modifiedBy: UUID + unitId: UUID +>>>>>>> 63aa5ee1c3 (WIP) } /** @@ -1271,7 +1277,8 @@ export function deserializeJsonDevChildAttendance(json: JsonOf { unitId: testDaycare.id, date, arrived: LocalTime.of(8, 15), - departed: LocalTime.of(16, 30) + departed: LocalTime.of(16, 30), + modifiedAt: HelsinkiDateTime.now(), + modifiedBy: systemInternalUser.id })) ) }) From fb035a8bcdb5c55dd94be0827d22ede49a48ef8d Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Thu, 14 Nov 2024 14:57:43 +0200 Subject: [PATCH 04/14] WIP --- .../unit/unit-reservations/ChildDateModal.tsx | 4 +- .../ChildReservationsTable.tsx | 2 +- .../generated/api-types/attendance.ts | 9 +++- .../generated/api-types/reservations.ts | 34 +++++++++++++- .../espoo/evaka/attendance/ChildAttendance.kt | 7 ++- .../attendance/ChildAttendanceQueries.kt | 10 ++++- .../AttendanceReservationController.kt | 44 ++++++++++++------- 7 files changed, 85 insertions(+), 25 deletions(-) diff --git a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDateModal.tsx b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDateModal.tsx index 6cc9b0191be..ced47c8e0c1 100644 --- a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDateModal.tsx +++ b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDateModal.tsx @@ -197,8 +197,8 @@ export default React.memo(function ChildDateModal({ attendances: editingFuture ? [] : childDayRecord.attendances.map((a) => ({ - startTime: a.formatStart(), - endTime: a.formatEnd() + startTime: a.interval.formatStart(), + endTime: a.interval.formatEnd() })), billableAbsence: childDayRecord.possibleAbsenceCategories.includes( 'BILLABLE' diff --git a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildReservationsTable.tsx b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildReservationsTable.tsx index 84875959a8e..d4d60325c6d 100644 --- a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildReservationsTable.tsx +++ b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildReservationsTable.tsx @@ -342,7 +342,7 @@ const ChildRowGroup = React.memo(function ChildRowGroup({ date={date} attendanceIndex={index} dateInfo={dateInfo} - attendance={child.attendances[index]} + attendance={child.attendances[index]?.interval} reservations={child.reservations} dailyServiceTimes={child.dailyServiceTimes} inOtherUnit={child.inOtherUnit} diff --git a/frontend/src/lib-common/generated/api-types/attendance.ts b/frontend/src/lib-common/generated/api-types/attendance.ts index c4b08031a3e..7339389d889 100644 --- a/frontend/src/lib-common/generated/api-types/attendance.ts +++ b/frontend/src/lib-common/generated/api-types/attendance.ts @@ -13,9 +13,13 @@ import { AbsenceType } from './absence' import { ChildDailyNote } from './note' import { ChildStickyNote } from './note' import { DailyServiceTimesValue } from './dailyservicetimes' +<<<<<<< HEAD import { DaycareId } from './shared' import { EmployeeId } from './shared' import { GroupId } from './shared' +======= +import { EvakaUser } from './user' +>>>>>>> 9571146eb1 (WIP) import { HelsinkiDateTimeRange } from './shared' import { JsonOf } from '../../json' import { PersonId } from './shared' @@ -96,6 +100,8 @@ export type AttendanceStatus = export interface AttendanceTimes { arrived: HelsinkiDateTime departed: HelsinkiDateTime | null + modifiedAt: HelsinkiDateTime | null + modifiedBy: EvakaUser | null } /** @@ -485,7 +491,8 @@ export function deserializeJsonAttendanceTimes(json: JsonOf): A return { ...json, arrived: HelsinkiDateTime.parseIso(json.arrived), - departed: (json.departed != null) ? HelsinkiDateTime.parseIso(json.departed) : null + departed: (json.departed != null) ? HelsinkiDateTime.parseIso(json.departed) : null, + modifiedAt: (json.modifiedAt != null) ? HelsinkiDateTime.parseIso(json.modifiedAt) : null } } diff --git a/frontend/src/lib-common/generated/api-types/reservations.ts b/frontend/src/lib-common/generated/api-types/reservations.ts index 8a841c5b6e5..5e77ed816b4 100644 --- a/frontend/src/lib-common/generated/api-types/reservations.ts +++ b/frontend/src/lib-common/generated/api-types/reservations.ts @@ -5,6 +5,7 @@ // GENERATED FILE: no manual modifications import FiniteDateRange from '../../finite-date-range' +import HelsinkiDateTime from '../../helsinki-date-time' import LocalDate from '../../local-date' import TimeInterval from '../../time-interval' import TimeRange from '../../time-range' @@ -13,8 +14,12 @@ import { AbsenceType } from './absence' import { ChildImageId } from './shared' import { ChildServiceNeedInfo } from './absence' import { DailyServiceTimesValue } from './dailyservicetimes' +<<<<<<< HEAD import { DaycareId } from './shared' import { GroupId } from './shared' +======= +import { EvakaUser } from './user' +>>>>>>> 9571146eb1 (WIP) import { HolidayPeriodEffect } from './holidayperiod' import { JsonOf } from '../../json' import { PersonId } from './shared' @@ -49,6 +54,17 @@ export interface AbsenceTypeResponse { staffCreated: boolean } +/** +* Generated from fi.espoo.evaka.reservations.AttendanceTimesForDate +*/ +export interface AttendanceTimesForDate { + date: LocalDate + interval: TimeInterval + modifiedAt: HelsinkiDateTime + modifiedBy: EvakaUser + staffModified: boolean +} + /** * Generated from fi.espoo.evaka.reservations.AttendanceReservationController.BackupPlacementType */ @@ -87,9 +103,15 @@ export interface ChildDatePresence { export interface ChildRecordOfDay { absenceBillable: AbsenceTypeResponse | null absenceNonbillable: AbsenceTypeResponse | null +<<<<<<< HEAD attendances: TimeInterval[] backupGroupId: GroupId | null childId: PersonId +======= + attendances: AttendanceTimesForDate[] + backupGroupId: UUID | null + childId: UUID +>>>>>>> 9571146eb1 (WIP) dailyServiceTimes: DailyServiceTimesValue | null groupId: GroupId | null inOtherUnit: boolean @@ -429,6 +451,16 @@ export function deserializeJsonAbsenceRequest(json: JsonOf): Abs } +export function deserializeJsonAttendanceTimesForDate(json: JsonOf): AttendanceTimesForDate { + return { + ...json, + date: LocalDate.parseIso(json.date), + interval: TimeInterval.parseJson(json.interval), + modifiedAt: HelsinkiDateTime.parseIso(json.modifiedAt) + } +} + + export function deserializeJsonChild(json: JsonOf): Child { return { ...json, @@ -451,7 +483,7 @@ export function deserializeJsonChildDatePresence(json: JsonOf export function deserializeJsonChildRecordOfDay(json: JsonOf): ChildRecordOfDay { return { ...json, - attendances: json.attendances.map(e => TimeInterval.parseJson(e)), + attendances: json.attendances.map(e => deserializeJsonAttendanceTimesForDate(e)), dailyServiceTimes: (json.dailyServiceTimes != null) ? deserializeJsonDailyServiceTimesValue(json.dailyServiceTimes) : null, reservations: json.reservations.map(e => deserializeJsonReservationResponse(e)) } diff --git a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt index 643d7c4bccb..b7b88714cf6 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt @@ -69,6 +69,11 @@ data class ChildAttendanceRow( fun asTimeInterval(): TimeInterval = TimeInterval(startTime, endTime) } -data class AttendanceTimes(val arrived: HelsinkiDateTime, val departed: HelsinkiDateTime?) +data class AttendanceTimes( + val arrived: HelsinkiDateTime, + val departed: HelsinkiDateTime?, + val modifiedAt: HelsinkiDateTime? = null, + @Nested("modified_by") val modifiedBy: EvakaUser? = null, +) data class ChildAbsence(val category: AbsenceCategory, val type: AbsenceType) diff --git a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt index d42e1f1de30..c5588d87527 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt @@ -21,6 +21,7 @@ import fi.espoo.evaka.shared.domain.FiniteDateRange import fi.espoo.evaka.shared.domain.HelsinkiDateTime import fi.espoo.evaka.shared.domain.TimeInterval import fi.espoo.evaka.shared.domain.TimeRange +import fi.espoo.evaka.user.EvakaUser import java.time.LocalDate import java.time.LocalTime import org.jdbi.v3.core.mapper.Nested @@ -288,6 +289,8 @@ private data class UnitChildAttendancesRow( val childId: ChildId, val arrived: HelsinkiDateTime, var departed: HelsinkiDateTime?, + val modifiedAt: HelsinkiDateTime, + @Nested("modified_by") val modifiedBy: EvakaUser, ) fun Database.Read.getUnitChildAttendances( @@ -301,8 +304,9 @@ fun Database.Read.getUnitChildAttendances( return createQuery { sql( """ -SELECT child_id, ca.arrived, ca.departed +SELECT child_id, ca.arrived, ca.departed, ca.modified_at, e.id AS modified_by_id, e.name AS modified_by_name, e.type AS modified_by_type FROM child_attendance ca +JOIN evaka_user e ON ca.modified_by = e.id WHERE ca.unit_id = ${bind(unitId)} AND tstzrange(arrived, departed) && ${bind(range)} """ ) @@ -310,7 +314,9 @@ WHERE ca.unit_id = ${bind(unitId)} AND tstzrange(arrived, departed) && ${bind(ra .toList() .groupBy( keySelector = { it.childId }, - valueTransform = { AttendanceTimes(it.arrived, it.departed) }, + valueTransform = { + AttendanceTimes(it.arrived, it.departed, it.modifiedAt, it.modifiedBy) + }, ) .mapValues { mergeOverNightRanges(it.value) diff --git a/service/src/main/kotlin/fi/espoo/evaka/reservations/AttendanceReservationController.kt b/service/src/main/kotlin/fi/espoo/evaka/reservations/AttendanceReservationController.kt index 085e7ebd2e7..15936e12b0b 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/reservations/AttendanceReservationController.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/reservations/AttendanceReservationController.kt @@ -39,6 +39,7 @@ import fi.espoo.evaka.shared.domain.BadRequest import fi.espoo.evaka.shared.domain.DateRange import fi.espoo.evaka.shared.domain.EvakaClock import fi.espoo.evaka.shared.domain.FiniteDateRange +import fi.espoo.evaka.shared.domain.HelsinkiDateTime import fi.espoo.evaka.shared.domain.NotFound import fi.espoo.evaka.shared.domain.TimeInterval import fi.espoo.evaka.shared.domain.TimeRange @@ -48,6 +49,7 @@ import fi.espoo.evaka.shared.domain.getOperationalDatesForChildren import fi.espoo.evaka.shared.domain.toFiniteDateRange import fi.espoo.evaka.shared.security.AccessControl import fi.espoo.evaka.shared.security.Action +import fi.espoo.evaka.user.EvakaUser import java.math.BigDecimal import java.time.LocalDate import java.time.LocalTime @@ -159,7 +161,8 @@ class AttendanceReservationController( ?.takeIf { !placementStatus.backupOtherUnit } - ?.sortedBy { it.start } ?: emptyList(), + ?.sortedBy { it.interval.start } + ?: emptyList(), absenceBillable = childData.absences[date] ?.get(AbsenceCategory.BILLABLE) @@ -719,7 +722,7 @@ data class UnitAttendanceReservations( data class ChildRecordOfDay( val childId: ChildId, val reservations: List, - val attendances: List, + val attendances: List, val absenceBillable: AbsenceTypeResponse?, val absenceNonbillable: AbsenceTypeResponse?, val possibleAbsenceCategories: Set, @@ -937,7 +940,7 @@ WHERE (p.unit_id = ${bind(unitId)} OR bc.unit_id = ${bind(unitId)}) AND daterang data class ChildData( val child: UnitAttendanceReservations.Child, val reservations: Map>, - val attendances: Map>, + val attendances: Map>, val absences: Map>, val operationalDays: Set, ) @@ -966,13 +969,13 @@ private data class ReservationTimesForDate( } } -private data class AttendanceTimesForDate( +data class AttendanceTimesForDate( val date: LocalDate, - val startTime: LocalTime, - val endTime: LocalTime?, -) { - fun toTimeInterval() = TimeInterval(startTime, endTime) -} + val interval: TimeInterval, + val modifiedAt: HelsinkiDateTime, + val modifiedBy: EvakaUser, + val staffModified: Boolean, +) private data class AbsenceForDate( val date: LocalDate, @@ -1011,10 +1014,21 @@ SELECT coalesce(( SELECT jsonb_agg(jsonb_build_object( 'date', att.date, - 'startTime', att.start_time, - 'endTime', att.end_time + 'interval', jsonb_build_object( + 'start', att.start_time, + 'end', att.end_time + ), + 'modifiedAt', att.modified_at, + 'modifiedBy', jsonb_build_object( + 'id', eu.id, + 'name', eu.name, + 'type', eu.type + ), + 'staffModified', eu.type <> 'CITIZEN' ) ORDER BY att.date, att.start_time) - FROM child_attendance att WHERE att.child_id = p.id AND between_start_and_end(${bind(dateRange)}, att.date) + FROM child_attendance att + JOIN evaka_user eu ON att.modified_by = eu.id + WHERE att.child_id = p.id AND between_start_and_end(${bind(dateRange)}, att.date) ), '[]'::jsonb) AS attendances, coalesce(( SELECT jsonb_agg(jsonb_build_object( @@ -1054,11 +1068,7 @@ WHERE p.id = ANY(${bind(childIds)}) keySelector = { it.date }, valueTransform = { it.toReservationTimes() }, ), - attendances = - row.attendances.groupBy( - keySelector = { it.date }, - valueTransform = { it.toTimeInterval() }, - ), + attendances = row.attendances.groupBy(keySelector = { it.date }), absences = row.absences .groupBy( From e052dc6355e361250abac58ef988cd946a70f636 Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Thu, 14 Nov 2024 16:13:34 +0200 Subject: [PATCH 05/14] WIP --- .../unit-reservations/ChildDayAttendance.tsx | 114 +++++++++++------- .../ChildReservationsTable.tsx | 2 +- .../defaults/employee/i18n/fi.tsx | 10 ++ 3 files changed, 79 insertions(+), 47 deletions(-) diff --git a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayAttendance.tsx b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayAttendance.tsx index ed93c9fff5a..d9c790b6278 100644 --- a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayAttendance.tsx +++ b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayAttendance.tsx @@ -16,14 +16,15 @@ import { ChildServiceNeedInfo } from 'lib-common/generated/api-types/absence' import { DailyServiceTimesValue } from 'lib-common/generated/api-types/dailyservicetimes' import { ScheduleType } from 'lib-common/generated/api-types/placement' import { + AttendanceTimesForDate, Reservation, UnitDateInfo } from 'lib-common/generated/api-types/reservations' import LocalDate from 'lib-common/local-date' import { reservationHasTimes } from 'lib-common/reservations' -import TimeInterval from 'lib-common/time-interval' import TimeRange from 'lib-common/time-range' import TimeRangeEndpoint from 'lib-common/time-range-endpoint' +import Tooltip from 'lib-components/atoms/Tooltip' import { IconOnlyButton } from 'lib-components/atoms/buttons/IconOnlyButton' import { fontWeights } from 'lib-components/typography' import { colors } from 'lib-customizations/common' @@ -38,7 +39,7 @@ interface Props { date: LocalDate attendanceIndex: number dateInfo: UnitDateInfo - attendance: TimeInterval | undefined + attendance: AttendanceTimesForDate | undefined reservations: Reservation[] dailyServiceTimes: DailyServiceTimesValue | null inOtherUnit: boolean @@ -111,50 +112,71 @@ export default React.memo(function ChildDayAttendance({ return ( - {!inOtherUnit && !isInBackupGroup ? ( - - {requiresBackupCare ? ( - - {i18n.unit.attendanceReservations.requiresBackupCare}{' '} - - - ) : ( - <> - - {attendance?.formatStart() ?? '-'} - - - {attendance?.end ? attendance.formatEnd() : '-'} - - - )} - - ) : null} - {!inOtherUnit && !isInBackupGroup && scheduleType !== 'TERM_BREAK' && ( - - - - )} + + {!inOtherUnit && !isInBackupGroup ? ( + + {requiresBackupCare ? ( + + {i18n.unit.attendanceReservations.requiresBackupCare}{' '} + + + ) : ( + <> + + {attendance?.interval?.formatStart() ?? '-'} + + + {attendance?.interval?.end + ? attendance.interval.formatEnd() + : '-'} + + + )} + + ) : null} + {!inOtherUnit && !isInBackupGroup && scheduleType !== 'TERM_BREAK' && ( + + + + )} + ) }) diff --git a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildReservationsTable.tsx b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildReservationsTable.tsx index d4d60325c6d..84875959a8e 100644 --- a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildReservationsTable.tsx +++ b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildReservationsTable.tsx @@ -342,7 +342,7 @@ const ChildRowGroup = React.memo(function ChildRowGroup({ date={date} attendanceIndex={index} dateInfo={dateInfo} - attendance={child.attendances[index]?.interval} + attendance={child.attendances[index]} reservations={child.reservations} dailyServiceTimes={child.dailyServiceTimes} inOtherUnit={child.inOtherUnit} diff --git a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx index cbf0e711dce..9081acdc320 100755 --- a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx +++ b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx @@ -2540,6 +2540,16 @@ export const fi = { requiresBackupCare: 'Tee varasijoitus', openReservationModal: 'Tee toistuva varaus', childCount: 'Lapsia läsnä', + lastModifiedStaff: (date: string, name: string) => ( +
+

*Henkilökunnan tekemä merkintä

+

+ Viimeksi muokattu {date}; muokkaaja: {name} +

+
+ ), + lastModifiedOther: (date: string, name: string) => + `Viimeksi muokattu ${date}; muokkaaja: ${name}`, reservationModal: { title: 'Tee varaus', selectedChildren: 'Lapset, joille varaus tehdään', From 8812fc38e95bdbe92c9a2b4aca5c21c1acb5ef9e Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Thu, 14 Nov 2024 22:01:13 +0200 Subject: [PATCH 06/14] WIP --- .../unit/unit-reservations/ChildDayReservation.tsx | 10 ++++++++-- .../fi/espoo/evaka/shared/db/SchemaConventionsTest.kt | 2 -- .../reservations/AttendanceReservationController.kt | 11 +++++++++++ .../fi/espoo/evaka/reservations/ReservationQueries.kt | 5 +++-- ...__attendance_reservation_modification_metadata.sql | 7 +++++++ 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayReservation.tsx b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayReservation.tsx index 3e071f3d341..7c8672c439b 100644 --- a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayReservation.tsx +++ b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayReservation.tsx @@ -119,8 +119,14 @@ export default React.memo(function ChildDayReservation({ ) : reservation && reservation.type === 'TIMES' ? ( ReservationInsert( @@ -548,6 +549,8 @@ class AttendanceReservationController( startTime = it.start, endTime = it.end, date = examinationDate, + createdAt = it.createdAt, + createdBy = it.createdBy, staffCreated = it.staffCreated, ) .toReservationTimes() @@ -960,6 +963,8 @@ private data class ReservationTimesForDate( val date: LocalDate, val startTime: LocalTime?, val endTime: LocalTime?, + val createdAt: HelsinkiDateTime, + val createdBy: EvakaUser, // TODO should these be modified_by instead? val staffCreated: Boolean, ) { fun toReservationTimes() = @@ -1005,6 +1010,12 @@ SELECT 'date', ar.date, 'startTime', ar.start_time, 'endTime', ar.end_time, + 'createdAt', ar.created_at, + 'createdBy', jsonb_build_object( + 'id', eu.id, + 'name', eu.name, + 'type', eu.type + ), 'staffCreated', eu.type <> 'CITIZEN' ) ORDER BY ar.date, ar.start_time) FROM attendance_reservation ar diff --git a/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt index 37019e28908..9b107dff371 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt @@ -121,14 +121,15 @@ data class ReservationInsert(val childId: ChildId, val date: LocalDate, val rang fun Database.Transaction.insertValidReservations( userId: EvakaUserId, + createdAt: HelsinkiDateTime, reservations: List, ): List { return reservations.mapNotNull { createQuery { sql( """ -INSERT INTO attendance_reservation (child_id, date, start_time, end_time, created_by) -SELECT ${bind(it.childId)}, ${bind(it.date)}, ${bind(it.range?.start)}, ${bind(it.range?.end)}, ${bind(userId)} +INSERT INTO attendance_reservation (child_id, date, start_time, end_time, created_at, created_by) +SELECT ${bind(it.childId)}, ${bind(it.date)}, ${bind(it.range?.start)}, ${bind(it.range?.end)}, ${bind(createdAt)}, ${bind(userId)} FROM realized_placement_all(${bind(it.date)}) rp JOIN daycare d ON d.id = rp.unit_id AND 'RESERVATIONS' = ANY(d.enabled_pilot_features) LEFT JOIN service_need sn ON sn.placement_id = rp.placement_id AND daterange(sn.start_date, sn.end_date, '[]') @> ${bind(it.date)} diff --git a/service/src/main/resources/db/migration/V463__attendance_reservation_modification_metadata.sql b/service/src/main/resources/db/migration/V463__attendance_reservation_modification_metadata.sql index 8ea9b65dd4e..86f63355d07 100644 --- a/service/src/main/resources/db/migration/V463__attendance_reservation_modification_metadata.sql +++ b/service/src/main/resources/db/migration/V463__attendance_reservation_modification_metadata.sql @@ -18,3 +18,10 @@ ALTER TABLE child_attendance RENAME COLUMN updated TO updated_at; CREATE TRIGGER set_timestamp BEFORE UPDATE ON child_attendance FOR EACH ROW EXECUTE PROCEDURE trigger_refresh_updated_at(); ALTER TABLE child_attendance RENAME COLUMN created TO created_at; + + +DROP TRIGGER set_timestamp ON attendance_reservation; +ALTER TABLE attendance_reservation RENAME COLUMN updated TO updated_at; +CREATE TRIGGER set_timestamp BEFORE UPDATE ON attendance_reservation FOR EACH ROW EXECUTE PROCEDURE trigger_refresh_updated_at(); + +ALTER TABLE attendance_reservation RENAME COLUMN created TO created_at; From fd593b14d3f07c3c26555490282fab8e5d251d0f Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Fri, 15 Nov 2024 14:08:27 +0200 Subject: [PATCH 07/14] WIP --- .../unit-reservations/ChildDayReservation.tsx | 4 ++-- .../evaka/reservations/ReservationQueries.kt | 17 ++++++++++++++++- .../fi/espoo/evaka/reservations/Reservations.kt | 1 + .../kotlin/fi/espoo/evaka/shared/dev/DevApi.kt | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayReservation.tsx b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayReservation.tsx index 7c8672c439b..60e2bf76c78 100644 --- a/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayReservation.tsx +++ b/frontend/src/employee-frontend/components/unit/unit-reservations/ChildDayReservation.tsx @@ -119,7 +119,7 @@ export default React.memo(function ChildDayReservation({ ) : reservation && reservation.type === 'TIMES' ? ( )} - {reservation.staffCreated && '*'} + {reservation?.staffCreated && '*'} ) : reservationIndex === 0 ? ( diff --git a/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt index 9b107dff371..c6eebb41f4b 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt @@ -29,6 +29,7 @@ import fi.espoo.evaka.shared.domain.HelsinkiDateTime import fi.espoo.evaka.shared.domain.TimeRange import fi.espoo.evaka.shared.domain.getHolidays import fi.espoo.evaka.shared.domain.isHoliday +import fi.espoo.evaka.user.EvakaUser import java.math.BigDecimal import java.time.LocalDate import java.time.LocalTime @@ -495,6 +496,8 @@ data class ConfirmedDayAbsenceInfo(val category: AbsenceCategory) data class ConfirmedDayReservationInfo( val start: LocalTime?, val end: LocalTime?, + val createdAt: HelsinkiDateTime, + val createdBy: EvakaUser, val staffCreated: Boolean, ) @@ -520,8 +523,20 @@ SELECT pcd.child_id, (SELECT coalesce(jsonb_agg(jsonb_build_object( 'start', s.start_time, 'end', s.end_time, + 'createdAt', s.created_at, + 'createdBy', jsonb_build_object( + 'id', s.created_by_id, + 'name', s.created_by_name, + 'type', s.created_by_type + ), 'staffCreated', s.staff_created)), '[]'::jsonb) - FROM (select ar.start_time, ar.end_time, eu.type <> 'CITIZEN' as staff_created + FROM (select ar.start_time, + ar.end_time, + ar.created_at, + eu.id AS created_by_id, + eu.name AS crated_by_name, + eu.type AS created_by_type, + eu.type <> 'CITIZEN' AS staff_created FROM attendance_reservation ar JOIN evaka_user eu ON ar.created_by = eu.id WHERE ar.child_id = pcd.child_id diff --git a/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt b/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt index f3f932afd8d..7e5240e79c6 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt @@ -319,6 +319,7 @@ fun createReservationsAndAbsences( val upsertedReservations = tx.insertValidReservations( user.evakaUserId, + now, validated.filterIsInstance().flatMap { res -> listOfNotNull(res.reservation, res.secondReservation).map { ReservationInsert(res.childId, res.date, it) 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 3e4a5b1785d..1744e39db4c 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 @@ -996,7 +996,7 @@ UPDATE placement SET end_date = ${bind(req.endDate)}, termination_requested_date db.connect { dbc -> dbc.transaction { tx -> tx.ensureFakeAdminExists() - tx.insertValidReservations(fakeAdmin.evakaUserId, body) + tx.insertValidReservations(fakeAdmin.evakaUserId, clock.now(), body) } } } From 0b576182e35fa654eb7397b2866ce3fc8a627948 Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Thu, 21 Nov 2024 22:14:49 +0200 Subject: [PATCH 08/14] WIP --- .../kotlin/fi/espoo/evaka/TestFixtures.kt | 7 ++ .../absence/AbsenceServiceIntegrationTest.kt | 4 +- .../evaka/attendance/AttendanceQueriesTest.kt | 111 +++++++++++++++-- .../GetAttendancesIntegrationTest.kt | 32 ++++- ...ceReservationsControllerIntegrationTest.kt | 115 ++++++++++++++++-- ...rvationControllerCitizenIntegrationTest.kt | 55 +++++++-- .../fi/espoo/evaka/absence/AbsenceQueries.kt | 2 +- .../espoo/evaka/attendance/ChildAttendance.kt | 4 +- .../attendance/ChildAttendanceQueries.kt | 18 ++- .../AttendanceReservationController.kt | 11 +- .../evaka/reservations/ReservationQueries.kt | 14 ++- .../espoo/evaka/reservations/Reservations.kt | 38 ++++-- .../evaka/shared/dev/DataInitializers.kt | 6 +- .../fi/espoo/evaka/reports/MealReportTests.kt | 12 ++ 14 files changed, 376 insertions(+), 53 deletions(-) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt index 619051f505d..0279ce9fb73 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt @@ -749,3 +749,10 @@ fun DevEmployee.toEvakaUser() = name = this.lastName + " " + this.firstName, type = EvakaUserType.EMPLOYEE, ) + +fun DevPerson.toEvakaUser(type: EvakaUserType) = + EvakaUser( + id = EvakaUserId(this.id.raw), + name = this.lastName + " " + this.firstName, + type = type, + ) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/absence/AbsenceServiceIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/absence/AbsenceServiceIntegrationTest.kt index 2d9eb69a850..4cc54793cea 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/absence/AbsenceServiceIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/absence/AbsenceServiceIntegrationTest.kt @@ -555,7 +555,7 @@ class AbsenceServiceIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr date = holidayPeriod.end.minusDays(2), startTime = null, endTime = null, - created = HelsinkiDateTime.of(placementStart, LocalTime.of(8, 0)), + createdAt = HelsinkiDateTime.of(placementStart, LocalTime.of(8, 0)), createdBy = EvakaUserId(employeeId.raw), ) ) @@ -565,7 +565,7 @@ class AbsenceServiceIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr date = holidayPeriod.end.minusDays(1), startTime = LocalTime.of(8, 0), endTime = LocalTime.of(16, 0), - created = HelsinkiDateTime.of(placementStart, LocalTime.of(9, 0)), + createdAt = HelsinkiDateTime.of(placementStart, LocalTime.of(9, 0)), createdBy = EvakaUserId(employeeId.raw), ) ) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/AttendanceQueriesTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/AttendanceQueriesTest.kt index a042076c009..f9e1b6d9877 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/AttendanceQueriesTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/AttendanceQueriesTest.kt @@ -5,6 +5,7 @@ package fi.espoo.evaka.attendance import fi.espoo.evaka.PureJdbiTest +import fi.espoo.evaka.shared.dev.DevEmployee import fi.espoo.evaka.shared.dev.DevPersonType import fi.espoo.evaka.shared.dev.insert import fi.espoo.evaka.shared.dev.insertTestChildAttendance @@ -13,6 +14,7 @@ import fi.espoo.evaka.testArea import fi.espoo.evaka.testChild_1 import fi.espoo.evaka.testChild_2 import fi.espoo.evaka.testDaycare +import fi.espoo.evaka.toEvakaUser import java.time.LocalDate import java.time.LocalTime import kotlin.test.assertEquals @@ -22,10 +24,12 @@ import org.junit.jupiter.api.Test class AttendanceQueriesTest : PureJdbiTest(resetDbBeforeEach = true) { private val now = HelsinkiDateTime.of(LocalDate.of(2021, 1, 1), LocalTime.of(12, 0, 0)) + private val employee = DevEmployee() @BeforeEach fun beforeEach() { db.transaction { tx -> + tx.insert(employee) tx.insert(testArea) tx.insert(testDaycare) listOf(testChild_1, testChild_2).forEach { tx.insert(it, DevPersonType.CHILD) } @@ -46,11 +50,23 @@ class AttendanceQueriesTest : PureJdbiTest(resetDbBeforeEach = true) { childId = testChild_1.id, arrived = now, departed = null, + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) } val attendances = db.read { it.getUnitChildAttendances(testDaycare.id, now) } assertEquals( - mapOf(testChild_1.id to listOf(AttendanceTimes(arrived = now, departed = null))), + mapOf( + testChild_1.id to + listOf( + AttendanceTimes( + arrived = now, + departed = null, + modifiedAt = now, + modifiedBy = employee.toEvakaUser(), + ) + ) + ), attendances, ) } @@ -63,13 +79,22 @@ class AttendanceQueriesTest : PureJdbiTest(resetDbBeforeEach = true) { childId = testChild_1.id, arrived = now.minusHours(1), departed = now, + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) } val attendances = db.read { it.getUnitChildAttendances(testDaycare.id, now) } assertEquals( mapOf( testChild_1.id to - listOf(AttendanceTimes(arrived = now.minusHours(1), departed = now)) + listOf( + AttendanceTimes( + arrived = now.minusHours(1), + departed = now, + modifiedAt = now, + modifiedBy = employee.toEvakaUser(), + ) + ) ), attendances, ) @@ -83,21 +108,39 @@ class AttendanceQueriesTest : PureJdbiTest(resetDbBeforeEach = true) { childId = testChild_1.id, arrived = now.minusHours(1), departed = now, + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) tx.insertTestChildAttendance( unitId = testDaycare.id, childId = testChild_2.id, arrived = now.minusHours(2), departed = null, + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) } val attendances = db.read { it.getUnitChildAttendances(testDaycare.id, now) } assertEquals( mapOf( testChild_1.id to - listOf(AttendanceTimes(arrived = now.minusHours(1), departed = now)), + listOf( + AttendanceTimes( + arrived = now.minusHours(1), + departed = now, + modifiedAt = now, + modifiedBy = employee.toEvakaUser(), + ) + ), testChild_2.id to - listOf(AttendanceTimes(arrived = now.minusHours(2), departed = null)), + listOf( + AttendanceTimes( + arrived = now.minusHours(2), + departed = null, + modifiedAt = now, + modifiedBy = employee.toEvakaUser(), + ) + ), ), attendances, ) @@ -111,24 +154,32 @@ class AttendanceQueriesTest : PureJdbiTest(resetDbBeforeEach = true) { childId = testChild_1.id, arrived = now.minusHours(3), departed = now.minusHours(2), + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) tx.insertTestChildAttendance( unitId = testDaycare.id, childId = testChild_1.id, arrived = now.minusHours(1), departed = now, + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) tx.insertTestChildAttendance( unitId = testDaycare.id, childId = testChild_2.id, arrived = now.minusHours(4), departed = now.minusHours(3), + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) tx.insertTestChildAttendance( unitId = testDaycare.id, childId = testChild_2.id, arrived = now.minusHours(2), departed = null, + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) } val attendances = db.read { it.getUnitChildAttendances(testDaycare.id, now) } @@ -137,13 +188,33 @@ class AttendanceQueriesTest : PureJdbiTest(resetDbBeforeEach = true) { // Newest attendance is first testChild_1.id to listOf( - AttendanceTimes(arrived = now.minusHours(1), departed = now), - AttendanceTimes(arrived = now.minusHours(3), departed = now.minusHours(2)), + AttendanceTimes( + arrived = now.minusHours(1), + departed = now, + modifiedAt = now, + modifiedBy = employee.toEvakaUser(), + ), + AttendanceTimes( + arrived = now.minusHours(3), + departed = now.minusHours(2), + modifiedAt = now, + modifiedBy = employee.toEvakaUser(), + ), ), testChild_2.id to listOf( - AttendanceTimes(arrived = now.minusHours(2), departed = null), - AttendanceTimes(arrived = now.minusHours(4), departed = now.minusHours(3)), + AttendanceTimes( + arrived = now.minusHours(2), + departed = null, + modifiedAt = now, + modifiedBy = employee.toEvakaUser(), + ), + AttendanceTimes( + arrived = now.minusHours(4), + departed = now.minusHours(3), + modifiedAt = now, + modifiedBy = employee.toEvakaUser(), + ), ), ), attendances, @@ -158,19 +229,30 @@ class AttendanceQueriesTest : PureJdbiTest(resetDbBeforeEach = true) { childId = testChild_1.id, arrived = now.minusDays(1), departed = now.minusDays(1).atEndOfDay(), + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) tx.insertTestChildAttendance( unitId = testDaycare.id, childId = testChild_1.id, arrived = now.atStartOfDay(), departed = null, + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) } val attendances = db.read { it.getUnitChildAttendances(testDaycare.id, now) } assertEquals( mapOf( testChild_1.id to - listOf(AttendanceTimes(arrived = now.minusDays(1), departed = null)) + listOf( + AttendanceTimes( + arrived = now.minusDays(1), + departed = null, + modifiedAt = now, + modifiedBy = employee.toEvakaUser(), + ) + ) ), attendances, ) @@ -184,12 +266,16 @@ class AttendanceQueriesTest : PureJdbiTest(resetDbBeforeEach = true) { childId = testChild_1.id, arrived = now.minusDays(1), departed = now.minusDays(1).atEndOfDay(), + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) tx.insertTestChildAttendance( unitId = testDaycare.id, childId = testChild_1.id, arrived = now.atStartOfDay(), departed = now.minusMinutes(45), + modifiedAt = now, + modifiedBy = employee.evakaUserId, ) } val attendances = db.read { it.getUnitChildAttendances(testDaycare.id, now) } @@ -197,7 +283,12 @@ class AttendanceQueriesTest : PureJdbiTest(resetDbBeforeEach = true) { mapOf( testChild_1.id to listOf( - AttendanceTimes(arrived = now.minusDays(1), departed = now.minusMinutes(45)) + AttendanceTimes( + arrived = now.minusDays(1), + departed = now.minusMinutes(45), + modifiedAt = now, + modifiedBy = employee.toEvakaUser(), + ) ) ), attendances, diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt index 5e31bba7501..9d5996d8e50 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt @@ -21,6 +21,7 @@ import fi.espoo.evaka.shared.dev.DevAbsence import fi.espoo.evaka.shared.dev.DevBackupCare import fi.espoo.evaka.shared.dev.DevDaycareGroup import fi.espoo.evaka.shared.dev.DevDaycareGroupPlacement +import fi.espoo.evaka.shared.dev.DevEmployee import fi.espoo.evaka.shared.dev.DevPersonType import fi.espoo.evaka.shared.dev.DevPlacement import fi.espoo.evaka.shared.dev.DevReservation @@ -36,6 +37,7 @@ import fi.espoo.evaka.testArea import fi.espoo.evaka.testChild_1 import fi.espoo.evaka.testDaycare import fi.espoo.evaka.testDaycare2 +import fi.espoo.evaka.toEvakaUser import java.time.LocalDate import java.time.LocalTime import java.util.UUID @@ -51,8 +53,12 @@ import org.springframework.beans.factory.annotation.Autowired class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = true) { @Autowired private lateinit var childAttendanceController: ChildAttendanceController + // TODO Re-write this a bit to use DevMobileDevice instead of what it actually does. + + private val evakaUser2 = DevEmployee() private val mobileUser = AuthenticatedUser.MobileDevice(MobileDeviceId(UUID.randomUUID())) - private val mobileUser2 = AuthenticatedUser.MobileDevice(MobileDeviceId(UUID.randomUUID())) + private val mobileUser2 = + AuthenticatedUser.MobileDevice(MobileDeviceId(UUID.randomUUID()), evakaUser2.id) private val groupId = GroupId(UUID.randomUUID()) private val groupId2 = GroupId(UUID.randomUUID()) private val groupName = "Testaajat" @@ -413,13 +419,21 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr date = now.toLocalDate(), startTime = reservationStart, endTime = reservationEnd, + createdAt = now, createdBy = mobileUser2.evakaUserId, ) ) } val child = expectOneChild() assertEquals( - listOf(ReservationResponse.Times(TimeRange(reservationStart, reservationEnd), true)), + listOf( + ReservationResponse.Times( + TimeRange(reservationStart, reservationEnd), + true, + now, + evakaUser2.toEvakaUser(), + ) + ), child.reservations, ) } @@ -438,7 +452,10 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr ) } val child = expectOneChild() - assertEquals(listOf(ReservationResponse.NoTimes(true)), child.reservations) + assertEquals( + listOf(ReservationResponse.NoTimes(true, now, evakaUser2.toEvakaUser())), + child.reservations, + ) } @Test @@ -467,7 +484,14 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr } val childInBackup = expectOneChild(backupUnitId, mobileUser2) assertEquals( - listOf(ReservationResponse.Times(TimeRange(reservationStart, reservationEnd), true)), + listOf( + ReservationResponse.Times( + TimeRange(reservationStart, reservationEnd), + true, + now, + evakaUser2.toEvakaUser(), + ) + ), childInBackup.reservations, ) } diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/AttendanceReservationsControllerIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/AttendanceReservationsControllerIntegrationTest.kt index 5b99311b7e2..7d33ae1cc48 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/AttendanceReservationsControllerIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/AttendanceReservationsControllerIntegrationTest.kt @@ -68,6 +68,7 @@ import fi.espoo.evaka.testChild_5 import fi.espoo.evaka.testChild_6 import fi.espoo.evaka.testDaycare import fi.espoo.evaka.testDaycare2 +import fi.espoo.evaka.toEvakaUser import fi.espoo.evaka.withHolidays import java.math.BigDecimal import java.time.LocalDate @@ -105,6 +106,9 @@ class AttendanceReservationsControllerIntegrationTest : private val fullDay = TimeRange(LocalTime.parse("00:00"), LocalTime.parse("23:59")) + private val employee = DevEmployee(employeeId) + private val employee2 = DevEmployee(employeeId2) + @BeforeEach fun beforeEach() { db.transaction { tx -> @@ -120,9 +124,9 @@ class AttendanceReservationsControllerIntegrationTest : tx.insert(testGroup2) tx.insert(testGroupInDaycare2) - tx.insert(DevEmployee(employeeId)) + tx.insert(employee) tx.insertDaycareAclRow(testDaycare.id, employeeId, UserRole.STAFF) - tx.insert(DevEmployee(employeeId2)) + tx.insert(employee2) tx.insertDaycareAclRow(testDaycare.id, employeeId2, UserRole.STAFF) } } @@ -181,6 +185,7 @@ class AttendanceReservationsControllerIntegrationTest : date = mon, startTime = LocalTime.of(8, 0), endTime = LocalTime.of(16, 0), + createdAt = now, createdBy = EvakaUserId(employeeId.raw), ) ) @@ -189,12 +194,15 @@ class AttendanceReservationsControllerIntegrationTest : unitId = testDaycare.id, arrived = HelsinkiDateTime.of(mon, LocalTime.of(8, 15)), departed = HelsinkiDateTime.of(mon, LocalTime.of(16, 5)), + modifiedAt = now, + modifiedBy = EvakaUserId(employeeId.raw), ) it.insert( DevAbsence( childId = testChild_1.id, date = tue, absenceType = AbsenceType.OTHER_ABSENCE, + modifiedAt = now, modifiedBy = EvakaUserId(employeeId.raw), absenceCategory = AbsenceCategory.BILLABLE, ) @@ -206,6 +214,7 @@ class AttendanceReservationsControllerIntegrationTest : date = wed, startTime = null, endTime = null, + createdAt = now, createdBy = EvakaUserId(employeeId.raw), ) ) @@ -310,6 +319,7 @@ class AttendanceReservationsControllerIntegrationTest : date = thu, startTime = LocalTime.of(9, 0), endTime = LocalTime.of(15, 0), + createdAt = now, createdBy = EvakaUserId(employeeId.raw), ) ) @@ -320,6 +330,7 @@ class AttendanceReservationsControllerIntegrationTest : date = fri, startTime = LocalTime.of(7, 0), endTime = LocalTime.of(17, 0), + createdAt = now, createdBy = EvakaUserId(employeeId.raw), ) ) @@ -420,13 +431,22 @@ class AttendanceReservationsControllerIntegrationTest : ReservationResponse.Times( TimeRange(LocalTime.of(8, 0), LocalTime.of(16, 0)), true, + now, + employee.toEvakaUser(), ) ), attendances = listOf( - TimeInterval( - start = LocalTime.of(8, 15), - end = LocalTime.of(16, 5), + AttendanceTimesForDate( + date = mon, // TODO not sure + interval = + TimeInterval( + start = LocalTime.of(8, 15), + end = LocalTime.of(16, 5), + ), + modifiedAt = now, + modifiedBy = employee.toEvakaUser(), + staffModified = true, ) ), absenceBillable = null, @@ -475,7 +495,8 @@ class AttendanceReservationsControllerIntegrationTest : assertEquals( UnitAttendanceReservations.ChildRecordOfDay( childId = testChild_1.id, - reservations = listOf(ReservationResponse.NoTimes(true)), + reservations = + listOf(ReservationResponse.NoTimes(true, now, employee.toEvakaUser())), attendances = emptyList(), absenceBillable = null, absenceNonbillable = null, @@ -567,6 +588,8 @@ class AttendanceReservationsControllerIntegrationTest : ReservationResponse.Times( TimeRange(LocalTime.of(9, 0), LocalTime.of(15, 0)), true, + now, + employee.toEvakaUser(), ) ), attendances = emptyList(), @@ -660,6 +683,7 @@ class AttendanceReservationsControllerIntegrationTest : date = mon, startTime = LocalTime.of(19, 0), endTime = LocalTime.of(23, 59), + createdAt = now, createdBy = EvakaUserId(employeeId.raw), ), DevReservation( @@ -667,6 +691,7 @@ class AttendanceReservationsControllerIntegrationTest : date = tue, startTime = LocalTime.of(0, 0), endTime = LocalTime.of(8, 0), + createdAt = now, createdBy = EvakaUserId(employeeId.raw), ), DevReservation( @@ -674,6 +699,7 @@ class AttendanceReservationsControllerIntegrationTest : date = tue, startTime = LocalTime.of(17, 30), endTime = LocalTime.of(23, 59), + createdAt = now, createdBy = EvakaUserId(employeeId.raw), ), DevReservation( @@ -681,6 +707,7 @@ class AttendanceReservationsControllerIntegrationTest : date = wed, startTime = LocalTime.of(0, 0), endTime = LocalTime.of(9, 30), + createdAt = now, createdBy = EvakaUserId(employeeId.raw), ), ) @@ -703,6 +730,8 @@ class AttendanceReservationsControllerIntegrationTest : unitId = testDaycare.id, arrived = it.first, departed = it.second, + modifiedAt = now, + modifiedBy = EvakaUserId(employeeId.raw), ) } } @@ -718,9 +747,20 @@ class AttendanceReservationsControllerIntegrationTest : ReservationResponse.Times( TimeRange(LocalTime.of(19, 0), LocalTime.of(23, 59)), true, + now, + employee.toEvakaUser(), + ) + ), + attendances = + listOf( + AttendanceTimesForDate( + mon, + TimeInterval(LocalTime.of(19, 10), LocalTime.of(23, 59)), + now, + employee.toEvakaUser(), + true, ) ), - attendances = listOf(TimeInterval(LocalTime.of(19, 10), LocalTime.of(23, 59))), absenceBillable = null, absenceNonbillable = null, possibleAbsenceCategories = setOf(AbsenceCategory.BILLABLE), @@ -743,16 +783,32 @@ class AttendanceReservationsControllerIntegrationTest : ReservationResponse.Times( TimeRange(LocalTime.of(0, 0), LocalTime.of(8, 0)), true, + now, + employee.toEvakaUser(), ), ReservationResponse.Times( TimeRange(LocalTime.of(17, 30), LocalTime.of(23, 59)), true, + now, + employee.toEvakaUser(), ), ), attendances = listOf( - TimeInterval(LocalTime.of(0, 0), LocalTime.of(10, 30)), - TimeInterval(LocalTime.of(17, 0), null), + AttendanceTimesForDate( + tue, + TimeInterval(LocalTime.of(0, 0), LocalTime.of(10, 30)), + now, + employee.toEvakaUser(), + true, + ), + AttendanceTimesForDate( + tue, + TimeInterval(LocalTime.of(17, 0), null), + now, + employee.toEvakaUser(), + true, + ), ), absenceBillable = null, absenceNonbillable = null, @@ -776,6 +832,8 @@ class AttendanceReservationsControllerIntegrationTest : ReservationResponse.Times( TimeRange(LocalTime.of(0, 0), LocalTime.of(9, 30)), true, + now, + employee.toEvakaUser(), ) ), attendances = emptyList(), @@ -975,13 +1033,26 @@ class AttendanceReservationsControllerIntegrationTest : ReservationResponse.Times( TimeRange(LocalTime.of(9, 0), LocalTime.of(17, 0)), true, + testNow, + employee.toEvakaUser(), ), ReservationResponse.Times( TimeRange(LocalTime.of(22, 0), LocalTime.of(23, 59)), true, + testNow, + employee.toEvakaUser(), ), ), - attendances = listOf(TimeInterval(start = LocalTime.of(12, 30), end = null)), + attendances = + listOf( + AttendanceTimesForDate( + wed, + TimeInterval(start = LocalTime.of(12, 30), end = null), + testNow, + employee.toEvakaUser(), + true, + ) + ), absenceBillable = AbsenceTypeResponse(AbsenceType.OTHER_ABSENCE, true), absenceNonbillable = AbsenceTypeResponse(AbsenceType.OTHER_ABSENCE, true), possibleAbsenceCategories = @@ -1025,14 +1096,26 @@ class AttendanceReservationsControllerIntegrationTest : ReservationResponse.Times( TimeRange(LocalTime.of(9, 0), LocalTime.of(17, 0)), true, + testNow, + employee2.toEvakaUser(), ), ReservationResponse.Times( TimeRange(LocalTime.of(21, 30), LocalTime.of(23, 59)), true, + testNow, + employee2.toEvakaUser(), ), ), attendances = - listOf(TimeInterval(start = LocalTime.of(12, 30), end = LocalTime.of(17, 0))), + listOf( + AttendanceTimesForDate( + wed, + TimeInterval(start = LocalTime.of(12, 30), end = LocalTime.of(17, 0)), + testNow, + employee2.toEvakaUser(), + true, + ) + ), absenceBillable = AbsenceTypeResponse(AbsenceType.FORCE_MAJEURE, true), absenceNonbillable = AbsenceTypeResponse(AbsenceType.OTHER_ABSENCE, true), possibleAbsenceCategories = @@ -1340,10 +1423,14 @@ class AttendanceReservationsControllerIntegrationTest : ReservationResponse.Times( TimeRange(LocalTime.of(8, 0), LocalTime.of(12, 0)), true, + now, + employee.toEvakaUser(), ), ReservationResponse.Times( TimeRange(LocalTime.of(13, 0), LocalTime.of(16, 0)), true, + now, + employee.toEvakaUser(), ), ), groupId = testGroup1.id, @@ -1649,6 +1736,7 @@ class AttendanceReservationsControllerIntegrationTest : date = mon, startTime = LocalTime.of(8, 0), endTime = LocalTime.of(12, 0), + createdAt = now, createdBy = EvakaUserId(employeeId.raw), ) ) @@ -1658,6 +1746,7 @@ class AttendanceReservationsControllerIntegrationTest : date = mon, startTime = LocalTime.of(13, 0), endTime = LocalTime.of(16, 0), + createdAt = now, createdBy = EvakaUserId(employeeId.raw), ) ) @@ -1666,6 +1755,7 @@ class AttendanceReservationsControllerIntegrationTest : childId = testChild_1.id, date = tue, absenceType = AbsenceType.OTHER_ABSENCE, + modifiedAt = now, modifiedBy = EvakaUserId(employeeId.raw), absenceCategory = AbsenceCategory.BILLABLE, ) @@ -1675,6 +1765,7 @@ class AttendanceReservationsControllerIntegrationTest : childId = testChild_1.id, date = tue, absenceType = AbsenceType.OTHER_ABSENCE, + modifiedAt = now, modifiedBy = EvakaUserId(employeeId.raw), absenceCategory = AbsenceCategory.NONBILLABLE, ) @@ -1684,6 +1775,7 @@ class AttendanceReservationsControllerIntegrationTest : childId = testChild_1.id, date = fri, absenceType = AbsenceType.OTHER_ABSENCE, + modifiedAt = now, modifiedBy = EvakaUserId(employeeId.raw), absenceCategory = AbsenceCategory.NONBILLABLE, ) @@ -1693,6 +1785,7 @@ class AttendanceReservationsControllerIntegrationTest : childId = testChild_2.id, date = tue, absenceType = AbsenceType.OTHER_ABSENCE, + modifiedAt = now, modifiedBy = EvakaUserId(employeeId.raw), absenceCategory = AbsenceCategory.BILLABLE, ) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/ReservationControllerCitizenIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/ReservationControllerCitizenIntegrationTest.kt index e2894aae5b9..a38572d994f 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/ReservationControllerCitizenIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/ReservationControllerCitizenIntegrationTest.kt @@ -59,6 +59,8 @@ import fi.espoo.evaka.snDaycareContractDays10 import fi.espoo.evaka.snDaycareFullDay35 import fi.espoo.evaka.snDaycareHours120 import fi.espoo.evaka.snPreschoolDaycareContractDays13 +import fi.espoo.evaka.toEvakaUser +import fi.espoo.evaka.user.EvakaUserType import fi.espoo.evaka.withHolidays import java.time.LocalDate import java.time.LocalTime @@ -96,6 +98,9 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB private val startTime = LocalTime.of(9, 0) private val endTime = LocalTime.of(17, 0) + private val now = HelsinkiDateTime.of(mockToday, LocalTime.of(13, 0)) + private val employee = DevEmployee() + @BeforeEach fun before() { reservationControllerCitizen = @@ -118,7 +123,6 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB val area = DevCareArea() val daycare = DevDaycare(areaId = area.id, enabledPilotFeatures = setOf(PilotFeature.RESERVATIONS)) - val employee = DevEmployee() val adult = DevPerson() val child1 = DevPerson(dateOfBirth = LocalDate.of(2015, 1, 1)) @@ -553,6 +557,7 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB date = monday, startTime = LocalTime.of(9, 0), endTime = LocalTime.of(16, 0), + createdAt = now, createdBy = adult.evakaUserId(), ), DevReservation( @@ -560,6 +565,7 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB date = tuesday, startTime = LocalTime.of(9, 0), endTime = LocalTime.of(16, 0), + createdAt = now, createdBy = adult.evakaUserId(), ), // No reservation on Wednesday @@ -613,6 +619,8 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB ReservationResponse.Times( TimeRange(LocalTime.of(9, 0), LocalTime.of(16, 0)), false, + now, + adult.toEvakaUser(EvakaUserType.CITIZEN), ) ), attendances = @@ -641,6 +649,8 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB ReservationResponse.Times( TimeRange(LocalTime.of(9, 0), LocalTime.of(16, 0)), false, + now, + adult.toEvakaUser(EvakaUserType.CITIZEN), ) ), attendances = @@ -1118,6 +1128,7 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB ), ) }, + now, ) val res = @@ -1159,14 +1170,24 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB child1.id, reservations = listOf( - ReservationResponse.Times(TimeRange(startTime, endTime), false) + ReservationResponse.Times( + TimeRange(startTime, endTime), + false, + now, + adult.toEvakaUser(EvakaUserType.CITIZEN), + ) ), ), dayChild( child2.id, reservations = listOf( - ReservationResponse.Times(TimeRange(startTime, endTime), false) + ReservationResponse.Times( + TimeRange(startTime, endTime), + false, + now, + adult.toEvakaUser(EvakaUserType.CITIZEN), + ) ), ), ) @@ -1183,14 +1204,24 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB child1.id, reservations = listOf( - ReservationResponse.Times(TimeRange(startTime, endTime), false) + ReservationResponse.Times( + TimeRange(startTime, endTime), + false, + now, + adult.toEvakaUser(EvakaUserType.CITIZEN), + ) ), ), dayChild( child2.id, reservations = listOf( - ReservationResponse.Times(TimeRange(startTime, endTime), false) + ReservationResponse.Times( + TimeRange(startTime, endTime), + false, + now, + adult.toEvakaUser(EvakaUserType.CITIZEN), + ) ), ), ) @@ -1210,7 +1241,7 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB val area = DevCareArea() val daycare = DevDaycare(areaId = area.id, enabledPilotFeatures = setOf(PilotFeature.RESERVATIONS)) - val employee = DevEmployee() + // val employee = DevEmployee() val adult = DevPerson() val child = DevPerson() @@ -1218,7 +1249,7 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB db.transaction { tx -> tx.insert(area) tx.insert(daycare) - tx.insert(employee) + //tx.insert(employee) tx.insert(adult, DevPersonType.ADULT) tx.insert(child, DevPersonType.CHILD) @@ -1245,6 +1276,7 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB ), DailyReservationRequest.Absent(child.id, tuesday), ), + now, ) val res = @@ -1259,7 +1291,14 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB dayChild( child.id, reservations = - listOf(ReservationResponse.Times(TimeRange(startTime, endTime), false)), + listOf( + ReservationResponse.Times( + TimeRange(startTime, endTime), + false, + now, + adult.toEvakaUser(EvakaUserType.CITIZEN), + ) + ), ) ), day.children, diff --git a/service/src/main/kotlin/fi/espoo/evaka/absence/AbsenceQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/absence/AbsenceQueries.kt index 5244ad5f442..5526384cab8 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/absence/AbsenceQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/absence/AbsenceQueries.kt @@ -556,7 +556,7 @@ fun Database.Read.getGroupReservations( WITH all_placements AS ( ${subquery(placementsQuery(dateRange, groupId))} ) -SELECT r.child_id, r.date, r.start_time, r.end_time, e.type AS created_by_evaka_user_type, r.created AS created_date +SELECT r.child_id, r.date, r.start_time, r.end_time, e.type AS created_by_evaka_user_type, r.created_at AS created_date FROM attendance_reservation r JOIN evaka_user e ON r.created_by = e.id WHERE between_start_and_end(${bind(dateRange)}, r.date) diff --git a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt index b7b88714cf6..53571195e55 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendance.kt @@ -72,8 +72,8 @@ data class ChildAttendanceRow( data class AttendanceTimes( val arrived: HelsinkiDateTime, val departed: HelsinkiDateTime?, - val modifiedAt: HelsinkiDateTime? = null, - @Nested("modified_by") val modifiedBy: EvakaUser? = null, + val modifiedAt: HelsinkiDateTime, + @Nested("modified_by") val modifiedBy: EvakaUser, ) data class ChildAbsence(val category: AbsenceCategory, val type: AbsenceType) diff --git a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt index c5588d87527..ca1dde75546 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceQueries.kt @@ -339,7 +339,23 @@ private fun mergeOverNightRanges(attendances: List): List attendance.modifiedAt) { + acc.dropLast(1) + + AttendanceTimes( + previous.arrived, + attendance.departed, + previous.modifiedAt, + previous.modifiedBy, + ) + } else { + acc.dropLast(1) + + AttendanceTimes( + previous.arrived, + attendance.departed, + attendance.modifiedAt, + attendance.modifiedBy, + ) + } } else { acc + attendance } diff --git a/service/src/main/kotlin/fi/espoo/evaka/reservations/AttendanceReservationController.kt b/service/src/main/kotlin/fi/espoo/evaka/reservations/AttendanceReservationController.kt index f8dc5999c53..14d8c3b60e4 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/reservations/AttendanceReservationController.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/reservations/AttendanceReservationController.kt @@ -969,8 +969,15 @@ private data class ReservationTimesForDate( ) { fun toReservationTimes() = when { - startTime == null || endTime == null -> ReservationResponse.NoTimes(staffCreated) - else -> ReservationResponse.Times(TimeRange(startTime, endTime), staffCreated) + startTime == null || endTime == null -> + ReservationResponse.NoTimes(staffCreated, createdAt, createdBy) + else -> + ReservationResponse.Times( + TimeRange(startTime, endTime), + staffCreated, + createdAt, + createdBy, + ) } } diff --git a/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt index c6eebb41f4b..ff223a62e82 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/reservations/ReservationQueries.kt @@ -169,7 +169,11 @@ SELECT ar.child_id, ar.start_time, ar.end_time, - eu.type <> 'CITIZEN' as staff_created + eu.type <> 'CITIZEN' as staff_created, + ar.created_at, + eu.id AS created_by_id, + eu.name AS created_by_name, + eu.type AS created_by_type FROM attendance_reservation ar JOIN evaka_user eu ON ar.created_by = eu.id WHERE ${predicate(where.forTable("ar"))} @@ -182,6 +186,12 @@ WHERE ${predicate(where.forTable("ar"))} column("child_id"), Reservation.of(column("start_time"), column("end_time")), column("staff_created"), + column("created_at"), + EvakaUser( + column("created_by_id"), + column("created_by_name"), + column("created_by_type"), + ), ) } @@ -534,7 +544,7 @@ SELECT pcd.child_id, ar.end_time, ar.created_at, eu.id AS created_by_id, - eu.name AS crated_by_name, + eu.name AS created_by_name, eu.type AS created_by_type, eu.type <> 'CITIZEN' AS staff_created FROM attendance_reservation ar diff --git a/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt b/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt index 7e5240e79c6..3f0bf0a2704 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/reservations/Reservations.kt @@ -41,6 +41,7 @@ import fi.espoo.evaka.shared.domain.HelsinkiDateTime import fi.espoo.evaka.shared.domain.TimeInterval import fi.espoo.evaka.shared.domain.TimeRange import fi.espoo.evaka.shared.utils.mapOfNotNullValues +import fi.espoo.evaka.user.EvakaUser import java.time.LocalDate import java.time.LocalTime import kotlin.math.roundToLong @@ -115,10 +116,20 @@ data class AbsenceTypeResponse(val absenceType: AbsenceType, val staffCreated: B @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") sealed class ReservationResponse : Comparable { - @JsonTypeName("NO_TIMES") data class NoTimes(val staffCreated: Boolean) : ReservationResponse() + @JsonTypeName("NO_TIMES") + data class NoTimes( + val staffCreated: Boolean, + val modifiedAt: HelsinkiDateTime, + val modifiedBy: EvakaUser, + ) : ReservationResponse() @JsonTypeName("TIMES") - data class Times(val range: TimeRange, val staffCreated: Boolean) : ReservationResponse() + data class Times( + val range: TimeRange, + val staffCreated: Boolean, + val modifiedAt: HelsinkiDateTime, + val modifiedBy: EvakaUser, + ) : ReservationResponse() override fun compareTo(other: ReservationResponse): Int { return when { @@ -140,9 +151,19 @@ sealed class ReservationResponse : Comparable { companion object { fun from(reservationRow: ReservationRow) = when (reservationRow.reservation) { - is Reservation.NoTimes -> NoTimes(reservationRow.staffCreated) + is Reservation.NoTimes -> + NoTimes( + reservationRow.staffCreated, + reservationRow.modifiedAt, + reservationRow.modifiedBy, + ) is Reservation.Times -> - Times(reservationRow.reservation.range, reservationRow.staffCreated) + Times( + reservationRow.reservation.range, + reservationRow.staffCreated, + reservationRow.modifiedAt, + reservationRow.modifiedBy, + ) } } } @@ -152,6 +173,8 @@ data class ReservationRow( val childId: ChildId, val reservation: Reservation, val staffCreated: Boolean, + val modifiedAt: HelsinkiDateTime, + val modifiedBy: EvakaUser, ) data class CreateReservationsResult( @@ -455,7 +478,7 @@ fun upsertChildDatePresence( val insertedReservations = reservations .filter { it.second == null } - .map { tx.insertReservation(userId, input.date, input.childId, it.first) } + .map { tx.insertReservation(userId, now, input.date, input.childId, it.first) } val deletedAttendances = tx.deleteAttendancesByDate(childId = input.childId, date = input.date) val insertedAttendances = @@ -548,6 +571,7 @@ private fun Database.Transaction.deleteReservations( private fun Database.Transaction.insertReservation( userId: EvakaUserId, + now: HelsinkiDateTime, date: LocalDate, childId: ChildId, reservation: Reservation, @@ -555,8 +579,8 @@ private fun Database.Transaction.insertReservation( return createQuery { sql( """ - INSERT INTO attendance_reservation (child_id, created_by, date, start_time, end_time) - VALUES (${bind(childId)}, ${bind(userId)}, ${bind(date)}, ${bind(reservation.asTimeRange()?.start)}, ${bind(reservation.asTimeRange()?.end)}) + INSERT INTO attendance_reservation (child_id, created_at, created_by, date, start_time, end_time) + VALUES (${bind(childId)}, ${bind(now)}, ${bind(userId)}, ${bind(date)}, ${bind(reservation.asTimeRange()?.start)}, ${bind(reservation.asTimeRange()?.end)}) RETURNING id """ ) 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 9c8d9ee0a28..296e1e60560 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 @@ -1188,7 +1188,7 @@ data class DevReservation( val date: LocalDate, val startTime: LocalTime?, val endTime: LocalTime?, - val created: HelsinkiDateTime = HelsinkiDateTime.now(), + val createdAt: HelsinkiDateTime = HelsinkiDateTime.now(), val createdBy: EvakaUserId, ) @@ -1196,8 +1196,8 @@ fun Database.Transaction.insert(row: DevReservation): AttendanceReservationId = createUpdate { sql( """ -INSERT INTO attendance_reservation (id, child_id, date, start_time, end_time, created, created_by) -VALUES (${bind(row.id)}, ${bind(row.childId)}, ${bind(row.date)}, ${bind(row.startTime)}, ${bind(row.endTime)}, ${bind(row.created)}, ${bind(row.createdBy)}) +INSERT INTO attendance_reservation (id, child_id, date, start_time, end_time, created_at, created_by) +VALUES (${bind(row.id)}, ${bind(row.childId)}, ${bind(row.date)}, ${bind(row.startTime)}, ${bind(row.endTime)}, ${bind(row.createdAt)}, ${bind(row.createdBy)}) RETURNING id """ ) diff --git a/service/src/test/kotlin/fi/espoo/evaka/reports/MealReportTests.kt b/service/src/test/kotlin/fi/espoo/evaka/reports/MealReportTests.kt index 1aff5c73070..1e86fec071f 100644 --- a/service/src/test/kotlin/fi/espoo/evaka/reports/MealReportTests.kt +++ b/service/src/test/kotlin/fi/espoo/evaka/reports/MealReportTests.kt @@ -16,11 +16,15 @@ import fi.espoo.evaka.reservations.ReservationResponse import fi.espoo.evaka.reservations.UnitAttendanceReservations import fi.espoo.evaka.serviceneed.ShiftCareType import fi.espoo.evaka.shared.ChildId +import fi.espoo.evaka.shared.EvakaUserId import fi.espoo.evaka.shared.PreschoolTermId import fi.espoo.evaka.shared.data.DateSet import fi.espoo.evaka.shared.domain.FiniteDateRange +import fi.espoo.evaka.shared.domain.MockEvakaClock import fi.espoo.evaka.shared.domain.TimeRange import fi.espoo.evaka.specialdiet.SpecialDiet +import fi.espoo.evaka.user.EvakaUser +import fi.espoo.evaka.user.EvakaUserType import java.time.LocalDate import java.time.LocalTime import java.util.* @@ -30,6 +34,8 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue class MealReportTests { + val clock = MockEvakaClock(2024, 1, 3, 3, 7) + @Test fun `mealReportData should return no meals for absent child`() { val testDate = LocalDate.of(2023, 4, 15) @@ -736,6 +742,8 @@ class MealReportTests { val lunchTime = TimeRange(LocalTime.of(11, 0), LocalTime.of(11, 20)) val snackTime = TimeRange(LocalTime.of(14, 0), LocalTime.of(14, 20)) + val user = EvakaUser(EvakaUserId(UUID.randomUUID()), "Test Name", EvakaUserType.EMPLOYEE) + val childData = mapOf( childId1 to @@ -757,6 +765,8 @@ class MealReportTests { ReservationResponse.Times( TimeRange(LocalTime.of(8, 0), LocalTime.of(14, 20)), false, + clock.now(), + user, ) ) ), @@ -785,6 +795,8 @@ class MealReportTests { ReservationResponse.Times( TimeRange(LocalTime.of(10, 0), LocalTime.of(14, 20)), false, + clock.now(), + user, ) ) ), From fc672b4c00a40add70ddc2c51b74e214d6975ee9 Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Mon, 25 Nov 2024 13:51:43 +0200 Subject: [PATCH 09/14] WIP --- .../kotlin/fi/espoo/evaka/TestFixtures.kt | 4 +++ .../GetAttendancesIntegrationTest.kt | 31 +++++++++++-------- ...rvationControllerCitizenIntegrationTest.kt | 4 +-- .../fi/espoo/evaka/shared/dev/DevApi.kt | 5 ++- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt index 0279ce9fb73..9817c834dd1 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt @@ -38,6 +38,7 @@ import fi.espoo.evaka.shared.dev.DevCareArea import fi.espoo.evaka.shared.dev.DevDaycare import fi.espoo.evaka.shared.dev.DevDaycareGroup import fi.espoo.evaka.shared.dev.DevEmployee +import fi.espoo.evaka.shared.dev.DevMobileDevice import fi.espoo.evaka.shared.dev.DevPerson import fi.espoo.evaka.shared.dev.DevPreschoolTerm import fi.espoo.evaka.shared.dev.insert @@ -756,3 +757,6 @@ fun DevPerson.toEvakaUser(type: EvakaUserType) = name = this.lastName + " " + this.firstName, type = type, ) + +fun DevMobileDevice.toEvakaUser() = + EvakaUser(id = EvakaUserId(this.id.raw), name = this.name, type = EvakaUserType.MOBILE_DEVICE) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt index 9d5996d8e50..17b62e77f1e 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt @@ -13,6 +13,7 @@ import fi.espoo.evaka.placement.PlacementType import fi.espoo.evaka.reservations.ReservationResponse import fi.espoo.evaka.shared.ChildId import fi.espoo.evaka.shared.DaycareId +import fi.espoo.evaka.shared.EmployeeId import fi.espoo.evaka.shared.GroupId import fi.espoo.evaka.shared.MobileDeviceId import fi.espoo.evaka.shared.PlacementId @@ -22,6 +23,7 @@ import fi.espoo.evaka.shared.dev.DevBackupCare import fi.espoo.evaka.shared.dev.DevDaycareGroup import fi.espoo.evaka.shared.dev.DevDaycareGroupPlacement import fi.espoo.evaka.shared.dev.DevEmployee +import fi.espoo.evaka.shared.dev.DevMobileDevice import fi.espoo.evaka.shared.dev.DevPersonType import fi.espoo.evaka.shared.dev.DevPlacement import fi.espoo.evaka.shared.dev.DevReservation @@ -55,10 +57,8 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr // TODO Re-write this a bit to use DevMobileDevice instead of what it actually does. - private val evakaUser2 = DevEmployee() private val mobileUser = AuthenticatedUser.MobileDevice(MobileDeviceId(UUID.randomUUID())) - private val mobileUser2 = - AuthenticatedUser.MobileDevice(MobileDeviceId(UUID.randomUUID()), evakaUser2.id) + private val mobileUser2 = DevMobileDevice(unitId = testDaycare2.id) private val groupId = GroupId(UUID.randomUUID()) private val groupId2 = GroupId(UUID.randomUUID()) private val groupName = "Testaajat" @@ -67,6 +67,10 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr private val placementStart = now.toLocalDate().minusDays(30) private val placementEnd = now.toLocalDate().plusDays(30) + private val user2 = mobileUser2.toEvakaUser() + private val employee2 = + DevEmployee(id = EmployeeId(user2.id.raw), lastName = user2.name, firstName = "") + @BeforeEach fun beforeEach() { db.transaction { tx -> @@ -95,7 +99,8 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr ) ) tx.createMobileDeviceToUnit(mobileUser.id, testDaycare.id) - tx.createMobileDeviceToUnit(mobileUser2.id, testDaycare2.id) + tx.insert(employee2) + tx.insert(mobileUser2) } } @@ -367,7 +372,7 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr departed = null, ) } - val childInBackup = expectOneChildAttendance(backupUnitId, mobileUser2) + val childInBackup = expectOneChildAttendance(backupUnitId, mobileUser2.user) assertEquals(AttendanceStatus.PRESENT, childInBackup.status) assertNotNull(childInBackup.attendances) assertNull(childInBackup.attendances[0].departed) @@ -399,7 +404,7 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr departed = null, ) } - val childInBackup = expectOneChildAttendance(backupUnitId, mobileUser2) + val childInBackup = expectOneChildAttendance(backupUnitId, mobileUser2.user) assertEquals(AttendanceStatus.PRESENT, childInBackup.status) assertNotNull(childInBackup.attendances) assertNull(childInBackup.attendances[0].departed) @@ -420,7 +425,7 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr startTime = reservationStart, endTime = reservationEnd, createdAt = now, - createdBy = mobileUser2.evakaUserId, + createdBy = mobileUser2.toEvakaUser().id, ) ) } @@ -431,7 +436,7 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr TimeRange(reservationStart, reservationEnd), true, now, - evakaUser2.toEvakaUser(), + mobileUser2.toEvakaUser(), ) ), child.reservations, @@ -447,13 +452,13 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr date = now.toLocalDate(), startTime = null, endTime = null, - createdBy = mobileUser2.evakaUserId, + createdBy = mobileUser2.toEvakaUser().id, ) ) } val child = expectOneChild() assertEquals( - listOf(ReservationResponse.NoTimes(true, now, evakaUser2.toEvakaUser())), + listOf(ReservationResponse.NoTimes(true, now, mobileUser2.toEvakaUser())), child.reservations, ) } @@ -478,18 +483,18 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr date = now.toLocalDate(), startTime = reservationStart, endTime = reservationEnd, - createdBy = mobileUser2.evakaUserId, + createdBy = mobileUser2.toEvakaUser().id, ) ) } - val childInBackup = expectOneChild(backupUnitId, mobileUser2) + val childInBackup = expectOneChild(backupUnitId, mobileUser2.user) assertEquals( listOf( ReservationResponse.Times( TimeRange(reservationStart, reservationEnd), true, now, - evakaUser2.toEvakaUser(), + mobileUser2.toEvakaUser(), ) ), childInBackup.reservations, diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/ReservationControllerCitizenIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/ReservationControllerCitizenIntegrationTest.kt index a38572d994f..51389ebb542 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/ReservationControllerCitizenIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/ReservationControllerCitizenIntegrationTest.kt @@ -1241,7 +1241,7 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB val area = DevCareArea() val daycare = DevDaycare(areaId = area.id, enabledPilotFeatures = setOf(PilotFeature.RESERVATIONS)) - // val employee = DevEmployee() + // val employee = DevEmployee() val adult = DevPerson() val child = DevPerson() @@ -1249,7 +1249,7 @@ class ReservationControllerCitizenIntegrationTest : FullApplicationTest(resetDbB db.transaction { tx -> tx.insert(area) tx.insert(daycare) - //tx.insert(employee) + // tx.insert(employee) tx.insert(adult, DevPersonType.ADULT) tx.insert(child, DevPersonType.CHILD) 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 1744e39db4c..9e48b318e69 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 @@ -2099,7 +2099,10 @@ data class DevMobileDevice( val name: String = "Laite", val longTermToken: UUID? = null, val pushNotificationCategories: Set = emptySet(), -) +) { + val user: AuthenticatedUser.MobileDevice + @JsonIgnore get() = AuthenticatedUser.MobileDevice(id) +} data class DevPersonalMobileDevice( val id: MobileDeviceId = MobileDeviceId(UUID.randomUUID()), From dc013d9f8f12ea0f5bef695aada18a4f2a0b3cbe Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Wed, 27 Nov 2024 15:08:48 +0200 Subject: [PATCH 10/14] Faux rebase --- ...ql => V475__attendance_reservation_modification_metadata.sql} | 0 service/src/main/resources/migrations.txt | 1 + 2 files changed, 1 insertion(+) rename service/src/main/resources/db/migration/{V463__attendance_reservation_modification_metadata.sql => V475__attendance_reservation_modification_metadata.sql} (100%) diff --git a/service/src/main/resources/db/migration/V463__attendance_reservation_modification_metadata.sql b/service/src/main/resources/db/migration/V475__attendance_reservation_modification_metadata.sql similarity index 100% rename from service/src/main/resources/db/migration/V463__attendance_reservation_modification_metadata.sql rename to service/src/main/resources/db/migration/V475__attendance_reservation_modification_metadata.sql diff --git a/service/src/main/resources/migrations.txt b/service/src/main/resources/migrations.txt index ba86afa7e1c..dc401bfdb20 100644 --- a/service/src/main/resources/migrations.txt +++ b/service/src/main/resources/migrations.txt @@ -470,3 +470,4 @@ V471__application_confidentiality.sql V472__employee_ssn.sql V473__person_municipality_of_residence.sql V474__holiday_questionnaire_open_ranges.sql +V475__attendance_reservation_modification_metadata.sql From c887bf80c7cb7e70368ae7f41a193c877bd972cc Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Thu, 28 Nov 2024 11:34:29 +0200 Subject: [PATCH 11/14] WIP --- .../src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt | 2 +- .../fi/espoo/evaka/attendance/ChildAttendanceController.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt index 9817c834dd1..2db90e99e34 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/TestFixtures.kt @@ -759,4 +759,4 @@ fun DevPerson.toEvakaUser(type: EvakaUserType) = ) fun DevMobileDevice.toEvakaUser() = - EvakaUser(id = EvakaUserId(this.id.raw), name = this.name, type = EvakaUserType.MOBILE_DEVICE) + EvakaUser(id = EvakaUserId(this.id.raw), name = this.name, type = EvakaUserType.EMPLOYEE) diff --git a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceController.kt b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceController.kt index dfd105e0f10..0928bea0095 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceController.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/attendance/ChildAttendanceController.kt @@ -410,6 +410,8 @@ class ChildAttendanceController( unitId, date, TimeInterval(startTime, endTime), + clock.now(), + user.evakaUserId, ) } } From 9e9b2e2eb2d68313a7fcea14e2a6245cc807e319 Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Fri, 29 Nov 2024 10:43:16 +0200 Subject: [PATCH 12/14] WIP --- .../espoo/evaka/attendance/GetAttendancesIntegrationTest.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt index 17b62e77f1e..afef25ba9fd 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/attendance/GetAttendancesIntegrationTest.kt @@ -58,7 +58,7 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr // TODO Re-write this a bit to use DevMobileDevice instead of what it actually does. private val mobileUser = AuthenticatedUser.MobileDevice(MobileDeviceId(UUID.randomUUID())) - private val mobileUser2 = DevMobileDevice(unitId = testDaycare2.id) + private val mobileUser2 = DevMobileDevice(unitId = testDaycare2.id, name = "Laite ") private val groupId = GroupId(UUID.randomUUID()) private val groupId2 = GroupId(UUID.randomUUID()) private val groupName = "Testaajat" @@ -69,7 +69,7 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr private val user2 = mobileUser2.toEvakaUser() private val employee2 = - DevEmployee(id = EmployeeId(user2.id.raw), lastName = user2.name, firstName = "") + DevEmployee(id = EmployeeId(user2.id.raw), lastName = "Laite", firstName = "") @BeforeEach fun beforeEach() { @@ -452,6 +452,7 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr date = now.toLocalDate(), startTime = null, endTime = null, + createdAt = now, createdBy = mobileUser2.toEvakaUser().id, ) ) @@ -483,6 +484,7 @@ class GetAttendancesIntegrationTest : FullApplicationTest(resetDbBeforeEach = tr date = now.toLocalDate(), startTime = reservationStart, endTime = reservationEnd, + createdAt = now, createdBy = mobileUser2.toEvakaUser().id, ) ) From 270225cac9669d2295a4407a970b8bf13c3a3006 Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Wed, 11 Dec 2024 13:53:51 +0200 Subject: [PATCH 13/14] WIP --- frontend/src/e2e-test/generated/api-types.ts | 8 ++---- .../generated/api-types/attendance.ts | 11 +++----- .../generated/api-types/reservations.ts | 26 +++++++++++-------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/frontend/src/e2e-test/generated/api-types.ts b/frontend/src/e2e-test/generated/api-types.ts index 3c607dac98d..25bb339137b 100644 --- a/frontend/src/e2e-test/generated/api-types.ts +++ b/frontend/src/e2e-test/generated/api-types.ts @@ -405,13 +405,9 @@ export interface DevChildAttendance { childId: PersonId date: LocalDate departed: LocalTime | null -<<<<<<< HEAD - unitId: DaycareId -======= modifiedAt: HelsinkiDateTime - modifiedBy: UUID - unitId: UUID ->>>>>>> 63aa5ee1c3 (WIP) + modifiedBy: EvakaUserId + unitId: DaycareId } /** diff --git a/frontend/src/lib-common/generated/api-types/attendance.ts b/frontend/src/lib-common/generated/api-types/attendance.ts index 7339389d889..35849b1a3c1 100644 --- a/frontend/src/lib-common/generated/api-types/attendance.ts +++ b/frontend/src/lib-common/generated/api-types/attendance.ts @@ -13,13 +13,10 @@ import { AbsenceType } from './absence' import { ChildDailyNote } from './note' import { ChildStickyNote } from './note' import { DailyServiceTimesValue } from './dailyservicetimes' -<<<<<<< HEAD import { DaycareId } from './shared' import { EmployeeId } from './shared' -import { GroupId } from './shared' -======= import { EvakaUser } from './user' ->>>>>>> 9571146eb1 (WIP) +import { GroupId } from './shared' import { HelsinkiDateTimeRange } from './shared' import { JsonOf } from '../../json' import { PersonId } from './shared' @@ -100,8 +97,8 @@ export type AttendanceStatus = export interface AttendanceTimes { arrived: HelsinkiDateTime departed: HelsinkiDateTime | null - modifiedAt: HelsinkiDateTime | null - modifiedBy: EvakaUser | null + modifiedAt: HelsinkiDateTime + modifiedBy: EvakaUser } /** @@ -492,7 +489,7 @@ export function deserializeJsonAttendanceTimes(json: JsonOf): A ...json, arrived: HelsinkiDateTime.parseIso(json.arrived), departed: (json.departed != null) ? HelsinkiDateTime.parseIso(json.departed) : null, - modifiedAt: (json.modifiedAt != null) ? HelsinkiDateTime.parseIso(json.modifiedAt) : null + modifiedAt: HelsinkiDateTime.parseIso(json.modifiedAt) } } diff --git a/frontend/src/lib-common/generated/api-types/reservations.ts b/frontend/src/lib-common/generated/api-types/reservations.ts index 5e77ed816b4..66006c481ce 100644 --- a/frontend/src/lib-common/generated/api-types/reservations.ts +++ b/frontend/src/lib-common/generated/api-types/reservations.ts @@ -14,12 +14,9 @@ import { AbsenceType } from './absence' import { ChildImageId } from './shared' import { ChildServiceNeedInfo } from './absence' import { DailyServiceTimesValue } from './dailyservicetimes' -<<<<<<< HEAD import { DaycareId } from './shared' -import { GroupId } from './shared' -======= import { EvakaUser } from './user' ->>>>>>> 9571146eb1 (WIP) +import { GroupId } from './shared' import { HolidayPeriodEffect } from './holidayperiod' import { JsonOf } from '../../json' import { PersonId } from './shared' @@ -103,15 +100,9 @@ export interface ChildDatePresence { export interface ChildRecordOfDay { absenceBillable: AbsenceTypeResponse | null absenceNonbillable: AbsenceTypeResponse | null -<<<<<<< HEAD - attendances: TimeInterval[] + attendances: AttendanceTimesForDate[] backupGroupId: GroupId | null childId: PersonId -======= - attendances: AttendanceTimesForDate[] - backupGroupId: UUID | null - childId: UUID ->>>>>>> 9571146eb1 (WIP) dailyServiceTimes: DailyServiceTimesValue | null groupId: GroupId | null inOtherUnit: boolean @@ -360,6 +351,8 @@ export namespace ReservationResponse { */ export interface NoTimes { type: 'NO_TIMES' + modifiedAt: HelsinkiDateTime + modifiedBy: EvakaUser staffCreated: boolean } @@ -368,6 +361,8 @@ export namespace ReservationResponse { */ export interface Times { type: 'TIMES' + modifiedAt: HelsinkiDateTime + modifiedBy: EvakaUser range: TimeRange staffCreated: boolean } @@ -652,14 +647,23 @@ export function deserializeJsonReservationChildInfo(json: JsonOf): ReservationResponse.NoTimes { + return { + ...json, + modifiedAt: HelsinkiDateTime.parseIso(json.modifiedAt) + } +} + export function deserializeJsonReservationResponseTimes(json: JsonOf): ReservationResponse.Times { return { ...json, + modifiedAt: HelsinkiDateTime.parseIso(json.modifiedAt), range: TimeRange.parseJson(json.range) } } export function deserializeJsonReservationResponse(json: JsonOf): ReservationResponse { switch (json.type) { + case 'NO_TIMES': return deserializeJsonReservationResponseNoTimes(json) case 'TIMES': return deserializeJsonReservationResponseTimes(json) default: return json } From c1c85025ba03d2906ac61e515adafb14998ae31d Mon Sep 17 00:00:00 2001 From: Kelvin Jackson Date: Fri, 13 Dec 2024 11:15:30 +0200 Subject: [PATCH 14/14] WIP --- .../AttendanceReservationsControllerIntegrationTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/AttendanceReservationsControllerIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/AttendanceReservationsControllerIntegrationTest.kt index 7d33ae1cc48..1797a897bd0 100644 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/AttendanceReservationsControllerIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/reservations/AttendanceReservationsControllerIntegrationTest.kt @@ -930,6 +930,7 @@ class AttendanceReservationsControllerIntegrationTest : startTime = LocalTime.of(9, 0), endTime = LocalTime.of(11, 0), createdBy = EvakaUserId(employeeId.raw), + createdAt = clock.now(), ) ) tx.insert( @@ -958,6 +959,8 @@ class AttendanceReservationsControllerIntegrationTest : ReservationResponse.Times( TimeRange(LocalTime.of(9, 0), LocalTime.of(11, 0)), true, + modifiedBy = employee.toEvakaUser(), + modifiedAt = clock.now(), ) ), absenceType = null,