Skip to content

Commit

Permalink
WIP forecast.
Browse files Browse the repository at this point in the history
  • Loading branch information
kreinhard committed Jan 13, 2025
1 parent fcfe11f commit 0b25918
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class ForecastOrderAnalysis {
val html = HtmlDocument(title)
html.add(H1(title))
html.add(Alert(Alert.Type.INFO).also { div ->
div.add(Span("Forecast values are displayed in "))
div.add(Span("Forecast values are shown in "))
div.add(Span("red,", style = "color: red; font-weight: bold;"))
div.add(Span(" invoiced amounts in "))
div.add(Span("black.", style = "color: black; font-weight: bold;"))
Expand All @@ -104,7 +104,7 @@ class ForecastOrderAnalysis {
addRow(table, translate("fibu.notYetInvoiced"), orderInfo.notYetInvoicedSum)
})

html.add(H2(translate("fibu.auftrag.forecast"))) // Forecast for all positions
html.add(H2("${translate("fibu.auftrag.forecast")} all positions")) // Forecast for all positions
html.add(HtmlTable().also { table ->
val headRow = table.addHeadRow()
headRow.addTH(translate("label.position.short"))
Expand Down Expand Up @@ -204,7 +204,7 @@ class ForecastOrderAnalysis {
}
}
})
html.add(H3(translate("fibu.auftrag.forecast")))
html.add(H3("${translate("fibu.auftrag.forecast")} #${posInfo.number}")) // Forecast current position
html.add(HtmlTable().also { table ->
val headRow = table.addHeadRow()
val row = table.addRow()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ class ForecastOrderPosInfo(
return
}
val monthCount = firstMonth.monthsBetween(lastMonth) + 1 // Jan-Jan -> 1, Jan-Feb -> 2, ...
val partlyNettoSum = probabilityNetSumWithoutPaymentSchedule.divide(
val partlyNetSum = probabilityNetSumWithoutPaymentSchedule.divide(
BigDecimal.valueOf(monthCount),
RoundingMode.HALF_UP
)
Expand All @@ -220,13 +220,13 @@ class ForecastOrderPosInfo(
// If month is the last month of performance period, the total rest of sum is to be invoiced.
if (DISTRIBUTE_UNUSED_BUDGET) {
// Version 1 (unused budget will be added to last month (overestimation)):
maxOf(partlyNettoSum, futureInvoicesAmountRest)
futureInvoicesAmountRest
} else {
// Version 2 (unused budget isn't part of forecast and will be shown as negative difference sum (more realistic scenario?):
minOf(partlyNettoSum, futureInvoicesAmountRest)
minOf(partlyNetSum, futureInvoicesAmountRest)
}
} else {
partlyNettoSum
partlyNetSum
}
if (value.abs() > BigDecimal.ONE) { // values < 0 are possible for Abrufaufträge (Sarah fragen, 4273)
setMonthValue(month, value)
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,23 @@ class ForecastOrderPosInfoTest {
netSum = BigDecimal("69456.24"),
invoicedSum = BigDecimal("6924.95")
).also { pos ->
ForecastOrderPosInfo(orderInfo, pos, baseDate = baseDate).also { forecastInfo ->
forecastInfo.calculate()
Assertions.assertEquals(6, forecastInfo.months.size)
ForecastOrderPosInfo(orderInfo, pos, baseDate = baseDate).also { fcPosInfo ->
fcPosInfo.calculate()
Assertions.assertEquals(6, fcPosInfo.months.size)
for (i in 0..1) {
Assertions.assertEquals(BigDecimal.ZERO, forecastInfo.months[i].toBeInvoicedSum)
Assertions.assertEquals(BigDecimal.ZERO, fcPosInfo.months[i].toBeInvoicedSum)
}
if (ForecastOrderPosInfo.DISTRIBUTE_UNUSED_BUDGET) {
for (i in 2..4) {
assertSame("13891.25", forecastInfo.months[i].toBeInvoicedSum)
assertSame("13891.25", fcPosInfo.months[i].toBeInvoicedSum)
}
assertSame("20857.54", forecastInfo.months[5].toBeInvoicedSum)
Assertions.assertEquals(BigDecimal.ZERO, forecastInfo.difference)
assertSame("20857.54", fcPosInfo.months[5].toBeInvoicedSum)
Assertions.assertEquals(BigDecimal.ZERO, fcPosInfo.difference)
} else {
for (i in 2..5) {
assertSame("13891.25", forecastInfo.months[i].toBeInvoicedSum)
assertSame("13891.25", fcPosInfo.months[i].toBeInvoicedSum)
}
assertSame("-6966.29", forecastInfo.difference)
assertSame("-6966.29", fcPosInfo.difference)
}
}
}
Expand All @@ -75,15 +75,15 @@ class ForecastOrderPosInfoTest {
AuftragsStatus.GELEGT, AuftragsPositionsPaymentType.TIME_AND_MATERIALS,
PeriodOfPerformanceType.SEEABOVE, netSum = BigDecimal("1000000.00")
).also { pos ->
ForecastOrderPosInfo(orderInfo, pos, baseDate = baseDate).also { forecastInfo ->
forecastInfo.calculate()
Assertions.assertEquals(13, forecastInfo.months.size)
Assertions.assertEquals(BigDecimal.ZERO, forecastInfo.difference)
Assertions.assertEquals(BigDecimal.ZERO, forecastInfo.months[0].toBeInvoicedSum)
ForecastOrderPosInfo(orderInfo, pos, baseDate = baseDate).also { fcPosInfo ->
fcPosInfo.calculate()
Assertions.assertEquals(13, fcPosInfo.months.size)
Assertions.assertEquals(BigDecimal.ZERO, fcPosInfo.difference)
Assertions.assertEquals(BigDecimal.ZERO, fcPosInfo.months[0].toBeInvoicedSum)
for (i in 1..12) {
assertSame("41666.6667", forecastInfo.months[i].toBeInvoicedSum)
assertSame("41666.6667", fcPosInfo.months[i].toBeInvoicedSum)
}
assertSame("125000.00", forecastInfo.getRemainingForecastSumAfter(PFDay.of(2025, Month.OCTOBER, 31)))
assertSame("125000.00", fcPosInfo.getRemainingForecastSumAfter(PFDay.of(2025, Month.OCTOBER, 31)))
}
}
}
Expand All @@ -98,27 +98,50 @@ class ForecastOrderPosInfoTest {
AuftragsStatus.BEAUFTRAGT, AuftragsPositionsPaymentType.FESTPREISPAKET,
PeriodOfPerformanceType.SEEABOVE, netSum = BigDecimal("64372.00")
).also { pos ->
ForecastOrderPosInfo(orderInfo, pos, baseDate = baseDate).also { forecastInfo ->
forecastInfo.calculate()
Assertions.assertEquals(7, forecastInfo.months.size, "September -> March")
ForecastOrderPosInfo(orderInfo, pos, baseDate = baseDate).also { fcPosInfo ->
fcPosInfo.calculate()
Assertions.assertEquals(7, fcPosInfo.months.size, "September -> March")
for (i in 0..3) {
// December payment is before baseDate.
Assertions.assertEquals(
BigDecimal.ZERO,
forecastInfo.months[i].toBeInvoicedSum,
fcPosInfo.months[i].toBeInvoicedSum,
"September - December no payments (all in the past)"
)
}
for (i in 4..6) {
Assertions.assertEquals(
"21457.33",
forecastInfo.months[i].toBeInvoicedSum.toString(),
fcPosInfo.months[i].toBeInvoicedSum.toString(),
"payments in January, February and March"
)
}
}
}
}
OrderInfo().also { orderInfo -> // Order 5850
orderInfo.status = AuftragsStatus.ABGESCHLOSSEN
orderInfo.periodOfPerformanceBegin = LocalDate.of(2024, Month.JANUARY, 2)
orderInfo.periodOfPerformanceEnd = LocalDate.of(2024, Month.DECEMBER, 31)
createPos(
AuftragsStatus.BEAUFTRAGT, AuftragsPositionsPaymentType.TIME_AND_MATERIALS,
PeriodOfPerformanceType.SEEABOVE, netSum = BigDecimal("120000.00")
).also { pos ->
pos.invoicedSum = BigDecimal("120000.00")
ForecastOrderPosInfo(orderInfo, pos, baseDate = baseDate).also { fcPosInfo ->
fcPosInfo.calculate()
Assertions.assertEquals(13, fcPosInfo.months.size, "September -> March")
for (i in 0..12) {
// December payment is before baseDate.
Assertions.assertEquals(
BigDecimal.ZERO,
fcPosInfo.months[i].toBeInvoicedSum,
"Jan - jan no payments (all is invoiced), ${fcPosInfo.months[i].date} should be 0.00 but is ${fcPosInfo.months[i].toBeInvoicedSum}"
)
}
}
}
}
}

private fun createPos(
Expand Down

0 comments on commit 0b25918

Please sign in to comment.