Skip to content

Commit

Permalink
Merge pull request #5692 from espoon-voltti/open-attendance-warning-o…
Browse files Browse the repository at this point in the history
…n-mobile

Varoitetaan toisessa yksikössä olevista avoimista kirjauksista mobiilissa
  • Loading branch information
Wnt authored Sep 20, 2024
2 parents 6aa2bd4 + 49567e0 commit 1b75dd2
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 37 deletions.
6 changes: 5 additions & 1 deletion frontend/src/e2e-test/pages/mobile/staff-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export class StaffAttendancePage {
shiftTimeText: Element
attendanceTimeTexts: ElementCollection
attendanceTimes: ElementCollection
openAttendanceWarning: Element
}
externalMemberPage: {
arrivalTime: Element
Expand Down Expand Up @@ -238,7 +239,10 @@ export class StaffAttendancePage {
attendanceTimes: page.findAllByDataQa('attendance-time'),
markArrivedBtn: page.findByDataQa('mark-arrived-btn'),
shiftTimeText: page.findByDataQa('shift-time'),
attendanceTimeTexts: page.findAllByDataQa('attendance-time')
attendanceTimeTexts: page.findAllByDataQa('attendance-time'),
openAttendanceWarning: page.findByDataQa(
'open-attendance-in-another-unit-warning'
)
}
this.externalMemberPage = {
arrivalTime: page.findByDataQa('arrival-time'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@ import {
Fixture,
fullDayTimeRange,
uuidv4,
familyWithTwoGuardians
familyWithTwoGuardians,
testDaycare
} from '../../dev-api/fixtures'
import {
createDefaultServiceNeedOptions,
getStaffAttendances,
resetServiceState
} from '../../generated/api-clients'
import { DevDaycareGroup, DevEmployee } from '../../generated/api-types'
import {
DevCareArea,
DevDaycareGroup,
DevEmployee
} from '../../generated/api-types'
import MobileNav from '../../pages/mobile/mobile-nav'
import {
StaffAttendanceEditPage,
Expand All @@ -39,6 +44,8 @@ let employeeName: string

const pin = '4242'

let careArea: DevCareArea

const daycareGroup2Fixture: DevDaycareGroup = {
...testDaycareGroup,
id: uuidv4(),
Expand All @@ -50,10 +57,10 @@ beforeEach(async () => {
await Fixture.family(familyWithTwoGuardians).save()
await createDefaultServiceNeedOptions()

const area = await Fixture.careArea().save()
careArea = await Fixture.careArea().save()
await Fixture.daycare({
...testDaycare2,
areaId: area.id,
areaId: careArea.id,
enabledPilotFeatures: ['REALTIME_STAFF_ATTENDANCE'],
operationTimes: [
fullDayTimeRange,
Expand Down Expand Up @@ -296,6 +303,45 @@ describe('Realtime staff attendance page', () => {
)
})

test('Message is shown when open attendance exist in another unit', async () => {
const anotherDaycare = await Fixture.daycare({
...testDaycare,
areaId: careArea.id,
enabledPilotFeatures: ['REALTIME_STAFF_ATTENDANCE']
}).save()

const anotherGroupId = uuidv4()
await Fixture.daycareGroup({
id: anotherGroupId,
daycareId: anotherDaycare.id,
name: 'Toiset testailijat'
}).save()

const mockedDateTime = HelsinkiDateTime.of(2022, 5, 5, 12, 0)
const yesterday = mockedDateTime.subHours(24)

await Fixture.realtimeStaffAttendance({
employeeId: staffFixture.id,
groupId: anotherGroupId,
arrived: yesterday,
departed: null
}).save()

await initPages(mockedDateTime)

await staffAttendancePage.selectTab('absent')
await staffAttendancePage.openStaffPage(employeeName)

await staffAttendancePage.staffMemberPage.openAttendanceWarning.assertTextEquals(
'Avoin kirjaus ke 4.5.2022 - Alkuräjähdyksen päiväkoti. Kirjaus on päätettävä ennen uuden lisäystä.'
)

await staffAttendancePage.assertEmployeeStatus('Poissa')
await staffAttendancePage.staffMemberPage.markArrivedBtn.assertDisabled(
true
)
})

test('Staff arrival page behaves correctly with different time values when no plan exists', async () => {
await initPages(HelsinkiDateTime.of(2022, 5, 5, 12, 0))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ interface Props<
onSave: (body: T[]) => void
onSuccess: () => void
onClose: () => void
unitId?: string
}

export interface EditedAttendance {
Expand Down Expand Up @@ -120,8 +119,7 @@ function StaffAttendanceDetailsModal<
validate,
onSave,
onSuccess,
onClose,
unitId
onClose
}: Props<T>) {
const { i18n } = useTranslation()

Expand Down Expand Up @@ -351,8 +349,8 @@ function StaffAttendanceDetailsModal<
)

const openAttendanceResult = useQueryResult(
employeeId && unitId
? openAttendanceQuery({ userId: employeeId, unitId })
employeeId
? openAttendanceQuery({ userId: employeeId })
: constantQuery({ openGroupAttendance: null })
)
const openAttendance = openAttendanceResult.isSuccess
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,6 @@ const StaffAttendanceModal = React.memo(function StaffAttendanceModal({
defaultGroupId={defaultGroupId}
onClose={onClose}
onSuccess={onSuccess}
unitId={unitId}
/>
) : (
<StaffAttendanceDetailsModal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@ import { uri } from 'lib-common/uri'
*/
export async function getOpenGroupAttendance(
request: {
userId: UUID,
unitId: UUID
userId: UUID
}
): Promise<OpenGroupAttendanceResponse> {
const params = createUrlSearchParams(
['userId', request.userId],
['unitId', request.unitId]
['userId', request.userId]
)
const { data: json } = await client.request<JsonOf<OpenGroupAttendanceResponse>>({
url: uri`/employee/staff-attendances/realtime/open-attendence`.toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ExternalStaffDepartureRequest } from 'lib-common/generated/api-types/at
import { FullDayAbsenceRequest } from 'lib-common/generated/api-types/attendance'
import { JsonCompatible } from 'lib-common/json'
import { JsonOf } from 'lib-common/json'
import { OpenGroupAttendanceResponse } from 'lib-common/generated/api-types/attendance'
import { StaffArrivalRequest } from 'lib-common/generated/api-types/attendance'
import { StaffAttendanceUpdateRequest } from 'lib-common/generated/api-types/attendance'
import { StaffAttendanceUpdateResponse } from 'lib-common/generated/api-types/attendance'
Expand All @@ -31,6 +32,7 @@ import { createUrlSearchParams } from 'lib-common/api'
import { deserializeJsonAttendanceChild } from 'lib-common/generated/api-types/attendance'
import { deserializeJsonChildAttendanceStatusResponse } from 'lib-common/generated/api-types/attendance'
import { deserializeJsonCurrentDayStaffAttendanceResponse } from 'lib-common/generated/api-types/attendance'
import { deserializeJsonOpenGroupAttendanceResponse } from 'lib-common/generated/api-types/attendance'
import { deserializeJsonStaffMember } from 'lib-common/generated/api-types/attendance'
import { uri } from 'lib-common/uri'

Expand Down Expand Up @@ -287,6 +289,26 @@ export async function getEmployeeAttendances(
}


/**
* Generated from fi.espoo.evaka.attendance.MobileRealtimeStaffAttendanceController.getOpenGroupAttendance
*/
export async function getOpenGroupAttendance(
request: {
userId: UUID
}
): Promise<OpenGroupAttendanceResponse> {
const params = createUrlSearchParams(
['userId', request.userId]
)
const { data: json } = await client.request<JsonOf<OpenGroupAttendanceResponse>>({
url: uri`/employee-mobile/realtime-staff-attendances/open-attendance`.toString(),
method: 'GET',
params
})
return deserializeJsonOpenGroupAttendanceResponse(json)
}


/**
* Generated from fi.espoo.evaka.attendance.MobileRealtimeStaffAttendanceController.markArrival
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import { useNavigate } from 'react-router-dom'
import styled from 'styled-components'

import { combine } from 'lib-common/api'
import { useQueryResult } from 'lib-common/query'
import { constantQuery, useQueryResult } from 'lib-common/query'
import useRouteParams from 'lib-common/useRouteParams'
import { Button } from 'lib-components/atoms/buttons/Button'
import { IconOnlyButton } from 'lib-components/atoms/buttons/IconOnlyButton'
import { LegacyButton } from 'lib-components/atoms/buttons/LegacyButton'
import ErrorSegment from 'lib-components/atoms/state/ErrorSegment'
import { ContentArea } from 'lib-components/layout/Container'
import { FixedSpaceColumn } from 'lib-components/layout/flex-helpers'
import { AlertBox } from 'lib-components/molecules/MessageBoxes'
import { H4, Label } from 'lib-components/typography'
import { defaultMargins } from 'lib-components/white-space'
import { featureFlags } from 'lib-customizations/employeeMobile'
Expand All @@ -30,7 +31,7 @@ import { unitInfoQuery } from '../units/queries'
import { EmployeeCardBackground } from './components/EmployeeCardBackground'
import { StaffMemberPageContainer } from './components/StaffMemberPageContainer'
import { TimeInfo } from './components/staff-components'
import { staffAttendanceQuery } from './queries'
import { openAttendanceQuery, staffAttendanceQuery } from './queries'
import { toStaff } from './utils'

export default React.memo(function StaffMemberPage({
Expand Down Expand Up @@ -60,6 +61,18 @@ export default React.memo(function StaffMemberPage({
[employeeId, unitInfoResponse, staffAttendanceResponse]
)

const openAttendanceResult = useQueryResult(
employeeId
? openAttendanceQuery({ userId: employeeId })
: constantQuery({ openGroupAttendance: null })
)
const openAttendance = openAttendanceResult.isSuccess
? openAttendanceResult.value.openGroupAttendance
: null

const openAttendanceInAnotherUnit =
!!openAttendance && openAttendance.unitId !== unitId

return renderResult(
employeeResponse,
({ isOperationalDate, staffMember }) => (
Expand Down Expand Up @@ -156,6 +169,16 @@ export default React.memo(function StaffMemberPage({
)
)}
<ContentArea opaque paddingHorizontal="s">
{openAttendanceInAnotherUnit && (
<AlertBox
data-qa="open-attendance-in-another-unit-warning"
message={`${i18n.attendances.staff.openAttendanceInAnotherUnitWarning} ${
openAttendance.date.formatExotic('EEEEEE d.M.yyyy') +
' - ' +
openAttendance.unitName
}${i18n.attendances.staff.openAttendanceInAnotherUnitWarningCont}`}
/>
)}
<FixedSpaceColumn alignItems="center">
{staffMember.present ? (
<LegacyButton
Expand All @@ -182,7 +205,9 @@ export default React.memo(function StaffMemberPage({
<LegacyButton
primary
data-qa="mark-arrived-btn"
disabled={!isOperationalDate}
disabled={
!isOperationalDate || openAttendanceInAnotherUnit
}
onClick={() =>
navigate(
routes.staffMarkArrived(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Arg0, UUID } from 'lib-common/types'
import {
getAttendancesByUnit,
getEmployeeAttendances,
getOpenGroupAttendance,
markArrival,
markDeparture,
markExternalArrival,
Expand Down Expand Up @@ -36,7 +37,12 @@ const queryKeys = createQueryKeys('staffAttendance', {
employeeId: UUID,
from: LocalDate,
to: LocalDate
) => ['unit', unitId, 'employeeId', employeeId, 'range', from, to]
) => ['unit', unitId, 'employeeId', employeeId, 'range', from, to],

openGroupAttendance: (arg: Arg0<typeof getOpenGroupAttendance>) => [
'openGroupAttendance',
arg
]
})

export const staffAttendanceQuery = query({
Expand Down Expand Up @@ -77,3 +83,8 @@ export const staffAttendanceMutation = mutation({
api: setAttendances,
invalidateQueryKeys: ({ unitId }) => [queryKeys.unit(unitId)]
})

export const openAttendanceQuery = query({
api: getOpenGroupAttendance,
queryKey: queryKeys.openGroupAttendance
})
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,10 @@ export const fi = {
dateTooEarly: 'Tarkista',
dateTooLate: 'Tarkista'
},
add: '+ Lisää uusi kirjaus'
add: '+ Lisää uusi kirjaus',
openAttendanceInAnotherUnitWarning: 'Avoin kirjaus ',
openAttendanceInAnotherUnitWarningCont:
'. Kirjaus on päätettävä ennen uuden lisäystä.'
},
timeDiffTooBigNotification:
'Voit tehdä sisäänkirjauksen +/- 30 min päähän nykyhetkestä. Kirjauksia voi tarvittaessa muokata työpöytäselaimen kautta.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package fi.espoo.evaka.attendance
import fi.espoo.evaka.Audit
import fi.espoo.evaka.AuditId
import fi.espoo.evaka.absence.getDaycareIdByGroup
import fi.espoo.evaka.attendance.RealtimeStaffAttendanceController.OpenGroupAttendanceResponse
import fi.espoo.evaka.shared.DaycareId
import fi.espoo.evaka.shared.EmployeeId
import fi.espoo.evaka.shared.GroupId
Expand Down Expand Up @@ -614,4 +615,28 @@ class MobileRealtimeStaffAttendanceController(private val ac: AccessControl) {

return listOf(ongoingAttendance.copy(departed = departureTime))
}

@GetMapping("open-attendance")
fun getOpenGroupAttendance(
db: Database,
user: AuthenticatedUser.MobileDevice,
clock: EvakaClock,
@RequestParam userId: EmployeeId,
): OpenGroupAttendanceResponse {
val openAttendance =
db.connect { dbc ->
dbc.read { tx ->
ac.requirePermissionFor(
tx,
user,
clock,
Action.Employee.READ_OPEN_GROUP_ATTENDANCE,
userId,
)
tx.getOpenGroupAttendancesForEmployee(userId)
}
}
.also { Audit.StaffOpenAttendanceRead.log(targetId = AuditId(userId)) }
return OpenGroupAttendanceResponse(openAttendance)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import fi.espoo.evaka.shared.GroupId
import fi.espoo.evaka.shared.StaffAttendanceExternalId
import fi.espoo.evaka.shared.StaffAttendanceRealtimeId
import fi.espoo.evaka.shared.auth.AuthenticatedUser
import fi.espoo.evaka.shared.auth.getDaycareAclRows
import fi.espoo.evaka.shared.db.Database
import fi.espoo.evaka.shared.domain.BadRequest
import fi.espoo.evaka.shared.domain.EvakaClock
Expand Down Expand Up @@ -324,29 +323,17 @@ class RealtimeStaffAttendanceController(private val accessControl: AccessControl
user: AuthenticatedUser.Employee,
clock: EvakaClock,
@RequestParam userId: EmployeeId,
@RequestParam unitId: DaycareId,
): OpenGroupAttendanceResponse {
val openAttendance =
db.connect { dbc ->
dbc.transaction { tx ->
// Check if the authenticated user has permission to read staff attendances
// for this unit
dbc.read { tx ->
accessControl.requirePermissionFor(
tx,
user,
clock,
Action.Unit.READ_STAFF_ATTENDANCES,
unitId,
Action.Employee.READ_OPEN_GROUP_ATTENDANCE,
userId,
)
// Also check that the given user belongs to this unit
val targetUserPartOfUnit =
tx.getDaycareAclRows(unitId, false).any { it.employee.id == userId }
if (!targetUserPartOfUnit) {
throw BadRequest("User doesn't belong to the unit")
}
// If the user has the permission to read staff attendances from this unit,
// then they are also permitted to check for open attendances of a colleague
// from all units
tx.getOpenGroupAttendancesForEmployee(userId)
}
}
Expand Down
Loading

0 comments on commit 1b75dd2

Please sign in to comment.