diff --git a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt index 242ce9e..3e1c7d1 100644 --- a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt @@ -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): 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): 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, item: Map) { + 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> { @@ -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>() 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, 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, 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>, + staffSalaryDataList: List + ) { + staffInfoList.forEachIndexed { index, staffInfo -> + val staffId = staffInfo["staffId"] as String + val staffSalaryData = staffSalaryDataList.find { it.staffId == staffId } - val financialYears = (staff["financialYears"] as List).map { year -> - val relevantSalaryData = staffSalaryData?.salaryData?.firstOrNull() { - year.isYearMonthInFinancialYear(YearMonth.from(it.financialYear)) + if (staffSalaryData != null) { + val updatedFinancialYears = (staffInfo["financialYears"] as List).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): 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 findPreviousValue(salaryDataList: List, 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 { + require(periodStartMonth in 1..12) { "periodStartMonth must be between 1 and 12" } + + val financialPeriods = mutableListOf() + + 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> = 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++); }