Browse Source

Update PNL Report to calculate base on financial year

tags/Baseline_30082024_BACKEND_UAT
MSI\2Fi 1 year ago
parent
commit
e6665377ed
1 changed files with 203 additions and 106 deletions
  1. +203
    -106
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt

+ 203
- 106
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt View File

@@ -1,12 +1,9 @@
package com.ffii.tsms.modules.report.service

import com.ffii.core.support.JdbcDao
import com.ffii.core.utils.ExcelUtils
import com.ffii.tsms.modules.data.entity.Customer
import com.ffii.tsms.modules.data.entity.Grade
import com.ffii.tsms.modules.data.entity.Salary
import com.ffii.tsms.modules.data.entity.SalaryEffective
import com.ffii.tsms.modules.data.entity.SalaryEffectiveRepository
import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.data.entity.Team
import com.ffii.tsms.modules.data.service.SalaryEffectiveService
@@ -25,7 +22,6 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.apache.poi.ss.usermodel.FormulaEvaluator
import org.apache.poi.ss.util.CellReference
import org.apache.poi.ss.util.RegionUtil
import org.apache.poi.xssf.usermodel.XSSFCellStyle
import org.apache.poi.xssf.usermodel.XSSFColor
import org.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Service
@@ -33,7 +29,6 @@ import java.io.ByteArrayOutputStream
import java.io.IOException
import java.math.BigDecimal
import java.time.LocalDate
import java.time.Year
import java.time.YearMonth
import java.time.format.DateTimeFormatter
import java.time.ZoneId
@@ -41,6 +36,7 @@ import java.time.format.DateTimeParseException
import java.time.temporal.ChronoUnit
import java.util.*
import java.awt.Color
import java.time.Year
import kotlin.collections.ArrayList


@@ -2144,35 +2140,73 @@ open class ReportService(
return jdbcDao.queryForList(sql.toString(), args)
}

fun getSalaryForMonth(recordDate: String, staffId: String, staffSalaryLists: List<SalaryEffectiveService.StaffSalaryData>): Double? {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM")
val monthDate = YearMonth.parse(recordDate, formatter)

var hourlyRate: Double? = null
// var financialYear = monthDate

val staffSalaryData = staffSalaryLists.find { it.staffId == staffId }
staffSalaryData?.salaryData?.forEach { salaryData ->
val financialYearStart = YearMonth.of(salaryData.financialYear.year, 10)
val financialYearEnd = YearMonth.of(salaryData.financialYear.year + 1, 9)

if ((monthDate.isAfter(financialYearStart) || monthDate.equals(financialYearStart)) && (monthDate.isBefore(financialYearEnd) || monthDate.equals(financialYearEnd))) {
// if(staffId == "B627"){
// println("----------------- Start -------------------------")
// println("financialYearStart: ${financialYearStart}")
// println("financialYearEnd: ${financialYearEnd}")
// println("monthDate: ${monthDate}")
// println("Result: ${(monthDate.isAfter(financialYearStart) || monthDate.equals(financialYearStart)) && monthDate.isBefore(financialYearEnd)}")
// println("hourlyRate: ${salaryData.hourlyRate.toDouble()}")
// println("---------------------- End -----------------------------")
// fun getSalaryForMonth(recordDate: String, staffId: String, staffSalaryLists: List<SalaryEffectiveService.StaffSalaryData>): Double? {
// val formatter = DateTimeFormatter.ofPattern("yyyy-MM")
// val monthDate = YearMonth.parse(recordDate, formatter)
//
// var hourlyRate: Double? = null
// var prevHourlyRate: Double? = null
//// var financialYear = monthDate
//
// val staffSalaryData = staffSalaryLists.find { it.staffId == staffId }?.salaryData
// staffSalaryData?.forEachIndexed { index, salaryData ->
// val financialYearStart = YearMonth.of(salaryData.financialYear.year, 10)
// val financialYearEnd = YearMonth.of(salaryData.financialYear.year + 1, 9)
//
// if ((monthDate.isAfter(financialYearStart) || monthDate.equals(financialYearStart))
// && (monthDate.isBefore(financialYearEnd) || monthDate.equals(financialYearEnd))) {
//// if(staffId == "B627"){
//// println("----------------- Start -------------------------")
//// println("financialYearStart: ${financialYearStart}")
//// println("financialYearEnd: ${financialYearEnd}")
//// println("monthDate: ${monthDate}")
//// println("Result: ${(monthDate.isAfter(financialYearStart) || monthDate.equals(financialYearStart)) && monthDate.isBefore(financialYearEnd)}")
//// println("hourlyRate: ${salaryData.hourlyRate.toDouble()}")
//// println("---------------------- End -----------------------------")
//// }
// hourlyRate = salaryData.hourlyRate.toDouble()
//
//// financialYear = YearMonth.parse(salaryData.financialYear.toString())
// return@forEachIndexed
// }else{
// if(index > 0){
// prevHourlyRate = staffSalaryData[index - 1].hourlyRate.toDouble()
// return@forEachIndexed
// }
hourlyRate = salaryData.hourlyRate.toDouble()
// financialYear = YearMonth.parse(salaryData.financialYear.toString())
return@forEach
// }
// }
//
// return hourlyRate ?: prevHourlyRate
// }

private fun updateInfo(info: MutableMap<String, Any?>, item: Map<String, Any>) {
val simpleFields = listOf("teamLead", "client", "code", "description")
val conditionalFields = mapOf(
"paidAmount" to "sumPaidAmount",
"subsidiary" to "subsidiary",
"expectedTotalFee" to "expectedTotalFee"
)

// Update simple fields
simpleFields.forEach { field ->
if (info[field] != item[field]) {
info[field] = item.getValue(field)
}
}

// Update conditional fields
if (info["code"] == item["code"]) {
conditionalFields.forEach { (infoField, itemField) ->
if (infoField !in info) {
info[infoField] = item.getValue(itemField)
}
}
}

return hourlyRate
// Update manhourExpenditure separately due to different key names
if (info["manhourExpenditure"] != item["sumManhourExpenditure"]) {
info["manhourExpenditure"] = item.getValue("sumManhourExpenditure")
}
}

open fun getManhoursSpent(projectId: Long, startMonth: String, endMonth: String): List<Map<String, Any>> {
@@ -2263,39 +2297,16 @@ open class ReportService(
"code" to projectsCode["code"],
"description" to projectsCode["description"]
)
val financialYears = getFinancialYearDates(queryStartMonth, queryEndMonth, 10)
val financialYears = getHalfYearFinancialPeriods(queryStartMonth, queryEndMonth, 10)

val staffInfoList = mutableListOf<Map<String, Any>>()

val staffSalaryLists = salaryEffectiveService.getStaffSalaryDataByProjectId(projectId)

for (item in manHoursSpent) {
if (info["teamLead"] != item.getValue("teamLead")) {
info["teamLead"] = item.getValue("teamLead")
}
if (info["client"] != item.getValue("client")) {
info["client"] = item.getValue("client")
}
if (info["code"] != item.getValue("code")) {
info["code"] = item.getValue("code")
}
if (info["code"] == item["code"] && "paidAmount" !in info) {
info["paidAmount"] = item.getValue("sumPaidAmount")
}
if (info["code"] == item["code"] && "subsidiary" !in info) {
info["subsidiary"] = item.getValue("subsidiary")
}
if (info["code"] == item["code"] && "expectedTotalFee" !in info) {
info["expectedTotalFee"] = item.getValue("expectedTotalFee")
}
if (info["manhourExpenditure"] != item.getValue("sumManhourExpenditure")) {
info["manhourExpenditure"] = item.getValue("sumManhourExpenditure")
}
if (info["description"] != item.getValue("description")) {
info["description"] = item.getValue("description")
}
updateInfo(info, item)

val hourlyRate = getSalaryForMonth(item.getValue("recordDate") as String, item.getValue("staffId") as String, staffSalaryLists) ?: (item.getValue("hourlyRate") as BigDecimal).toDouble()
val hourlyRate = getSalaryForMonth(item.getValue("recordDate") as String, item.getValue("staffId") as String, staffSalaryLists, queryStartMonth, queryEndMonth) ?: (item.getValue("hourlyRate") as BigDecimal).toDouble()

if (!staffInfoList.any { it["staffId"] == item["staffId"] && it["name"] == item["name"] }) {

@@ -2358,51 +2369,97 @@ open class ReportService(
staffInfoList[updatedIndex] = updatedMap
}
}
// println("staffInfoList----------------- ${staffInfoList.filter { it["staffId"] == "B627" }}")
// println("info----------------- $info")

staffInfoList.forEachIndexed { index, staff ->
val staffId = staff["staffId"]
val staffSalaryData = staffSalaryLists.find { it.staffId == staffId }
// println("----------------------- $staffId ---------------------------")
// println(staff)
// println(staffSalaryData)
updateStaffFinancialYears(staffInfoList, staffSalaryLists)

tempList.add(mapOf("info" to info))
tempList.add(mapOf("staffInfoList" to staffInfoList))

return tempList
}

fun getSalaryForMonth(recordDate: String, staffId: String, staffSalaryLists: List<SalaryEffectiveService.StaffSalaryData>, start: YearMonth, end: YearMonth): Double? {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM")
val monthDate = YearMonth.parse(recordDate, formatter)

val staffSalaryData = staffSalaryLists.find { it.staffId == staffId }?.salaryData ?: return null

return findSalaryForMonth(staffSalaryData, monthDate, start, end)
}

private fun findSalaryForMonth(salaryDataList: List<SalaryEffectiveService.SalaryData>, targetMonth: YearMonth, start: YearMonth, end: YearMonth): Double? {
if (salaryDataList.isEmpty()) return null

val periodStartMonth = 10

val financialPeriods = getHalfYearFinancialPeriods(start, end, periodStartMonth)

// First, try to find a salary in the current financial period
val currentFinancialPeriod = financialPeriods.find { targetMonth in it.start..it.end }
val currentSalary = currentFinancialPeriod?.let { period ->
salaryDataList.find { salaryData ->
val salaryYearMonth = YearMonth.from(salaryData.financialYear)
salaryYearMonth in period.start..period.end
}?.hourlyRate?.toDouble()
}

if (currentSalary != null) {
return currentSalary
}

// If not found, find the most recent previous salary
return findPreviousValue(salaryDataList, targetMonth) { it.hourlyRate.toDouble() }
}

fun updateStaffFinancialYears(
staffInfoList: MutableList<Map<String, Any>>,
staffSalaryDataList: List<SalaryEffectiveService.StaffSalaryData>
) {
staffInfoList.forEachIndexed { index, staffInfo ->
val staffId = staffInfo["staffId"] as String
val staffSalaryData = staffSalaryDataList.find { it.staffId == staffId }

val financialYears = (staff["financialYears"] as List<FinancialYear>).map { year ->
val relevantSalaryData = staffSalaryData?.salaryData?.firstOrNull() {
year.isYearMonthInFinancialYear(YearMonth.from(it.financialYear))
if (staffSalaryData != null) {
val updatedFinancialYears = (staffInfo["financialYears"] as List<FinancialYear>).map { financialYear ->
updateFinancialYear(financialYear, staffSalaryData.salaryData)
}

val updatedStaffInfo = staffInfo.toMutableMap().apply {
this["financialYears"] = updatedFinancialYears
}
// println("it.financialYear: ${relevantSalaryData?.financialYear}")
// println("year: ${year.start}- ${year.end}")
// println("it.salaryPoint: ${relevantSalaryData?.salaryPoint}")
// println("it.hourlyRate: ${relevantSalaryData?.hourlyRate?.toDouble()}")
// println("it.staffId: ${relevantSalaryData?.staffId}")

// Create a new FinancialYear object with the updated salaryPoint
year.copy(
start = year.start,
end = year.end,
salaryPoint = relevantSalaryData?.salaryPoint ?: (staff["salaryPoint"] as Long).toInt(),
hourlyRate = relevantSalaryData?.hourlyRate?.toDouble() ?: (staff["hourlyRate"] as BigDecimal).toDouble()
)
}

// Create a new map with the updated financialYears
val updatedStaff = staff.toMutableMap().apply {
this["financialYears"] = financialYears
staffInfoList[index] = updatedStaffInfo
}
}
}

// Update the staffInfoList with the new staff map
staffInfoList[index] = updatedStaff
fun updateFinancialYear(financialYear: FinancialYear, salaryDataList: List<SalaryEffectiveService.SalaryData>): FinancialYear {
val relevantSalaryData = salaryDataList.find { salaryData ->
val salaryYearMonth = YearMonth.from(salaryData.financialYear)
salaryYearMonth >= financialYear.start && salaryYearMonth <= financialYear.end
}

tempList.add(mapOf("info" to info))
tempList.add(mapOf("staffInfoList" to staffInfoList))
if (relevantSalaryData != null) {
return financialYear.copy(
hourlyRate = relevantSalaryData.hourlyRate.toDouble(),
salaryPoint = relevantSalaryData.salaryPoint
)
} else {
val previousHourlyRate = findPreviousValue(salaryDataList, financialYear.start) { it.hourlyRate }
val previousSalaryPoint = findPreviousValue(salaryDataList, financialYear.start) { it.salaryPoint }

// println("Only Staff Info List --------------------- ${tempList.first() { it.containsKey("staffInfoList") }["staffInfoList"]}")
// println("tempList----------------- ${tempList.filter { it["staffId"] == "B627" }}")
return financialYear.copy(
hourlyRate = previousHourlyRate?.toDouble() ?: financialYear.hourlyRate,
salaryPoint = previousSalaryPoint ?: financialYear.salaryPoint
)
}
}

return tempList
fun <T> findPreviousValue(salaryDataList: List<SalaryEffectiveService.SalaryData>, currentStart: YearMonth, valueSelector: (SalaryEffectiveService.SalaryData) -> T): T? {
return salaryDataList
.filter { YearMonth.from(it.financialYear) < currentStart }
.maxByOrNull { it.financialYear }
?.let(valueSelector)
}

fun genPandLReport(projectId: Long, startMonth: String, endMonth: String): ByteArray {
@@ -2431,6 +2488,51 @@ open class ReportService(
}
}

// 6 months as a period
fun getHalfYearFinancialPeriods(
startDate: YearMonth,
endDate: YearMonth,
periodStartMonth: Int
): List<FinancialYear> {
require(periodStartMonth in 1..12) { "periodStartMonth must be between 1 and 12" }

val financialPeriods = mutableListOf<FinancialYear>()

var currentDate = startDate

// Adjust the start date to the beginning of the current or previous period
currentDate = if (currentDate.monthValue < periodStartMonth) {
YearMonth.of(currentDate.year - 1, periodStartMonth)
} else {
YearMonth.of(currentDate.year, periodStartMonth)
}

while (currentDate <= endDate) {
val startYear = currentDate.year
val startMonth = currentDate.monthValue

val endDate = currentDate.plusMonths(5)
val endYear = endDate.year
val endMonth = endDate.monthValue

val financialPeriod = FinancialYear(
start = YearMonth.of(startYear, startMonth),
end = YearMonth.of(endYear, endMonth),
hourlyRate = 0.0,
salaryPoint = 0
)

financialPeriods.add(financialPeriod)

// Move to the next half-year period
currentDate = currentDate.plusMonths(6)
}

// Remove any periods that start after the specified end date
return financialPeriods.filter { it.start <= endDate }
}

// 12 months as a period
fun getFinancialYearDates(
startDate: YearMonth,
endDate: YearMonth,
@@ -2478,6 +2580,15 @@ open class ReportService(

return financialYearDates
}
private fun getOrCreateRow(sheet: Sheet, startRow: Int): Row{
val row: Row = sheet.getRow(startRow) ?: sheet.createRow(startRow)
return row
}

private fun getOrCreateCell(row: Row, columnIndex: Int): Cell{
val cell: Cell = row.getCell(columnIndex) ?: row.createCell(columnIndex)
return cell
}

fun createPandLReportWorkbook(
templatePath: String,
@@ -2512,7 +2623,7 @@ open class ReportService(
val convertEndMonth = YearMonth.of(endDate.year, endDate.month)

val monthRange: MutableList<Map<String, Any>> = ArrayList()
val financialYears = getFinancialYearDates(queryStartMonth, queryEndMonth, 10)
val financialYears = getHalfYearFinancialPeriods(queryStartMonth, queryEndMonth, 10)

var currentDate = startDate
if (currentDate == endDate) {
@@ -2674,20 +2785,6 @@ open class ReportService(
}
}

// val salaryPointCell = row.getCell(2) ?: row.createCell(2)
// salaryPointCell.apply {
// setCellValue((staff.getValue("salaryPoint") as Long).toDouble())
// }
// CellUtil.setAlignment(salaryPointCell, HorizontalAlignment.CENTER);
//
// sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 3, 4))
// val hourlyRateCell = row.getCell(3) ?: row.createCell(3)
// hourlyRateCell.apply {
// setCellValue((staff.getValue("hourlyRate") as Double))
// }
// CellUtil.setAlignment(hourlyRateCell, HorizontalAlignment.CENTER);
// CellUtil.setVerticalAlignment(hourlyRateCell, VerticalAlignment.CENTER);

sheet.setRowBreak(rowNum++);
}



Loading…
Cancel
Save