Skip to content

Commit

Permalink
Merge pull request #259 from micromata/Release-8.1-SNAPSHOT
Browse files Browse the repository at this point in the history
Release 8.1 snapshot
Forecast, order snapshot and Wicket-bug fixed: return in textarea submits form.
  • Loading branch information
kreinhard authored Jan 17, 2025
2 parents 4d4ac35 + 0a2fd8f commit 7a15cec
Show file tree
Hide file tree
Showing 18 changed files with 286 additions and 97 deletions.
36 changes: 25 additions & 11 deletions ToDo.adoc
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
Aktuell:
==== Aktuell:

- Favoriten bei Scriptausführung für Parameter.
- KI-Anteil in Zeitberichten
- Viewpage für user für non-admins.
- Scripting: Ergebnis Unresolved reference 'memo', 'todo'.: line 94 to 94 (add only activated plugins)
- Can't get value type from 'org.projectforge.business.timesheet.TimesheetPrefData'. Class not found (old incompatible ProjectForge version)?
- Summen in DB-Exporten eintragen und serverseitig evaluieren.
- Groovy-scripts: remove or fix.
- AG-Grid: setColumnStates wird nicht in den UserPrefs gespeichert.
- Wicket: Auftragsbuch: org.apache.wicket.core.request.mapper.StalePageException: A request to page '[Page class = org.projectforge.web.fibu.AuftragEditPage, id = 9, render count = 3]' has been made with stale 'renderCount'. The page will be re-rendered.
- -XX:ReservedCodeCacheSize=100m
- QueryFilter.sortAndLimitMaxRowsWhileSelect: not yet supported
- Gradle-Version-Checker: Read lib.versions.toml und Vergleichen mit fatjar.
Expand Down Expand Up @@ -44,32 +43,43 @@ History
- reindexing settings implementieren. Aktuell wird nur komplett gemass-indexed.
- History of AddressCampaignValueDO's (AddressCampaignValueDao.convertToDisplayHistoryEntries removed)
Später
==== Später

- Fakturaquote
- Suche-Seite hat veraltete Bereiche, AddressListPage läuft auf Fehler.
- OrderExport: paymentSchedules werden gefetcht.

Ganz später
==== Ganz später

- Kalenderlist ruft x-fach DB: FIND GroupDO resultClass=GroupDO auf.

Rancher
==== Rancher

[source]
----
docker system df
docker system prune -a --volumes
docker system df
docker volume ls
docker volume rm <volume-name>
----

==== Postgresql-Dump-Imports bechleunigen:

Postgresql-Dump-Imports bechleunigen:

[source]
----
docker run --name projectforge-postgres -p 127.0.0.1:5432:5432 -e POSTGRES_PASSWORD=$PGPASSWORD -e POSTGRES_USER=projectforge -d postgres:13.18
docker run -e PGPASSWORD=$PGPASSWORD -it --rm --link projectforge-postgres:postgres postgres:13.18 psql -h postgres -U projectforge
ALTER SYSTEM SET fsync = off;
ALTER SYSTEM SET synchronous_commit = off;
SET maintenance_work_mem = '512MB';
----

==== Postgresql-Orderbook-Snapshots-Imports:

[source]
----
gunzip projectforge-*.sql.gz
docker run -v ~/ProjectForgeBackup/pf.sql:/mnt/pf.sql -e PGPASSWORD=$PGPASSWORD -it --rm --link projectforge-postgres:postgres postgres:13.18 psql -h postgres -U projectforge -q -f /mnt/pf.sql
Expand All @@ -78,15 +88,20 @@ update t_pf_user_password SET password_hash='SHA{BC871652288E56E306CFA093BEFC3FF
update t_pf_user SET password='SHA{BC871652288E56E306CFA093BEFC3FFCD0ED8872}', password_salt=null, email='m.developer@localhost';
update t_calendar set ext_subscription=false;
insert into t_pf_user_password (pk,deleted,user_id,password_hash) values(2,false,2,'SHA{BC871652288E56E306CFA093BEFC3FFCD0ED8872}');
----

Orderbook-Export über die GUI Auftragsbuch -> Dev: export order book

[source]
----
\c postgres;
DROP DATABASE projectforge;
CREATE DATABASE projectforge;
----

Orderbooks importieren:
[source]
----
docker cp ~/ProjectForgeBackup/ProjectForge-Orderbook_*.gz projectforge-postgres:/tmp/
\set file_path '/tmp/ProjectForge-Orderbook_2023-11-01.gz'
Expand All @@ -95,5 +110,4 @@ INSERT INTO t_fibu_orderbook_snapshots (date, created, serialized_orderbook, siz
docker run -e PGPASSWORD=$PGPASSWORD -it --rm --link projectforge-postgres:postgres postgres:13.18 pg_dump -h postgres -U projectforge --data-only --column-inserts --table=t_fibu_orderbook_snapshots
remove duplicates
psql -f export.sql


----
1 change: 1 addition & 0 deletions projectforge-application/src/main/resources/i18nKeys.json
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@
{"i18nKey":"fibu.auftrag.invoice.info","bundleName":"I18nResources","translation":", invoiced: {0}, not yet invoiced: {1}","translationDE":", fakturiert: {0}, noch nicht fakturiert: {1}","usedInClasses":["org.projectforge.web.fibu.AuftragEditForm"],"usedInFiles":[]},
{"i18nKey":"fibu.auftrag.mail.intro","bundleName":"I18nResources","translation":"the order #{0} was created or changed.","translationDE":"der Auftrag #{0} wurde ge&auml;ndert bzw. angelegt.","usedInClasses":[],"usedInFiles":["./projectforge-business/src/main/resources/mail/orderChangeNotification.html"]},
{"i18nKey":"fibu.auftrag.nettoSumme","bundleName":"I18nResources","translation":"Net sum","translationDE":"Nettosumme","usedInClasses":["org.projectforge.business.fibu.AuftragsPositionDO","org.projectforge.business.fibu.ForecastOrderAnalysis","org.projectforge.business.fibu.OrderExport","org.projectforge.rest.fibu.AuftragPagesRest","org.projectforge.web.fibu.AuftragEditForm","org.projectforge.web.fibu.AuftragListPage"],"usedInFiles":["./projectforge-business/src/main/resources/mail/orderChangeNotification.html"]},
{"i18nKey":"fibu.auftrag.nettoSumme.weighted","bundleName":"I18nResources","translation":"Weighted net wum","translationDE":"Gewichtete Nettosumme","usedInClasses":["org.projectforge.business.fibu.ForecastOrderAnalysis"],"usedInFiles":[]},
{"i18nKey":"fibu.auftrag.nummer","bundleName":"I18nResources","translation":"Number","translationDE":"Nummer","usedInClasses":["org.projectforge.business.fibu.AuftragDO","org.projectforge.business.fibu.AuftragDao","org.projectforge.business.fibu.ForecastOrderAnalysis","org.projectforge.web.fibu.AuftragEditForm"],"usedInFiles":[]},
{"i18nKey":"fibu.auftrag.nummer.short","bundleName":"I18nResources","translation":"No","translationDE":"Nr.","usedInClasses":["org.projectforge.business.fibu.OrderExport","org.projectforge.web.fibu.AuftragListPage"],"usedInFiles":[]},
{"i18nKey":"fibu.auftrag.paymentschedule","bundleName":"I18nResources","translation":"Payment schedule","translationDE":"Zahlplan","usedInClasses":["org.projectforge.business.fibu.AuftragDO","org.projectforge.business.fibu.ForecastOrderAnalysis","org.projectforge.business.fibu.OrderExport","org.projectforge.web.fibu.AuftragEditForm"],"usedInFiles":[]},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ package org.projectforge.business.fibu
import de.micromata.merlin.excel.ExcelSheet
import de.micromata.merlin.excel.ExcelWorkbook
import mu.KotlinLogging
import org.jetbrains.kotlin.cfg.pseudocode.or
import org.projectforge.business.fibu.ForecastExportContext.*
import org.projectforge.business.fibu.orderbooksnapshots.OrderbookSnapshotsService
import org.projectforge.business.scripting.ScriptLogger
Expand Down Expand Up @@ -430,7 +429,7 @@ open class ForecastExport { // open needed by Wicket.
val posInfo = orderInfo?.getInfoPosition(pos.id)
val netSum = pos.netSum ?: BigDecimal.ZERO
val invoicedSum = posInfo?.invoicedSum ?: BigDecimal.ZERO
val forecastInfo = ForecastOrderPosInfo(order, pos, PFDay.fromOrNull(baseDate) ?: ctx.baseDate)
val forecastInfo = ForecastOrderPosInfo(order, pos)
forecastInfo.calculate()
sheet.setBigDecimalValue(row, ForecastCol.NETTOSUMME.header, netSum).cellStyle = ctx.currencyCellStyle
if (invoicedSum.compareTo(BigDecimal.ZERO) != 0) {
Expand Down Expand Up @@ -488,7 +487,7 @@ open class ForecastExport { // open needed by Wicket.
).cellStyle =
ctx.percentageCellStyle

sheet.setBigDecimalValue(row, ForecastCol.PROBABILITY_NETSUM.header, forecastInfo.probabilityNetSum).cellStyle =
sheet.setBigDecimalValue(row, ForecastCol.PROBABILITY_NETSUM.header, forecastInfo.weightedNetSum).cellStyle =
ctx.currencyCellStyle

sheet.setStringValue(row, ForecastCol.ANSPRECHPARTNER.header, order.contactPerson?.getFullname())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ class ForecastOrderAnalysis {
}?.sortedBy { it.orderPosNumber }
result?.forEach { fcPosInfo ->
val posInfo = fcPosInfo.orderPosInfo
val snapshotDate = orderInfo.snapshotDate
// Add all invoices:
auftragsRechnungCache.getRechnungsPosInfosByAuftragsPositionId(posInfo.id)?.forEach { invoicePosInfo ->
filterInvoices(posInfo, snapshotDate)?.forEach { invoicePosInfo ->
val invoiceInfo = invoicePosInfo.rechnungInfo
val date = invoiceInfo?.date
val netSum = invoicePosInfo.netSum
Expand Down Expand Up @@ -109,7 +110,15 @@ class ForecastOrderAnalysis {
return htmlExport(orderId = orderId, orderNumber = orderNumber, snapshotDate = snapshotDate).toByteArray()
}

fun htmlExport(orderId: Long? = null, orderNumber: Int? = null, snapshotDate: LocalDate? = null): String {
fun htmlExport(
orderId: Long? = null,
orderNumber: Int? = null,
snapshotDate: LocalDate? = null,
checkAccess: Boolean = true,
): String {
if (checkAccess) {
auftragDao.find(orderId) // Throws AccessException if not allowed.
}
val orderInfo =
loadOrder(orderId = orderId, orderNumber = orderNumber, snapshotDate)
?: return noAnalysis("Order with id $orderId or positions not found.")
Expand All @@ -120,9 +129,15 @@ class ForecastOrderAnalysis {
if (firstMonth == null || lastMonth == null) {
return noAnalysis("No order positions found for order with id ${orderId}.")
}
val title = "Forecast Order Analysis for order #${orderInfo.nummer}, ${PFDateTime.now().format()}"
val title = "Forecast Order Analysis for order #${orderInfo.nummer}"
val html = HtmlDocument(title)
html.add(Html.H1(title))
html.add(Html.H2().also {
orderInfo.snapshotDate?.let { snapshotDate ->
it.add(Html.Span("Snapshot date: $snapshotDate, ", style = "color: red; font-weight: bold;"))
}
it.add(Html.Span("created: ${PFDateTime.now().format()}"))
})
html.add(Html.Alert(Html.Alert.Type.INFO).also { div ->
div.add(Html.Text("Forecast values are shown in "))
div.add(Html.Span("blue,", style = "color: blue; font-weight: bold;"))
Expand Down Expand Up @@ -152,7 +167,13 @@ class ForecastOrderAnalysis {
translate("fibu.periodOfPerformance"),
"${orderInfo.periodOfPerformanceBegin.formatForUser()} - ${orderInfo.periodOfPerformanceEnd.formatForUser()}"
)
addRow(table, translate("fibu.probabilityOfOccurrence"), "${orderInfo.probabilityOfOccurrence} %")
addRow(table, translate("fibu.auftrag.nettoSumme"), orderInfo.netSum.formatCurrency(true))
addRow(
table,
translate("fibu.auftrag.nettoSumme.weighted"),
list.sumOf { it.weightedNetSum }.formatCurrency(true),
)
addRow(table, translate("fibu.invoiced"), orderInfo.invoicedSum, suppressZero = false)
addRow(table, translate("fibu.notYetInvoiced"), orderInfo.notYetInvoicedSum)
addRow(table, "Lost buget", lostBudget)
Expand Down Expand Up @@ -223,6 +244,11 @@ class ForecastOrderAnalysis {
addRow(table, translate("fibu.auftrag.position.art"), translate(posInfo.art))
addRow(table, translate("fibu.auftrag.position.paymenttype"), translate(posInfo.paymentType))
addRow(table, translate("fibu.auftrag.nettoSumme"), posInfo.netSum.formatCurrency(true))
addRow(
table,
translate("fibu.auftrag.nettoSumme.weighted"),
fcPosInfo.weightedNetSum.formatCurrency(true)
)
addRow(table, translate("fibu.invoiced"), posInfo.invoicedSum.formatCurrency(true))
addRow(table, translate("fibu.notYetInvoiced"), posInfo.notYetInvoiced)
addRow(table, translate("projectmanagement.personDays"), posInfo.personDays.formatForUser())
Expand All @@ -246,7 +272,7 @@ class ForecastOrderAnalysis {
tr.addTH(translate("fibu.rechnung.status.bezahlt"))
tr.addTH(translate("fibu.rechnung.text"), CssClass.EXPAND)
}
auftragsRechnungCache.getRechnungsPosInfosByAuftragsPositionId(posInfo.id)?.forEach { invoicePosInfo ->
filterInvoices(posInfo, orderInfo.snapshotDate)?.forEach { invoicePosInfo ->
val invoiceInfo = invoicePosInfo.rechnungInfo
table.addRow().also { row ->
row.addTD("${invoiceInfo?.nummer}#${invoicePosInfo.number}")
Expand Down Expand Up @@ -319,6 +345,15 @@ class ForecastOrderAnalysis {
return amount
}

private fun filterInvoices(posInfo: OrderPositionInfo, snapshotDate: LocalDate?): Collection<RechnungPosInfo>? {
val invoicePositions = auftragsRechnungCache.getRechnungsPosInfosByAuftragsPositionId(posInfo.id)
return if (snapshotDate == null) {
invoicePositions
} else {
invoicePositions?.filter { (it.rechnungInfo?.date ?: LocalDate.MAX) <= snapshotDate }
}
}

companion object {
private lateinit var instance: ForecastOrderAnalysis

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ class ForecastOrderPosInfo(
@JsonIgnore
val orderInfo: OrderInfo,
val orderPosInfo: OrderPositionInfo,
baseDate: PFDay = PFDay.now()
) {
class MonthEntry(
/** First day of month. */
Expand All @@ -69,8 +68,10 @@ class ForecastOrderPosInfo(

class PaymentEntryInfo(val scheduleDate: LocalDate, val amount: BigDecimal)

var baseMonth = baseDate.beginOfMonth
private set
/**
* Snapshot date of the order or beginning of the current month.
*/
val baseMonth = PFDay.fromOrNow(orderInfo.snapshotDate).beginOfMonth
var orderNumber = orderInfo.nummer
private set
var orderPosNumber = orderPosInfo.number
Expand All @@ -81,9 +82,9 @@ class ForecastOrderPosInfo(
private set
lateinit var probability: BigDecimal
private set
lateinit var probabilityNetSum: BigDecimal
lateinit var weightedNetSum: BigDecimal
private set
lateinit var probabilityNetSumWithoutPaymentSchedule: BigDecimal
lateinit var weightedNetSumWithoutPaymentSchedule: BigDecimal
private set
val invoicedSum = orderPosInfo.invoicedSum
lateinit var toBeInvoicedSum: BigDecimal
Expand Down Expand Up @@ -112,24 +113,24 @@ class ForecastOrderPosInfo(

fun calculate() {
probability = ForecastUtils.getProbabilityOfAccurence(orderInfo, orderPosInfo)
probabilityNetSum = ForecastUtils.computeProbabilityNetSum(orderInfo, orderPosInfo)
toBeInvoicedSum = if (probabilityNetSum > invoicedSum) probabilityNetSum - invoicedSum else BigDecimal.ZERO
weightedNetSum = ForecastUtils.computeProbabilityNetSum(orderInfo, orderPosInfo)
toBeInvoicedSum = if (weightedNetSum > invoicedSum) weightedNetSum - invoicedSum else BigDecimal.ZERO
paymentSchedules = ForecastUtils.getPaymentSchedule(orderInfo, orderPosInfo)
createMonths()
val sumPaymentSchedule = ForecastUtils.computeProbabilityPaymentSchedule(orderInfo, orderPosInfo)
// handle payment schedule
handlePaymentSchedules()
// compute diff, return if diff is empty
probabilityNetSumWithoutPaymentSchedule = probabilityNetSum - sumPaymentSchedule
if (probabilityNetSumWithoutPaymentSchedule.compareTo(BigDecimal.ZERO) != 0) {
weightedNetSumWithoutPaymentSchedule = weightedNetSum - sumPaymentSchedule
if (weightedNetSumWithoutPaymentSchedule.compareTo(BigDecimal.ZERO) != 0) {
// handle diff
when (orderPosInfo.paymentType) {
AuftragsPositionsPaymentType.FESTPREISPAKET -> { // fill rest at end of project time
val month = months.last()
val value = if (probabilityNetSumWithoutPaymentSchedule > toBeInvoicedSum) {
val value = if (weightedNetSumWithoutPaymentSchedule > toBeInvoicedSum) {
toBeInvoicedSum
} else {
probabilityNetSumWithoutPaymentSchedule
weightedNetSumWithoutPaymentSchedule
}
if (value.abs() > BigDecimal.ONE) { // Ignore rounding errors.
month.toBeInvoicedSum += value
Expand Down Expand Up @@ -215,7 +216,7 @@ class ForecastOrderPosInfo(
return
}
val monthCount = firstMonth.monthsBetween(lastMonth) + 1 // Jan-Jan -> 1, Jan-Feb -> 2, ...
val partlyNetSum = probabilityNetSumWithoutPaymentSchedule.divide(
val partlyNetSum = weightedNetSumWithoutPaymentSchedule.divide(
BigDecimal.valueOf(monthCount),
RoundingMode.HALF_UP
)
Expand All @@ -238,9 +239,9 @@ class ForecastOrderPosInfo(
if (futureInvoicesAmountRest > partlyNetSum) {
monthEntry.lostBudget = futureInvoicesAmountRest - partlyNetSum
monthEntry.lostBudgetPercent =
if (probabilityNetSum > BigDecimal.ZERO) {
if (weightedNetSum > BigDecimal.ZERO) {
(monthEntry.lostBudget * BigDecimal(100)).divide(
probabilityNetSum,
weightedNetSum,
RoundingMode.HALF_UP
).toInt()
} else {
Expand Down
Loading

0 comments on commit 7a15cec

Please sign in to comment.