Skip to content

Commit

Permalink
Merge pull request #290 from micromata/Release-8.1-SNAPSHOT
Browse files Browse the repository at this point in the history
ForecastExport: filtering by unit, customer and projects is now suppo…
  • Loading branch information
kreinhard authored Feb 6, 2025
2 parents 5561d98 + f983519 commit 06b09be
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ private val log = KotlinLogging.logger {}
*/
@Service
open class ForecastExport { // open needed by Wicket.

@Autowired
private lateinit var accessChecker: AccessChecker

Expand All @@ -88,12 +89,20 @@ open class ForecastExport { // open needed by Wicket.
@Autowired
private lateinit var applicationContext: ApplicationContext

/**
* Export the forecast sheet.
* @param origFilter The filter for the orders to export.
* @param planningDate If given, the monthly forecast will be calculated with the specified date and inserted as plan data.
* @param snapshotDate Today (null) or, the day of the snapshot, if the orderList is loaded from order book snapshots.
* @param fillUnitCol The function to get the unit of the order to show in the unit column.
*/
@JvmOverloads
@Throws(IOException::class)
open fun xlsExport(
origFilter: AuftragFilter,
planningDate: LocalDate? = null,
snapshotDate: LocalDate? = null
snapshotDate: LocalDate? = null,
fillUnitCol: ((orderInfo: OrderInfo) -> String)? = null,
): ByteArray? {
val startDateParam = origFilter.periodOfPerformanceStartDate
val startDate = if (startDateParam != null) PFDay.from(startDateParam).beginOfMonth else PFDay.now().beginOfYear
Expand Down Expand Up @@ -145,7 +154,8 @@ open class ForecastExport { // open needed by Wicket.
snapshotDate = closestSnapshotDate,
showAll = showAll,
auftragFilter = filter,
scriptLogger = scriptLogger
scriptLogger = scriptLogger,
fillUnitCol = fillUnitCol,
)
} catch (ex: Exception) {
log.error(ex) { "Error exporting forecast: $ex" }
Expand Down Expand Up @@ -216,6 +226,7 @@ open class ForecastExport { // open needed by Wicket.
snapshotDate: LocalDate?,
auftragFilter: AuftragFilter,
scriptLogger: ScriptLogger?,
fillUnitCol: ((orderInfo: OrderInfo) -> String)?,
): ByteArray? {
if (orderList.isEmpty()) {
val msg = "No orders found for export."
Expand Down Expand Up @@ -280,6 +291,7 @@ open class ForecastExport { // open needed by Wicket.
baseDate = PFDay.fromOrNow(snapshotDate),
planningDate = planningDate,
snapshot = snapshotDate != null,
fillUnitCol = fillUnitCol,
)
ctx.showAll = showAll

Expand All @@ -290,7 +302,13 @@ open class ForecastExport { // open needed by Wicket.
log.debug { "info sheet: $infoSheet" }

val orderPositionsFound =
fillOrderPositions(orderList, ctx, ctx.forecastSheet, baseDate = snapshotDate, useAuftragsCache)
fillOrderPositions(
orderList,
ctx,
ctx.forecastSheet,
baseDate = snapshotDate,
useAuftragsCache,
)
if (!orderPositionsFound) {
val msg = "No orders positions found for export."
scriptLogger?.info { msg } ?: log.info { msg } // scriptLogger does also log.info
Expand All @@ -303,10 +321,14 @@ open class ForecastExport { // open needed by Wicket.
replaceMonthDatesInHeaderRow(invoicesSheet, startDate)
replaceMonthDatesInHeaderRow(invoicesPrevYearSheet, prevYearBaseDate)
replaceMonthDatesInHeaderRow(planningInvoicesSheet, startDate)
ExcelUtils.setAutoFilter(forecastSheet, FORECAST_HEAD_ROW, 0, FORECAST_NUMBER_OF_COLS)
if (!ctx.hasUnitColEntries) {
ExcelUtils.setColumnHidden(forecastSheet, ForecastCol.UNIT.header, true)
ExcelUtils.setColumnHidden(planningSheet, ForecastCol.UNIT.header, true)
}
ExcelUtils.setAutoFilter(forecastSheet, FORECAST_HEAD_ROW, 0, FORECAST_NUMBER_OF_COLS_AUTOFILTER)
invoicesSheet.setAutoFilter()
invoicesPrevYearSheet.setAutoFilter()
ExcelUtils.setAutoFilter(planningSheet, FORECAST_HEAD_ROW, 0, FORECAST_NUMBER_OF_COLS)
ExcelUtils.setAutoFilter(planningSheet, FORECAST_HEAD_ROW, 0, FORECAST_NUMBER_OF_COLS_AUTOFILTER)
planningInvoicesSheet.setAutoFilter()

fillPlanningForecast(planningDate, auftragFilter, ctx)
Expand Down Expand Up @@ -372,10 +394,16 @@ open class ForecastExport { // open needed by Wicket.
return orderPositionFound
}

private fun fillPlanningForecast(planningDate: LocalDate?, auftragFilter: AuftragFilter, ctx: Context) {
private fun fillPlanningForecast(planningDate: LocalDate?, auftragFilter: AuftragFilter, ctx: Context, ) {
planningDate ?: return
val orderList = readSnapshot(planningDate, auftragFilter) ?: return
fillOrderPositions(orderList, ctx, ctx.planningSheet, baseDate = planningDate, useAuftragsCache = false)
val orderList = readSnapshot(planningDate, auftragFilter)
fillOrderPositions(
orderList,
ctx,
ctx.planningSheet,
baseDate = planningDate,
useAuftragsCache = false,
)
}

private fun replaceMonthDatesInHeaderRow(
Expand Down Expand Up @@ -413,8 +441,34 @@ open class ForecastExport { // open needed by Wicket.
baseDate: LocalDate?,
useAuftragsCache: Boolean,
) {
val isPlanningSheet = ctx.planningSheet == sheet
sheet.setIntValue(row, ForecastCol.ORDER_NR.header, order.nummer)
sheet.setStringValue(row, ForecastCol.POS_NR.header, "#${pos.number}")
ExcelUtils.setLongValue(sheet, row, ForecastCol.PROJECT_ID.header, order.projektId)
val excelRow = row + 1 // Excel row number for formulas, 1-based.
if (isPlanningSheet) {
// Planning sheet: visible column is true, if the project is visible in the forecast sheet.
val visibleProjectIdCol =
ctx.forecastSheet.getColumnDef(ForecastCol.VISIBLE_PROJECT_ID.header)?.columnNumberAsLetters
val projectIdCol = ctx.forecastSheet.getColumnDef(ForecastCol.PROJECT_ID.header)?.columnNumberAsLetters
ExcelUtils.setCellFormula(
sheet,
row,
ForecastCol.VISIBLE.header,
"COUNTIF(Forecast_Data!$visibleProjectIdCol$11:$visibleProjectIdCol$100000, $projectIdCol$excelRow) > 0"
)
} else {
// Visible cell is 1, if row is visible (by filter), otherwise, 0.
ExcelUtils.setCellFormula(sheet, row, ForecastCol.VISIBLE.header, "SUBTOTAL(3, A$excelRow)")
val visibleCol = ctx.forecastSheet.getColumnDef(ForecastCol.VISIBLE.header)?.columnNumberAsLetters
val projectIdCol = ctx.forecastSheet.getColumnDef(ForecastCol.PROJECT_ID.header)?.columnNumberAsLetters
ExcelUtils.setCellFormula(
sheet,
row,
ForecastCol.VISIBLE_PROJECT_ID.header,
"IF($visibleCol$excelRow=1, $projectIdCol$excelRow, \"\")"
)
}
order.angebotsDatum?.let {
sheet.setDateValue(row, ForecastCol.DATE_OF_OFFER.header, PFDay(it).localDate, ctx.excelDateFormat)
}
Expand All @@ -424,6 +478,12 @@ open class ForecastExport { // open needed by Wicket.
order.entscheidungsDatum?.let {
sheet.setDateValue(row, ForecastCol.DATE_OF_DECISION.header, PFDay(it).localDate, ctx.excelDateFormat)
}
ctx.fillUnitCol?.invoke(order)?.let {
if (it.isNotBlank()) {
ctx.hasUnitColEntries = true
}
sheet.setStringValue(row, ForecastCol.UNIT.header, it)
}
sheet.setStringValue(row, ForecastCol.CUSTOMER.header, order.kundeAsString)
sheet.setStringValue(row, ForecastCol.PROJECT.header, order.projektAsString)
sheet.setStringValue(row, ForecastCol.TITEL.header, order.titel)
Expand Down Expand Up @@ -595,7 +655,10 @@ open class ForecastExport { // open needed by Wicket.

private const val FORECAST_HEAD_ROW = 9
private const val FORECAST_FISRT_ORDER_ROW = FORECAST_HEAD_ROW + 1
private const val FORECAST_NUMBER_OF_COLS = 45
private const val FORECAST_NUMBER_OF_COLS_AUTOFILTER = 47

// Two more technical cols: ProjectID, visible and visibleID
private const val FORECAST_NUMBER_OF_COLS = FORECAST_NUMBER_OF_COLS_AUTOFILTER + 3

fun formatMonthHeader(date: PFDay): String {
return date.format(formatter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ import org.projectforge.business.excel.ExcelDateFormats
import org.projectforge.business.excel.XlsContentProvider
import org.projectforge.common.DateFormatType
import org.projectforge.excel.ExcelUtils
import org.projectforge.framework.i18n.translate
import org.projectforge.framework.i18n.translateMsg
import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext
import org.projectforge.framework.time.DateFormats
import org.projectforge.framework.time.PFDay
Expand All @@ -54,6 +52,7 @@ internal class ForecastExportContext(
val baseDate: PFDay = PFDay.now(),
val planningDate: LocalDate? = null,
val snapshot: Boolean = false,
val fillUnitCol: ((orderInfo: OrderInfo) -> String)? = null,
) {
enum class Sheet(val title: String) {
FORECAST("Forecast_Data"),
Expand All @@ -66,7 +65,7 @@ internal class ForecastExportContext(

enum class ForecastCol(val header: String) {
ORDER_NR("Nr."), POS_NR("Pos."), DATE_OF_OFFER("Angebotsdatum"), DATE("Erfassungsdatum"),
DATE_OF_DECISION("Entscheidungsdatum"), CUSTOMER("Kunde"), PROJECT("Projekt"),
DATE_OF_DECISION("Entscheidungsdatum"), UNIT("Unit"), CUSTOMER("Kunde"), PROJECT("Projekt"),
TITEL("Titel"), POS_TITLE("Pos.-Titel"), ART("Art"), ABRECHNUNGSART("Abrechnungsart"),
AUFTRAG_STATUS("Auftrag Status"), POSITION_STATUS("Position Status"),
PT("PT"), NETTOSUMME("Nettosumme"), FAKTURIERT("fakturiert"),
Expand All @@ -75,13 +74,15 @@ internal class ForecastExportContext(
EINTRITTSWAHRSCHEINLICHKEIT("Eintrittswahrsch. in %"), ANSPRECHPARTNER("Ansprechpartner"),
STRUKTUR_ELEMENT("Strukturelement"), BEMERKUNG("Bemerkung"), PROBABILITY_NETSUM("gewichtete Nettosumme"),
ANZAHL_MONATE("Anzahl Monate"), FORECAST_TYPE("Forecasttyp"), PAYMENT_SCHEDULE("Zahlplan"),
REMAINING("Rest"), DIFFERENCE("Abweichung"), WARNING("Warnung")
REMAINING("Rest"), DIFFERENCE("Abweichung"), WARNING("Warnung"),
PROJECT_ID("ProjectID"), VISIBLE("visible"), VISIBLE_PROJECT_ID("visibleID")
}

enum class InvoicesCol(val header: String) {
INVOICE_NR("Nr."), POS_NR("Pos."), DATE("Datum"), CUSTOMER("Kunde"), PROJECT("Projekt"),
SUBJECT("Betreff"), POS_TEXT("Positionstext"), DATE_OF_PAYMENT("Bezahldatum"),
LEISTUNGSZEITRAUM("Leistungszeitraum"), ORDER("Auftrag"), NETSUM("Netto")
LEISTUNGSZEITRAUM("Leistungszeitraum"), ORDER("Auftrag"), NETSUM("Netto"),
PROJECT_ID("ProjectID"), VISIBLE("visible")
}

enum class MonthCol(val header: String) {
Expand Down Expand Up @@ -144,4 +145,6 @@ internal class ForecastExportContext(
false // showAll is true, if no filter is given and for financial and controlling staff only.
val orderPositionMap = mutableMapOf<Long, OrderPositionInfo>()
val orderMapByPositionId = mutableMapOf<Long, OrderInfo>()

var hasUnitColEntries = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ package org.projectforge.business.fibu

import de.micromata.merlin.excel.ExcelSheet
import mu.KotlinLogging
import org.projectforge.business.fibu.ForecastExportContext.ForecastCol
import org.projectforge.business.fibu.kost.ProjektCache
import org.projectforge.excel.ExcelUtils
import org.projectforge.framework.time.PFDay
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
Expand Down Expand Up @@ -99,7 +101,16 @@ internal class ForecastExportInvoices { // open needed by Wicket.
}
} else {
monthIndex += 12
insertIntoSheet(ctx, ctx.invoicesPrevYearSheet, invoice, pos, order, orderPosId, firstMonthCol, monthIndex)
insertIntoSheet(
ctx,
ctx.invoicesPrevYearSheet,
invoice,
pos,
order,
orderPosId,
firstMonthCol,
monthIndex
)
}
}
}
Expand All @@ -110,8 +121,24 @@ internal class ForecastExportInvoices { // open needed by Wicket.
order: OrderInfo?, orderPosId: Long?, firstMonthCol: Int, monthIndex: Int,
) {
val rowNumber = sheet.createRow().rowNum
val excelRowNumber = rowNumber + 1 // Excel row numbers start with 1.
sheet.setIntValue(rowNumber, ForecastExportContext.InvoicesCol.INVOICE_NR.header, invoice.nummer)
ExcelUtils.setLongValue(
sheet,
rowNumber,
ForecastExportContext.InvoicesCol.PROJECT_ID.header,
invoice.projekt?.id
)
sheet.setStringValue(rowNumber, ForecastExportContext.InvoicesCol.POS_NR.header, "#${pos.number}")
val visibleProjectIdCol =
ctx.forecastSheet.getColumnDef(ForecastCol.VISIBLE_PROJECT_ID.header)?.columnNumberAsLetters
val projectIdCol = ctx.invoicesSheet.getColumnDef(ForecastExportContext.InvoicesCol.PROJECT_ID.header)?.columnNumberAsLetters
ExcelUtils.setCellFormula(
sheet,
rowNumber,
ForecastExportContext.InvoicesCol.VISIBLE.header,
"COUNTIF(Forecast_Data!$visibleProjectIdCol$11:$visibleProjectIdCol$100000, $projectIdCol$excelRowNumber) > 0"
)
sheet.setDateValue(
rowNumber,
ForecastExportContext.InvoicesCol.DATE.header,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ internal class OrderConverterService {
}
info.calculateInvoicedSum(info.infoPositions)
info.kundeAsString = kundeAsString
info.projektId = projekt?.id
info.projektAsString = projekt?.name
info.updatePaymentScheduleEntries(paymentSchedules)
}
Expand Down
Loading

0 comments on commit 06b09be

Please sign in to comment.