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

Maksupäätöksien hakuun uusi hakuehto: ei avoimia tuloselvityksiä #6043

Merged
merged 4 commits into from
Nov 28, 2024
Merged
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
3 changes: 2 additions & 1 deletion frontend/src/lib-common/generated/api-types/invoicing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: 'Ei avoimia tuloselvityksiä'
},
status: {
DRAFT: 'Luonnos',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -524,6 +527,181 @@ 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 with distinctions param NO_OPEN_INCOME_STATEMENTS`() {
val clock = RealEvakaClock()
val decisionWithHandledStatement =
createFeeDecisionsForFamily(testAdult_1, testAdult_2, listOf(testChild_1))
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,
decisionWithOpenAdultStatement,
decisionWithFarAwayAndFutureOpenStatements,
decisionWithOpenChildStatement,
)
)
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 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 =
searchDecisions(
SearchFeeDecisionRequest(
page = 0,
distinctions = listOf(DistinctiveParams.NO_OPEN_INCOME_STATEMENTS),
)
)

assertEqualEnough(
listOf(
toSummary(decisionWithHandledStatement),
toSummary(decisionWithFarAwayAndFutureOpenStatements),
),
result.data,
)
}

@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) }
Expand Down Expand Up @@ -2025,21 +2203,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ enum class DistinctiveParams {
NO_STARTING_PLACEMENTS,
MAX_FEE_ACCEPTED,
PRESCHOOL_CLUB,
NO_OPEN_INCOME_STATEMENTS,
}

@RestController
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 =
Expand Down