From f48a41f0828132ba08f162d5f7e20e350c2e32a2 Mon Sep 17 00:00:00 2001 From: Tero Laakso Date: Wed, 27 Nov 2024 14:44:58 +0200 Subject: [PATCH 1/4] Move PRESCHOOL_CLUB test to where other distinctions param tests are --- .../invoicing/FeeDecisionIntegrationTest.kt | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt index 31fcc491500..6b82938ce4c 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt @@ -524,6 +524,21 @@ class FeeDecisionIntegrationTest : FullApplicationTest(resetDbBeforeEach = true) assertEqualEnough(listOf(toSummary(testDecisionWithNoStartingChild)), result.data) } + @Test + fun `search works with distinctions param PRESCHOOL_CLUB`() { + db.transaction { tx -> tx.upsertFeeDecisions(testDecisions + preschoolClubDecisions) } + val result = + searchDecisions( + SearchFeeDecisionRequest( + page = 0, + distinctions = listOf(DistinctiveParams.PRESCHOOL_CLUB), + ) + ) + + assertEquals(2, result.data.size) + assertEqualEnough(preschoolClubDecisions.map { toSummary(it) }, result.data) + } + @Test fun `search works as expected with existing area param`() { db.transaction { tx -> tx.upsertFeeDecisions(testDecisions) } @@ -2025,21 +2040,6 @@ class FeeDecisionIntegrationTest : FullApplicationTest(resetDbBeforeEach = true) getPdf(decision.id, adminUser) } - @Test - fun `search works with distinctions param PRESCHOOL_CLUB`() { - db.transaction { tx -> tx.upsertFeeDecisions(testDecisions + preschoolClubDecisions) } - val result = - searchDecisions( - SearchFeeDecisionRequest( - page = 0, - distinctions = listOf(DistinctiveParams.PRESCHOOL_CLUB), - ) - ) - - assertEquals(2, result.data.size) - assertEqualEnough(preschoolClubDecisions.map { toSummary(it) }, result.data) - } - @Test fun `Email notification is sent to hof when decision in WAITING_FOR_SENDING is set to SENT`() { // optInAdult has an email address, and does not require manual sending of PDF decision From b5dfa00c84f2f4dd73e6fcef3b45ceb6b07c844a Mon Sep 17 00:00:00 2001 From: Tero Laakso Date: Wed, 27 Nov 2024 16:17:53 +0200 Subject: [PATCH 2/4] Add filter for fee decisions without open income statements --- .../generated/api-types/invoicing.ts | 3 +- .../defaults/employee/i18n/fi.tsx | 3 +- .../invoicing/FeeDecisionIntegrationTest.kt | 88 +++++++++++++++++++ .../controller/FeeDecisionController.kt | 1 + .../invoicing/data/FeeDecisionQueries.kt | 17 ++-- 5 files changed, 105 insertions(+), 7 deletions(-) diff --git a/frontend/src/lib-common/generated/api-types/invoicing.ts b/frontend/src/lib-common/generated/api-types/invoicing.ts index 16217a02621..a68f6e1368e 100644 --- a/frontend/src/lib-common/generated/api-types/invoicing.ts +++ b/frontend/src/lib-common/generated/api-types/invoicing.ts @@ -54,7 +54,8 @@ export const feeDecisionDistinctiveParams = [ 'RETROACTIVE', 'NO_STARTING_PLACEMENTS', 'MAX_FEE_ACCEPTED', - 'PRESCHOOL_CLUB' + 'PRESCHOOL_CLUB', + 'NO_OPEN_INCOME_STATEMENTS' ] as const export type DistinctiveParams = typeof feeDecisionDistinctiveParams[number] diff --git a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx index bf06c44ace4..f58822c25a4 100755 --- a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx +++ b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx @@ -3076,7 +3076,8 @@ export const fi = { RETROACTIVE: 'Takautuva päätös', NO_STARTING_PLACEMENTS: 'Piilota uudet aloittavat lapset', MAX_FEE_ACCEPTED: 'Suostumus korkeimpaan maksuun', - PRESCHOOL_CLUB: 'Vain esiopetuksen kerho' + PRESCHOOL_CLUB: 'Vain esiopetuksen kerho', + NO_OPEN_INCOME_STATEMENTS: 'Piilota avonaiset tuloselvitykset' }, status: { DRAFT: 'Luonnos', diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt index 6b82938ce4c..62a35c489a7 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt @@ -9,6 +9,9 @@ import fi.espoo.evaka.FullApplicationTest import fi.espoo.evaka.emailclient.Email import fi.espoo.evaka.emailclient.IEmailMessageProvider import fi.espoo.evaka.emailclient.MockEmailClient +import fi.espoo.evaka.incomestatement.IncomeStatementBody +import fi.espoo.evaka.incomestatement.createIncomeStatement +import fi.espoo.evaka.incomestatement.updateIncomeStatementHandled import fi.espoo.evaka.invoicing.controller.DistinctiveParams import fi.espoo.evaka.invoicing.controller.FeeDecisionController import fi.espoo.evaka.invoicing.controller.FeeDecisionSortParam @@ -539,6 +542,91 @@ class FeeDecisionIntegrationTest : FullApplicationTest(resetDbBeforeEach = true) assertEqualEnough(preschoolClubDecisions.map { toSummary(it) }, result.data) } + @Test + fun `search works with distinctions param NO_OPEN_INCOME_STATEMENTS`() { + val clock = RealEvakaClock() + val decisionWithHandledStatement = + createFeeDecisionsForFamily(testAdult_1, testAdult_2, listOf(testChild_1)) + val decisionWithOpenStatement = + createFeeDecisionsForFamily(testAdult_3, testAdult_4, listOf(testChild_2)) + val decisionWithFarAwayAndFutureOpenStatements = + createFeeDecisionsForFamily(testAdult_5, testAdult_6, listOf(testChild_3)) + + db.transaction { tx -> + tx.upsertFeeDecisions( + listOf( + decisionWithHandledStatement, + decisionWithOpenStatement, + decisionWithFarAwayAndFutureOpenStatements, + ) + ) + val adult1StatementId = + tx.createIncomeStatement( + body = + IncomeStatementBody.HighestFee( + clock.today().minusMonths(2), + clock.today().minusMonths(1), + ), + personId = testAdult_1.id, + ) + // testAdult_2 statement not submitted + tx.updateIncomeStatementHandled(adult1StatementId, "handled", testDecisionMaker_1.id) + val adult3StatementId = + tx.createIncomeStatement( + body = + IncomeStatementBody.HighestFee( + clock.today().minusMonths(2), + clock.today().minusMonths(1), + ), + personId = testAdult_3.id, + ) + tx.updateIncomeStatementHandled(adult3StatementId, "handled", testDecisionMaker_1.id) + tx.createIncomeStatement( + body = + IncomeStatementBody.HighestFee( + clock.today().minusMonths(2), + clock.today().minusMonths(1), + ), + personId = testAdult_4.id, + ) + // testAdult_4 statement not handled + tx.createIncomeStatement( + body = + IncomeStatementBody.HighestFee( + clock.today().minusMonths(20), + clock.today().minusMonths(14).minusDays(1), + ), + personId = testAdult_5.id, + ) + // testAdult_5 statement not handled + tx.createIncomeStatement( + body = + IncomeStatementBody.HighestFee( + clock.today().plusDays(1), + clock.today().plusMonths(12), + ), + personId = testAdult_6.id, + ) + // testAdult_6 statement not handled + } + + val result = + searchDecisions( + SearchFeeDecisionRequest( + page = 0, + distinctions = listOf(DistinctiveParams.NO_OPEN_INCOME_STATEMENTS), + ) + ) + + assertEqualEnough( + listOf( + toSummary(decisionWithHandledStatement), + toSummary(decisionWithFarAwayAndFutureOpenStatements), + ), + result.data, + ) + } + @Test fun `search works as expected with existing area param`() { db.transaction { tx -> tx.upsertFeeDecisions(testDecisions) } diff --git a/service/src/main/kotlin/fi/espoo/evaka/invoicing/controller/FeeDecisionController.kt b/service/src/main/kotlin/fi/espoo/evaka/invoicing/controller/FeeDecisionController.kt index f77986142cd..e29b5986ceb 100755 --- a/service/src/main/kotlin/fi/espoo/evaka/invoicing/controller/FeeDecisionController.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/invoicing/controller/FeeDecisionController.kt @@ -68,6 +68,7 @@ enum class DistinctiveParams { NO_STARTING_PLACEMENTS, MAX_FEE_ACCEPTED, PRESCHOOL_CLUB, + NO_OPEN_INCOME_STATEMENTS, } @RestController diff --git a/service/src/main/kotlin/fi/espoo/evaka/invoicing/data/FeeDecisionQueries.kt b/service/src/main/kotlin/fi/espoo/evaka/invoicing/data/FeeDecisionQueries.kt index 0e2aff0acc4..59b9e621a6b 100644 --- a/service/src/main/kotlin/fi/espoo/evaka/invoicing/data/FeeDecisionQueries.kt +++ b/service/src/main/kotlin/fi/espoo/evaka/invoicing/data/FeeDecisionQueries.kt @@ -420,16 +420,13 @@ fun Database.Read.searchFeeDecisions( freeTextSearchQuery(listOf("head", "partner", "child"), searchTextWithoutNumbers) val withNullHours = distinctiveParams.contains(DistinctiveParams.UNCONFIRMED_HOURS) - val havingExternalChildren = distinctiveParams.contains(DistinctiveParams.EXTERNAL_CHILD) - val retroactiveOnly = distinctiveParams.contains(DistinctiveParams.RETROACTIVE) - val noStartingPlacements = distinctiveParams.contains(DistinctiveParams.NO_STARTING_PLACEMENTS) - val maxFeeAccepted = distinctiveParams.contains(DistinctiveParams.MAX_FEE_ACCEPTED) - val preschoolClub = distinctiveParams.contains(DistinctiveParams.PRESCHOOL_CLUB) + val noOpenIncomeStatements = + distinctiveParams.contains(DistinctiveParams.NO_OPEN_INCOME_STATEMENTS) val (numberQuery, numberParams) = disjointNumberQuery("decision", "decision_number", numberParamsRaw) @@ -469,6 +466,16 @@ fun Database.Read.searchFeeDecisions( "(decision.head_of_family_income->>'effect' = 'MAX_FEE_ACCEPTED' OR decision.partner_income->>'effect' = 'MAX_FEE_ACCEPTED')" else null, if (preschoolClub) "decisions_with_preschool_club_placement IS NOT NULL" else null, + if (noOpenIncomeStatements) + """ + NOT EXISTS ( + SELECT FROM income_statement + WHERE person_id IN (decision.head_of_family_id, decision.partner_id, part.child_id) AND + daterange(start_date, end_date, '[]') && daterange((:now - interval '14 months')::date, :now::date, '[]') AND + handler_id IS NULL + ) + """ + else null, ) val youngestChildQuery = From 4194ba1d3681b2dcca474f45edfade0aea439ef5 Mon Sep 17 00:00:00 2001 From: Tero Laakso Date: Thu, 28 Nov 2024 10:55:20 +0200 Subject: [PATCH 3/4] Add more tests for NO_OPEN_INCOME_STATEMENTS --- .../invoicing/FeeDecisionIntegrationTest.kt | 79 ++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt b/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt index 62a35c489a7..6f15ec40a4f 100755 --- a/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt +++ b/service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/FeeDecisionIntegrationTest.kt @@ -547,17 +547,20 @@ class FeeDecisionIntegrationTest : FullApplicationTest(resetDbBeforeEach = true) val clock = RealEvakaClock() val decisionWithHandledStatement = createFeeDecisionsForFamily(testAdult_1, testAdult_2, listOf(testChild_1)) - val decisionWithOpenStatement = + val decisionWithOpenAdultStatement = createFeeDecisionsForFamily(testAdult_3, testAdult_4, listOf(testChild_2)) val decisionWithFarAwayAndFutureOpenStatements = createFeeDecisionsForFamily(testAdult_5, testAdult_6, listOf(testChild_3)) + val decisionWithOpenChildStatement = + createFeeDecisionsForFamily(testAdult_7, partner = null, listOf(testChild_4)) db.transaction { tx -> tx.upsertFeeDecisions( listOf( decisionWithHandledStatement, - decisionWithOpenStatement, + decisionWithOpenAdultStatement, decisionWithFarAwayAndFutureOpenStatements, + decisionWithOpenChildStatement, ) ) val adult1StatementId = @@ -608,6 +611,25 @@ class FeeDecisionIntegrationTest : FullApplicationTest(resetDbBeforeEach = true) personId = testAdult_6.id, ) // testAdult_6 statement not handled + val adult7StatementId = + tx.createIncomeStatement( + body = + IncomeStatementBody.HighestFee( + clock.today().minusMonths(2), + clock.today().minusMonths(1), + ), + personId = testAdult_7.id, + ) + tx.updateIncomeStatementHandled(adult7StatementId, "handled", testDecisionMaker_1.id) + tx.createIncomeStatement( + body = + IncomeStatementBody.HighestFee( + clock.today().minusMonths(2), + clock.today().minusMonths(1), + ), + personId = testChild_4.id, + ) + // testChild_4 statement not handled } val result = @@ -627,6 +649,59 @@ class FeeDecisionIntegrationTest : FullApplicationTest(resetDbBeforeEach = true) ) } + @Test + fun `search works with distinctions param NO_OPEN_INCOME_STATEMENTS for childless decisions`() { + val clock = RealEvakaClock() + val decisionWithHandledStatements = + createFeeDecisionsForFamily(testAdult_1, testAdult_2, listOf()) + val decisionWithOpenStatements = + createFeeDecisionsForFamily(testAdult_3, testAdult_4, listOf()) + + db.transaction { tx -> + tx.upsertFeeDecisions(listOf(decisionWithHandledStatements, decisionWithOpenStatements)) + val adult1StatementId = + tx.createIncomeStatement( + body = + IncomeStatementBody.HighestFee( + clock.today().minusMonths(2), + clock.today().minusMonths(1), + ), + personId = testAdult_1.id, + ) + // testAdult_2 statement not submitted + tx.updateIncomeStatementHandled(adult1StatementId, "handled", testDecisionMaker_1.id) + val adult3StatementId = + tx.createIncomeStatement( + body = + IncomeStatementBody.HighestFee( + clock.today().minusMonths(2), + clock.today().minusMonths(1), + ), + personId = testAdult_3.id, + ) + tx.updateIncomeStatementHandled(adult3StatementId, "handled", testDecisionMaker_1.id) + tx.createIncomeStatement( + body = + IncomeStatementBody.HighestFee( + clock.today().minusMonths(2), + clock.today().minusMonths(1), + ), + personId = testAdult_4.id, + ) + // testAdult_4 statement not handled + } + + val result = + searchDecisions( + SearchFeeDecisionRequest( + page = 0, + distinctions = listOf(DistinctiveParams.NO_OPEN_INCOME_STATEMENTS), + ) + ) + + assertEqualEnough(listOf(toSummary(decisionWithHandledStatements)), result.data) + } + @Test fun `search works as expected with existing area param`() { db.transaction { tx -> tx.upsertFeeDecisions(testDecisions) } From bdcb3892442ca79efcd38b0ef72b067aab1e0e10 Mon Sep 17 00:00:00 2001 From: Tero Laakso Date: Thu, 28 Nov 2024 12:30:13 +0200 Subject: [PATCH 4/4] Update translation --- frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx index f58822c25a4..a291533e6e8 100755 --- a/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx +++ b/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx @@ -3077,7 +3077,7 @@ export const fi = { NO_STARTING_PLACEMENTS: 'Piilota uudet aloittavat lapset', MAX_FEE_ACCEPTED: 'Suostumus korkeimpaan maksuun', PRESCHOOL_CLUB: 'Vain esiopetuksen kerho', - NO_OPEN_INCOME_STATEMENTS: 'Piilota avonaiset tuloselvitykset' + NO_OPEN_INCOME_STATEMENTS: 'Ei avoimia tuloselvityksiä' }, status: { DRAFT: 'Luonnos',