Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Titania raportin jatkokehitys #6140

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 43 additions & 30 deletions frontend/src/employee-frontend/components/reports/TitaniaErrors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from 'lib-common/generated/api-types/reports'
import { useQueryResult } from 'lib-common/query'
import Title from 'lib-components/atoms/Title'
import { MutateButton } from 'lib-components/atoms/buttons/MutateButton'
import ReturnButton from 'lib-components/atoms/buttons/ReturnButton'
import { Container, ContentArea } from 'lib-components/layout/Container'
import { Table, Thead, Th, Tbody, Td, Tr } from 'lib-components/layout/Table'
Expand All @@ -20,7 +21,7 @@ import { Gap } from 'lib-components/white-space'
import { useTranslation } from '../../state/i18n'
import { renderResult } from '../async-rendering'

import { titaniaErrorsReportQuery } from './queries'
import { clearTitaniaErrorMutation, titaniaErrorsReportQuery } from './queries'

export default React.memo(function TitaniaErrors() {
const { i18n } = useTranslation()
Expand All @@ -36,54 +37,66 @@ export default React.memo(function TitaniaErrors() {
{renderResult(titaniaErrorsResult, (rows) => (
<>
{rows.map((row: TitaniaErrorReportRow) => (
<>
<H1 key={row.requestTime.format()}>
<div key={row.requestTime.format()}>
<H1>
{i18n.reports.titaniaErrors.header +
' ' +
row.requestTime.format()}
</H1>
{row.units.map((unit: TitaniaErrorUnit) => (
<>
<H2 key={unit.unitName}>{unit.unitName}</H2>
<div key={unit.unitName}>
<H2>{unit.unitName}</H2>
{unit.employees.map((employee: TitaniaErrorEmployee) => (
<>
<H3 key={employee.employeeName}>
<div key={employee.employeeName}>
<H3>
{employee.employeeName +
(employee.employeeNumber == ''
? ''
: ' (' + employee.employeeNumber + ')')}
</H3>
<Table>
<Thead>
<Th>{i18n.reports.titaniaErrors.date}</Th>
<Th>{i18n.reports.titaniaErrors.shift1}</Th>
<Th>{i18n.reports.titaniaErrors.shift2}</Th>
<Tr>
<Th>{i18n.reports.titaniaErrors.date}</Th>
<Th>{i18n.reports.titaniaErrors.shift1}</Th>
<Th>{i18n.reports.titaniaErrors.shift2}</Th>
<Th />
</Tr>
</Thead>
<Tbody>
{employee.conflictingShifts.map(
(conflict, index) => (
<Tr key={index}>
<Td>{conflict.shiftDate.format()}</Td>
<Td>
{conflict.shiftBegins.format() +
' - ' +
conflict.shiftEnds.format()}
</Td>
<Td>
{conflict.overlappingShiftBegins.format() +
' - ' +
conflict.overlappingShiftEnds.format()}
</Td>
</Tr>
)
)}
{employee.conflictingShifts.map((conflict) => (
<Tr key={conflict.id}>
<Td>{conflict.shiftDate.format()}</Td>
<Td>
{conflict.shiftBegins.format() +
' - ' +
conflict.shiftEnds.format()}
</Td>
<Td>
{conflict.overlappingShiftBegins.format() +
' - ' +
conflict.overlappingShiftEnds.format()}
</Td>
<Td>
<MutateButton
primary
text={i18n.common.remove}
mutation={clearTitaniaErrorMutation}
onClick={() => ({
conflictId: conflict.id
})}
data-qa={`delete-button-${conflict.id}`}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</>
</div>
))}
</>
</div>
))}
</>
</div>
))}
</>
))}
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/employee-frontend/components/reports/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Arg0, UUID } from 'lib-common/types'

import { sendJamixOrders } from '../../generated/api-clients/jamix'
import {
clearTitaniaErrors,
getAssistanceNeedsAndActionsReport,
getAssistanceNeedsAndActionsReportByChild,
getAttendanceReservationReportByChild,
Expand Down Expand Up @@ -253,6 +254,11 @@ export const titaniaErrorsReportQuery = query({
queryKey: queryKeys.titaniaErrorsReport
})

export const clearTitaniaErrorMutation = mutation({
api: clearTitaniaErrors,
invalidateQueryKeys: () => [queryKeys.titaniaErrorsReport()]
})

export const incompleteIncomeReportQuery = query({
api: getIncompleteIncomeReport,
queryKey: queryKeys.incompleteIncomeReport
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/employee-frontend/generated/api-clients/reports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import { SextetReportRow } from 'lib-common/generated/api-types/reports'
import { SourceUnitsReportRow } from 'lib-common/generated/api-types/reports'
import { StartingPlacementsRow } from 'lib-common/generated/api-types/reports'
import { TitaniaErrorReportRow } from 'lib-common/generated/api-types/reports'
import { TitaniaErrorsId } from 'lib-common/generated/api-types/shared'
import { UnitsReportRow } from 'lib-common/generated/api-types/reports'
import { VardaChildErrorReportRow } from 'lib-common/generated/api-types/reports'
import { VardaUnitErrorReportRow } from 'lib-common/generated/api-types/reports'
Expand Down Expand Up @@ -1021,6 +1022,22 @@ export async function getStartingPlacementsReport(
}


/**
* Generated from fi.espoo.evaka.reports.TitaniaErrorReport.clearTitaniaErrors
*/
export async function clearTitaniaErrors(
request: {
conflictId: TitaniaErrorsId
}
): Promise<void> {
const { data: json } = await client.request<JsonOf<void>>({
url: uri`/employee/reports/titania-errors/${request.conflictId}`.toString(),
method: 'DELETE'
})
return json
}


/**
* Generated from fi.espoo.evaka.reports.TitaniaErrorReport.getTitaniaErrorsReport
*/
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib-common/generated/api-types/reports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { PreschoolAssistanceLevel } from './assistance'
import { ProviderType } from './daycare'
import { ServiceNeedId } from './shared'
import { ServiceNeedOption } from './application'
import { TitaniaErrorsId } from './shared'
import { UUID } from '../../types'
import { VoucherValueDecisionId } from './shared'

Expand Down Expand Up @@ -954,6 +955,7 @@ export interface StartingPlacementsRow {
* Generated from fi.espoo.evaka.reports.TitaniaErrorConflict
*/
export interface TitaniaErrorConflict {
id: TitaniaErrorsId
overlappingShiftBegins: LocalTime
overlappingShiftEnds: LocalTime
shiftBegins: LocalTime
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib-common/generated/api-types/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ export type StaffAttendanceExternalId = string

export type StaffAttendanceRealtimeId = string

export type TitaniaErrorsId = Id<'TitaniaErrors'>

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

import fi.espoo.evaka.Audit
import fi.espoo.evaka.shared.TitaniaConflictId
import fi.espoo.evaka.shared.auth.AuthenticatedUser
import fi.espoo.evaka.shared.db.Database
import fi.espoo.evaka.shared.domain.EvakaClock
Expand All @@ -13,7 +14,9 @@ import fi.espoo.evaka.shared.security.AccessControl
import fi.espoo.evaka.shared.security.Action
import java.time.LocalDate
import java.time.LocalTime
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController

@RestController
Expand All @@ -38,6 +41,27 @@ class TitaniaErrorReport(private val accessControl: AccessControl) {
}
.also { Audit.TitaniaReportRead.log() }
}

@DeleteMapping("/employee/reports/titania-errors/{conflictId}")
fun clearTitaniaErrors(
db: Database,
user: AuthenticatedUser.Employee,
clock: EvakaClock,
@PathVariable conflictId: TitaniaConflictId,
) {
return db.connect { dbc ->
dbc.transaction { tx ->
accessControl.requirePermissionFor(
tx,
user,
clock,
Action.Global.READ_TITANIA_ERRORS,
)
tx.deleteTitaniaError(conflictId)
}
}
.also { Audit.TitaniaReportRead.log() }
}
}

fun Database.Read.getTitaniaErrors(): List<TitaniaErrorReportRow> {
Expand All @@ -49,7 +73,8 @@ fun Database.Read.getTitaniaErrors(): List<TitaniaErrorReportRow> {
te.request_time,
emp.first_name,
emp.last_name,
emp.employee_number,
emp.employee_number,
te.id,
te.shift_date,
te.shift_begins,
te.shift_ends,
Expand Down Expand Up @@ -100,6 +125,7 @@ fun Database.Read.getTitaniaErrors(): List<TitaniaErrorReportRow> {
employeeEntry.value[0].employeeNumber ?: "",
employeeEntry.value.map { shiftEntry ->
TitaniaErrorConflict(
shiftEntry.id,
shiftEntry.shiftDate,
shiftEntry.shiftBegins,
shiftEntry.shiftEnds,
Expand All @@ -115,11 +141,29 @@ fun Database.Read.getTitaniaErrors(): List<TitaniaErrorReportRow> {
}
}

fun Database.Transaction.deleteTitaniaErrors() {
createUpdate { sql("DELETE FROM titania_errors") }.execute()
}

fun Database.Transaction.deleteTitaniaError(id: TitaniaConflictId) {
createUpdate {
sql(
"""
DELETE FROM titania_errors
WHERE id = ${bind(id)}
"""
.trimIndent()
)
}
.execute()
}

data class TitaniaDbRow(
val requestTime: HelsinkiDateTime,
val firstName: String,
val lastName: String,
val employeeNumber: String?,
val id: TitaniaConflictId,
val shiftDate: LocalDate,
val shiftBegins: LocalTime,
val shiftEnds: LocalTime,
Expand All @@ -129,6 +173,7 @@ data class TitaniaDbRow(
)

data class TitaniaErrorConflict(
val id: TitaniaConflictId,
val shiftDate: LocalDate,
val shiftBegins: LocalTime,
val shiftEnds: LocalTime,
Expand Down
4 changes: 4 additions & 0 deletions service/src/main/kotlin/fi/espoo/evaka/shared/Id.kt
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ sealed interface DatabaseTable {

sealed class StaffOccupancyCoefficient : DatabaseTable

sealed class TitaniaErrors : DatabaseTable

sealed class VoucherValueDecision : DatabaseTable
}

Expand Down Expand Up @@ -344,6 +346,8 @@ typealias StaffAttendanceRealtimeId = Id<DatabaseTable.StaffAttendanceRealtime>

typealias StaffOccupancyCoefficientId = Id<DatabaseTable.StaffOccupancyCoefficient>

typealias TitaniaConflictId = Id<DatabaseTable.TitaniaErrors>

typealias VoucherValueDecisionId = Id<DatabaseTable.VoucherValueDecision>

@JsonSerialize(converter = Id.ToJson::class)
Expand Down
26 changes: 8 additions & 18 deletions service/src/main/kotlin/fi/espoo/evaka/titania/TitaniaService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import fi.espoo.evaka.shared.EmployeeId
import fi.espoo.evaka.shared.db.Database
import fi.espoo.evaka.shared.domain.HelsinkiDateTime
import fi.espoo.evaka.shared.domain.HelsinkiDateTimeRange
import fi.espoo.evaka.shared.domain.TimeRange
import java.time.Duration
import java.time.LocalTime
import java.time.format.DateTimeFormatter
Expand Down Expand Up @@ -62,7 +61,7 @@ class TitaniaService(private val idConverter: TitaniaEmployeeIdConverter) {
val employeeNumbers = persons.map { (employeeNumber, _) -> employeeNumber }.distinct()
val employeeNumberToId = tx.getEmployeeIdsByNumbers(employeeNumbers)

var unmergedSameDayPlans = mutableListOf<StaffAttendancePlan>()
var unmergedPlans = mutableListOf<StaffAttendancePlan>()
val overlappingShifts = mutableListOf<TitaniaOverLappingShifts>()

val newPlans =
Expand Down Expand Up @@ -120,26 +119,17 @@ class TitaniaService(private val idConverter: TitaniaEmployeeIdConverter) {
plans.add(next)
}

if (
unmergedSameDayPlans.lastOrNull()?.startTime?.toLocalDate() !=
next.startTime.toLocalDate()
) {
unmergedSameDayPlans = mutableListOf(next)
if (unmergedPlans.isEmpty()) {
unmergedPlans = mutableListOf(next)
} else {
// identical shifts are deduplicated later, ignore them here
if (next !in unmergedSameDayPlans) {
unmergedSameDayPlans
if (next !in unmergedPlans) {
unmergedPlans
.filter { it.employeeId == next.employeeId }
.filter {
TimeRange(
next.startTime.toLocalTime(),
next.endTime.toLocalTime(),
)
HelsinkiDateTimeRange(next.startTime, next.endTime)
.overlaps(
TimeRange(
it.startTime.toLocalTime(),
it.endTime.toLocalTime(),
)
HelsinkiDateTimeRange(it.startTime, it.endTime)
)
}
.forEach {
Expand All @@ -155,7 +145,7 @@ class TitaniaService(private val idConverter: TitaniaEmployeeIdConverter) {
)
}

unmergedSameDayPlans.add(next)
unmergedPlans.add(next)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALTER TABLE titania_errors
ADD COLUMN id uuid DEFAULT ext.uuid_generate_v1mc();

ALTER TABLE titania_errors
ADD CONSTRAINT titania_errors_pkey PRIMARY KEY (id);
1 change: 1 addition & 0 deletions service/src/main/resources/migrations.txt
Original file line number Diff line number Diff line change
Expand Up @@ -472,3 +472,4 @@ V473__person_municipality_of_residence.sql
V474__holiday_questionnaire_open_ranges.sql
V475__application_modified_metadata.sql
V476__finance_metadata_process.sql
V477__titania_errors_id.sql
Loading