| @@ -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++); | |||
| } | |||