Skip to content

Commit 6ae59a3

Browse files
committed
Use targetMonth = NULL for unapplied corrections
1 parent c89a4f4 commit 6ae59a3

File tree

10 files changed

+50
-107
lines changed

10 files changed

+50
-107
lines changed

frontend/src/employee-frontend/components/person-profile/PersonInvoiceCorrections.tsx

+10-8
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,9 @@ const ChildSection = React.memo(function ChildSection({
199199
orderBy(
200200
corrections,
201201
[
202-
(c) => c.data.targetMonth.year,
203-
(c) => c.data.targetMonth.month,
202+
// orderBy puts undefineds first in desc ordering, so unapplied corrections are first in the list
203+
(c) => c.data.targetMonth?.year,
204+
(c) => c.data.targetMonth?.month,
204205
(c) => c.data.period.start.toSystemTzDate(),
205206
(c) => c.data.period.end.toSystemTzDate(),
206207
(c) => c.data.product,
@@ -309,8 +310,9 @@ const InvoiceCorrectionRowReadView = React.memo(
309310
)}
310311
<Tr data-qa="invoice-details-invoice-row">
311312
<Td data-qa="target-month">
312-
{correction.targetMonth.month.toString().padStart(2, '0')} /{' '}
313-
{correction.targetMonth.year}
313+
{correction.targetMonth === null
314+
? i18n.invoiceCorrections.nextTargetMonth
315+
: `${correction.targetMonth.month.toString().padStart(2, '0')} / ${correction.targetMonth.year}`}
314316
</Td>
315317
<Td data-qa="period">{correction.period.format()}</Td>
316318
<Td data-qa="product">{productName}</Td>
@@ -423,9 +425,10 @@ const InvoiceCorrectionEditModal = React.memo(
423425
const form = useForm(
424426
correctionForm,
425427
() => ({
426-
targetMonth: row
427-
? localDate.fromDate(row.targetMonth.atDay(1))
428-
: localDate.empty(),
428+
targetMonth:
429+
row && row.targetMonth
430+
? localDate.fromDate(row.targetMonth.atDay(1))
431+
: localDate.empty(),
429432
product: {
430433
domValue: row?.product ?? '',
431434
options: products.map((p) => ({
@@ -479,7 +482,6 @@ const InvoiceCorrectionEditModal = React.memo(
479482
body: {
480483
headOfFamilyId: personId,
481484
childId,
482-
targetMonth: null, // backend fills for now
483485
product: product.value(),
484486
description: description.value(),
485487
unitId: unit.value(),

frontend/src/lib-common/generated/api-types/invoicing.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ export interface InvoiceCorrection {
536536
note: string
537537
period: FiniteDateRange
538538
product: string
539-
targetMonth: YearMonth
539+
targetMonth: YearMonth | null
540540
unitId: UUID
541541
unitPrice: number
542542
}
@@ -552,7 +552,6 @@ export interface InvoiceCorrectionInsert {
552552
note: string
553553
period: FiniteDateRange
554554
product: string
555-
targetMonth: YearMonth | null
556555
unitId: UUID
557556
unitPrice: number
558557
}
@@ -1316,16 +1315,15 @@ export function deserializeJsonInvoiceCorrection(json: JsonOf<InvoiceCorrection>
13161315
return {
13171316
...json,
13181317
period: FiniteDateRange.parseJson(json.period),
1319-
targetMonth: YearMonth.parseIso(json.targetMonth)
1318+
targetMonth: (json.targetMonth != null) ? YearMonth.parseIso(json.targetMonth) : null
13201319
}
13211320
}
13221321

13231322

13241323
export function deserializeJsonInvoiceCorrectionInsert(json: JsonOf<InvoiceCorrectionInsert>): InvoiceCorrectionInsert {
13251324
return {
13261325
...json,
1327-
period: FiniteDateRange.parseJson(json.period),
1328-
targetMonth: (json.targetMonth != null) ? YearMonth.parseIso(json.targetMonth) : null
1326+
period: FiniteDateRange.parseJson(json.period)
13291327
}
13301328
}
13311329

service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/service/InvoiceCorrectionsIntegrationTest.kt

+15-15
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
172172
assertEquals(100_00, secondInvoice.rows.first().unitPrice)
173173
assertEquals(2, secondInvoice.rows.last().amount)
174174
assertEquals(-40_00, secondInvoice.rows.last().unitPrice)
175-
assertEquals(getCorrectionForMonth(secondMonth).id, secondInvoice.rows.last().correctionId)
175+
assertEquals(getUnappliedCorrection().id, secondInvoice.rows.last().correctionId)
176176

177177
insertAndSendInvoice(secondInvoice)
178178

@@ -184,7 +184,7 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
184184
assertEquals(100_00, thirdInvoice.rows.first().unitPrice)
185185
assertEquals(1, thirdInvoice.rows.last().amount)
186186
assertEquals(-40_00, thirdInvoice.rows.last().unitPrice)
187-
assertEquals(getCorrectionForMonth(thirdMonth).id, thirdInvoice.rows.last().correctionId)
187+
assertEquals(getUnappliedCorrection().id, thirdInvoice.rows.last().correctionId)
188188

189189
insertAndSendInvoice(thirdInvoice)
190190
}
@@ -213,7 +213,7 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
213213
assertEquals(100_00, secondInvoice.rows.first().unitPrice)
214214
assertEquals(1, secondInvoice.rows.last().amount)
215215
assertEquals(-100_00, secondInvoice.rows.last().unitPrice)
216-
assertEquals(getCorrectionForMonth(secondMonth).id, secondInvoice.rows.last().correctionId)
216+
assertEquals(getUnappliedCorrection().id, secondInvoice.rows.last().correctionId)
217217

218218
insertAndSendInvoice(secondInvoice)
219219

@@ -225,7 +225,7 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
225225
assertEquals(100_00, thirdInvoice.rows.first().unitPrice)
226226
assertEquals(1, thirdInvoice.rows.last().amount)
227227
assertEquals(-50_00, thirdInvoice.rows.last().unitPrice)
228-
assertEquals(getCorrectionForMonth(thirdMonth).id, thirdInvoice.rows.last().correctionId)
228+
assertEquals(getUnappliedCorrection().id, thirdInvoice.rows.last().correctionId)
229229

230230
insertAndSendInvoice(thirdInvoice)
231231
}
@@ -274,7 +274,7 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
274274
assertEquals(100_00, secondInvoice.rows.first().unitPrice)
275275
assertEquals(1, secondInvoice.rows.last().amount)
276276
assertEquals(-50_00, secondInvoice.rows.last().unitPrice)
277-
assertEquals(getCorrectionForMonth(secondMonth).id, secondInvoice.rows.last().correctionId)
277+
assertEquals(getUnappliedCorrection().id, secondInvoice.rows.last().correctionId)
278278

279279
insertAndSendInvoice(secondInvoice)
280280
}
@@ -326,7 +326,7 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
326326
assertEquals(30_00, secondInvoice.rows.first().unitPrice)
327327
assertEquals(3, secondInvoice.rows.last().amount)
328328
assertEquals(-10_00, secondInvoice.rows.last().unitPrice)
329-
val secondCorrection = getCorrectionForMonth(secondMonth)
329+
val secondCorrection = getUnappliedCorrection()
330330
assertEquals(secondCorrection.id, secondInvoice.rows.last().correctionId)
331331

332332
insertAndSendInvoice(secondInvoice)
@@ -339,7 +339,7 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
339339
assertEquals(100_00, thirdInvoice.rows.first().unitPrice)
340340
assertEquals(2, thirdInvoice.rows.last().amount)
341341
assertEquals(-40_00, thirdInvoice.rows.last().unitPrice)
342-
assertEquals(getCorrectionForMonth(thirdMonth).id, thirdInvoice.rows.last().correctionId)
342+
assertEquals(getUnappliedCorrection().id, thirdInvoice.rows.last().correctionId)
343343

344344
insertAndSendInvoice(thirdInvoice)
345345

@@ -351,7 +351,7 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
351351
assertEquals(100_00, fourthInvoice.rows.first().unitPrice)
352352
assertEquals(1, fourthInvoice.rows.last().amount)
353353
assertEquals(-40_00, fourthInvoice.rows.last().unitPrice)
354-
assertEquals(getCorrectionForMonth(fourthMonth).id, fourthInvoice.rows.last().correctionId)
354+
assertEquals(getUnappliedCorrection().id, fourthInvoice.rows.last().correctionId)
355355

356356
insertAndSendInvoice(fourthInvoice)
357357
}
@@ -363,7 +363,8 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
363363
val invoice = applyCorrections(createTestInvoice(100_00, month), month)
364364
insertAndSendInvoice(invoice)
365365

366-
val correction = getCorrectionForMonth(month)
366+
val correction =
367+
db.read { tx -> tx.getInvoiceCorrectionsByIds(setOf(correctionId)).single() }
367368
assertEquals(correctionId, correction.id)
368369
assertEquals(1, correction.amount)
369370
assertEquals(-50_00, correction.unitPrice)
@@ -376,9 +377,9 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
376377
val invoice = applyCorrections(createTestInvoice(100_00, month), month)
377378
insertAndSendInvoice(invoice)
378379

379-
val nextMonthCorrection = getCorrectionForMonth(month.plusMonths(1))
380-
assertEquals(1, nextMonthCorrection.amount)
381-
assertEquals(-100_00, nextMonthCorrection.unitPrice)
380+
val unappliedCorrection = getUnappliedCorrection()
381+
assertEquals(1, unappliedCorrection.amount)
382+
assertEquals(-100_00, unappliedCorrection.unitPrice)
382383
}
383384

384385
@Test
@@ -455,7 +456,7 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
455456
it.insert(
456457
DevInvoiceCorrection(
457458
headOfFamilyId = adult.id,
458-
targetMonth = month,
459+
targetMonth = null,
459460
childId = child.id,
460461
amount = amount,
461462
unitPrice = unitPrice,
@@ -468,6 +469,5 @@ class InvoiceCorrectionsIntegrationTest : PureJdbiTest(resetDbBeforeEach = true)
468469
)
469470
}
470471

471-
private fun getCorrectionForMonth(month: YearMonth) =
472-
db.read { it.getInvoiceCorrectionsForMonth(month) }.single()
472+
private fun getUnappliedCorrection() = db.read { it.getUnappliedInvoiceCorrections() }.single()
473473
}

service/src/integrationTest/kotlin/fi/espoo/evaka/invoicing/service/InvoiceGeneratorIntegrationTest.kt

+3-4
Original file line numberDiff line numberDiff line change
@@ -4646,7 +4646,7 @@ class InvoiceGeneratorIntegrationTest : PureJdbiTest(resetDbBeforeEach = true) {
46464646
db.transaction {
46474647
it.insert(
46484648
DevInvoiceCorrection(
4649-
targetMonth = month,
4649+
targetMonth = null,
46504650
headOfFamilyId = testAdult_1.id,
46514651
childId = testChild_1.id,
46524652
amount = 1,
@@ -4696,9 +4696,8 @@ class InvoiceGeneratorIntegrationTest : PureJdbiTest(resetDbBeforeEach = true) {
46964696
}
46974697

46984698
@Test
4699-
fun `unapplied invoice correction is moved to the current month`() {
4699+
fun `unapplied invoice correction is applied to the next draft invoice`() {
47004700
val month = YearMonth.of(2019, 1)
4701-
val previousMonth = month.minusMonths(1)
47024701

47034702
val period = FiniteDateRange.ofMonth(month)
47044703
db.transaction(insertChildParentRelation(testAdult_1.id, testChild_1.id, period))
@@ -4727,7 +4726,7 @@ class InvoiceGeneratorIntegrationTest : PureJdbiTest(resetDbBeforeEach = true) {
47274726
db.transaction {
47284727
it.insert(
47294728
DevInvoiceCorrection(
4730-
targetMonth = previousMonth,
4729+
targetMonth = null,
47314730
headOfFamilyId = testAdult_1.id,
47324731
childId = testChild_1.id,
47334732
amount = 1,

service/src/main/kotlin/fi/espoo/evaka/invoicing/service/InvoiceCorrection.kt

+8-14
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ data class InvoiceWithCorrection(@PropagateNull val id: InvoiceId, val status: I
2424

2525
data class InvoiceCorrection(
2626
val id: InvoiceCorrectionId,
27-
val targetMonth: YearMonth,
27+
val targetMonth: YearMonth?,
2828
val headOfFamilyId: PersonId,
2929
val childId: ChildId,
3030
val unitId: DaycareId,
@@ -52,7 +52,6 @@ data class InvoiceCorrection(
5252

5353
fun toInsert() =
5454
InvoiceCorrectionInsert(
55-
targetMonth = targetMonth,
5655
headOfFamilyId = headOfFamilyId,
5756
childId = childId,
5857
unitId = unitId,
@@ -94,18 +93,18 @@ fun generateInvoiceCorrectionChanges(
9493
val appliedTotal = row.amount * row.unitPrice
9594
val outstandingTotal = total - appliedTotal
9695

97-
if (outstandingTotal == 0) {
98-
// Correction is fully applied
99-
return@mapNotNull null
100-
}
101-
10296
val assignedInvoiceCorrection =
10397
InvoiceCorrectionUpdate(
10498
id = correction.id,
99+
targetMonth = targetMonth,
105100
amount = row.amount,
106101
unitPrice = row.unitPrice,
107102
)
108103

104+
if (outstandingTotal == 0) {
105+
// Correction is fully applied
106+
return@mapNotNull assignedInvoiceCorrection to null
107+
}
109108
val (remainingAmount, remainingUnitPrice) =
110109
if (
111110
row.unitPrice == correction.unitPrice &&
@@ -117,15 +116,10 @@ fun generateInvoiceCorrectionChanges(
117116
correction.amount to (outstandingTotal / correction.amount)
118117
}
119118
val remainingCorrection =
120-
correction
121-
.copy(
122-
targetMonth = targetMonth.plusMonths(1),
123-
amount = remainingAmount,
124-
unitPrice = remainingUnitPrice,
125-
)
126-
.toInsert()
119+
correction.copy(amount = remainingAmount, unitPrice = remainingUnitPrice).toInsert()
127120

128121
assignedInvoiceCorrection to remainingCorrection
129122
}
130123
.unzip()
124+
.let { (updates, inserts) -> updates to inserts.filterNotNull() }
131125
}

service/src/main/kotlin/fi/espoo/evaka/invoicing/service/InvoiceCorrectionQueries.kt

+5-38
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@
44

55
package fi.espoo.evaka.invoicing.service
66

7-
import fi.espoo.evaka.invoicing.data.getFirstUninvoicedMonth
87
import fi.espoo.evaka.shared.ChildId
98
import fi.espoo.evaka.shared.DaycareId
109
import fi.espoo.evaka.shared.InvoiceCorrectionId
1110
import fi.espoo.evaka.shared.PersonId
1211
import fi.espoo.evaka.shared.db.Database
1312
import fi.espoo.evaka.shared.db.Predicate
14-
import fi.espoo.evaka.shared.db.PredicateSql
1513
import fi.espoo.evaka.shared.domain.FiniteDateRange
1614
import java.time.YearMonth
1715
import kotlin.math.abs
@@ -48,8 +46,8 @@ WHERE ${predicate(where.forTable("invoice_correction"))}
4846
}
4947
.toList()
5048

51-
fun Database.Read.getInvoiceCorrectionsForMonth(month: YearMonth): List<InvoiceCorrection> =
52-
getInvoiceCorrections(Predicate { where("$it.target_month = ${bind(month)}") })
49+
fun Database.Read.getUnappliedInvoiceCorrections(): List<InvoiceCorrection> =
50+
getInvoiceCorrections(Predicate { where("$it.target_month IS NULL") })
5351

5452
fun Database.Read.getInvoiceCorrectionsByIds(
5553
ids: Set<InvoiceCorrectionId>
@@ -65,33 +63,7 @@ fun Database.Read.getInvoiceCorrectionsForHeadOfFamily(
6563
.thenByDescending { abs(it.amount * it.unitPrice) }
6664
)
6765

68-
fun Database.Transaction.movePastUnappliedInvoiceCorrections(
69-
headOfFamilyIds: Set<PersonId>?,
70-
targetMonth: YearMonth,
71-
) {
72-
val headOfFamilyPredicate =
73-
if (headOfFamilyIds != null) {
74-
PredicateSql { where("head_of_family_id = ANY (${bind(headOfFamilyIds)})") }
75-
} else {
76-
PredicateSql.alwaysTrue()
77-
}
78-
79-
execute {
80-
sql(
81-
"""
82-
UPDATE invoice_correction
83-
SET target_month = ${bind(targetMonth)}
84-
WHERE
85-
target_month < ${bind(targetMonth)} AND
86-
${predicate(headOfFamilyPredicate)} AND
87-
NOT EXISTS (SELECT FROM invoice_row WHERE correction_id = invoice_correction.id)
88-
"""
89-
)
90-
}
91-
}
92-
9366
data class InvoiceCorrectionInsert(
94-
val targetMonth: YearMonth?,
9567
val headOfFamilyId: PersonId,
9668
val childId: ChildId,
9769
val unitId: DaycareId,
@@ -110,17 +82,11 @@ fun Database.Transaction.insertInvoiceCorrection(
11082
fun Database.Transaction.insertInvoiceCorrections(
11183
corrections: Iterable<InvoiceCorrectionInsert>
11284
): List<InvoiceCorrectionId> {
113-
val defaultTargetMonth =
114-
if (corrections.any { it.targetMonth == null }) {
115-
getFirstUninvoicedMonth()
116-
} else null // not needed
117-
11885
return prepareBatch(corrections) {
11986
sql(
12087
"""
121-
INSERT INTO invoice_correction (target_month, head_of_family_id, child_id, unit_id, product, period, amount, unit_price, description, note)
88+
INSERT INTO invoice_correction (head_of_family_id, child_id, unit_id, product, period, amount, unit_price, description, note)
12289
VALUES (
123-
${bind { it.targetMonth ?: defaultTargetMonth }},
12490
${bind { it.headOfFamilyId }},
12591
${bind { it.childId }},
12692
${bind { it.unitId }},
@@ -141,6 +107,7 @@ RETURNING id
141107

142108
data class InvoiceCorrectionUpdate(
143109
val id: InvoiceCorrectionId,
110+
val targetMonth: YearMonth,
144111
val amount: Int,
145112
val unitPrice: Int,
146113
)
@@ -150,7 +117,7 @@ fun Database.Transaction.updateInvoiceCorrections(items: Iterable<InvoiceCorrect
150117
sql(
151118
"""
152119
UPDATE invoice_correction
153-
SET amount = ${bind { it.amount }}, unit_price = ${bind { it.unitPrice }}
120+
SET target_month = ${bind { it.targetMonth }}, amount = ${bind { it.amount }}, unit_price = ${bind { it.unitPrice }}
154121
WHERE id = ${bind { it.id }}
155122
"""
156123
)

0 commit comments

Comments
 (0)