diff --git a/src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt b/src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt index efda40a..5b9cf60 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt @@ -6,10 +6,13 @@ 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.SalaryRepository +import com.ffii.tsms.modules.data.entity.Staff import com.ffii.tsms.modules.data.entity.StaffRepository import org.springframework.stereotype.Service import java.math.BigDecimal import java.time.LocalDate +import java.time.LocalDateTime +import java.time.temporal.ChronoUnit @Service open class SalaryEffectiveService( @@ -90,4 +93,41 @@ open class SalaryEffectiveService( // println(salaryEffectiveLists) return salaryEffectiveLists } + + data class MonthlyStaffSalaryData( + val staff: Staff, + val date: LocalDate, + val hourlyRate: Double, + ) + + open fun getMonthlyStaffSalaryData(startDate: LocalDate, endDate: LocalDate): List { + val salaryEffective = salaryEffectiveRepository.findAll() + .groupBy { Pair(it.date.year, it.date.month) } + .map { (_, se) -> se.maxByOrNull { it.date }!!} + salaryEffective.sortedWith(compareBy({it.staff.staffId}, {it.date})) + + val staffs = staffRepository.findAll().sortedBy { it.staffId }.filter { it.deleted == false } + + val result = mutableListOf() + val dateList = (0..ChronoUnit.MONTHS.between(startDate, endDate)).map { startDate.withDayOfMonth(1).plusMonths(it) } + + staffs.forEach { staff -> + dateList.forEach{ date -> + val staffSalaryEffective = salaryEffective.filter { it.staff.staffId.equals(staff.staffId) } + var currentHourlyRate = staffSalaryEffective.filter { date.isEqual(it.date.withDayOfMonth(1)) || date.isAfter(it.date.withDayOfMonth(1)) }.minByOrNull { it.date } + + if (currentHourlyRate == null) { + currentHourlyRate = staffSalaryEffective.minByOrNull { it.date } + } + + result += MonthlyStaffSalaryData( + staff = staff, + date = date, + hourlyRate = currentHourlyRate?.salary?.hourlyRate?.toDouble() ?: staff?.salary?.hourlyRate?.toDouble() ?: 0.0 + ) + } + } + + return result + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/data/web/SalaryEffectiveController.kt b/src/main/java/com/ffii/tsms/modules/data/web/SalaryEffectiveController.kt index e889545..7c97b2b 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/SalaryEffectiveController.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/SalaryEffectiveController.kt @@ -4,16 +4,20 @@ import com.ffii.core.response.RecordsRes import com.ffii.core.utils.CriteriaArgsBuilder import com.ffii.tsms.modules.data.service.SalaryEffectiveService import jakarta.servlet.http.HttpServletRequest +import jakarta.validation.Valid import org.springframework.web.bind.ServletRequestBindingException -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* +import java.time.LocalDate @RestController @RequestMapping("/salaryEffective") class SalaryEffectiveController(private val salaryEffectiveService: SalaryEffectiveService) { + @GetMapping("/test") + fun test(@RequestParam startDate: LocalDate, @RequestParam endDate: LocalDate): List { + return salaryEffectiveService.getMonthlyStaffSalaryData(startDate, endDate) + } // @GetMapping("/combo") // @Throws(ServletRequestBindingException::class) // fun combo(request: HttpServletRequest?): RecordsRes> { 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 e041c1e..a7c8833 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 @@ -2,8 +2,8 @@ package com.ffii.tsms.modules.report.service import com.ffii.core.support.JdbcDao import com.ffii.tsms.modules.data.entity.* -import com.ffii.tsms.modules.data.entity.projections.SalaryEffectiveInfo import com.ffii.tsms.modules.data.service.SalaryEffectiveService +import com.ffii.tsms.modules.data.service.SalaryEffectiveService.MonthlyStaffSalaryData import com.ffii.tsms.modules.project.entity.Invoice import com.ffii.tsms.modules.project.entity.Milestone import com.ffii.tsms.modules.project.entity.Project @@ -156,11 +156,19 @@ open class ReportService( project: Project, invoices: List, timesheets: List, + monthlyStaffSalaryEffective: List, dateType: String ): ByteArray { // Generate the Excel report with query results val workbook: Workbook = - createProjectCashFlowReport(project, invoices, timesheets, dateType, PROJECT_CASH_FLOW_REPORT) + createProjectCashFlowReport( + project, + invoices, + timesheets, + monthlyStaffSalaryEffective, + dateType, + PROJECT_CASH_FLOW_REPORT + ) // Write the workbook to a ByteArrayOutputStream val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() @@ -319,11 +327,20 @@ open class ReportService( timesheets: List, teams: List, grades: List, + monthlyStaffSalaryEffective: List, teamId: String, ): ByteArray { // Generate the Excel report with query results val workbook: Workbook = - createCrossTeamChargeReport(month, timesheets, teams, grades, teamId, CROSS_TEAM_CHARGE_REPORT) + createCrossTeamChargeReport( + month, + timesheets, + teams, + grades, + monthlyStaffSalaryEffective, + teamId, + CROSS_TEAM_CHARGE_REPORT + ) // Write the workbook to a ByteArrayOutputStream val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() @@ -431,7 +448,9 @@ open class ReportService( endDateCell.setCellValue(if (item["planEnd"] != null) item.getValue("planEnd").toString() else "N/A") val totalFeeCell = row.createCell(7) - val totalFee = (item["expectedTotalFee"]?.let { it as Double } ?: 0.0) - (item["subContractFee"]?.let { it as Double } ?: 0.0) + val totalFee = + (item["expectedTotalFee"]?.let { it as Double } ?: 0.0) - (item["subContractFee"]?.let { it as Double } + ?: 0.0) totalFeeCell.apply { setCellValue(totalFee) cellStyle.dataFormat = accountingStyle @@ -693,6 +712,7 @@ open class ReportService( project: Project, invoices: List, timesheets: List, + monthlyStaffSalaryEffective: List, dateType: String, templatePath: String, ): Workbook { @@ -763,8 +783,10 @@ open class ReportService( rowIndex = 11 val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! } val actualExpenditure = timesheets.sumOf { timesheet -> - timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed - ?: 0.0)) +// timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed ?: 0.0)) + (monthlyStaffSalaryEffective.find { + it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month + }?.hourlyRate ?: 0.0) * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed ?: 0.0)) } sheet.getRow(rowIndex).apply { createCell(1).apply { @@ -805,10 +827,16 @@ open class ReportService( timesheetEntries.map { timesheet -> if (timesheet.normalConsumed != null) { timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) - .times(timesheet.staff!!.salary.hourlyRate.toDouble()) +// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) + .times(monthlyStaffSalaryEffective.find { + it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month + }?.hourlyRate ?: 0.0) } else if (timesheet.otConsumed != null) { timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) - .times(timesheet.staff!!.salary.hourlyRate.toDouble()) +// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) + .times(monthlyStaffSalaryEffective.find { + it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month + }?.hourlyRate ?: 0.0) } else { 0.0 } @@ -2043,47 +2071,95 @@ open class ReportService( return jdbcDao.queryForList(sql.toString(), args) } + open fun getProjectResourceOverconsumptionReport_projectInfo(args: Map): List> { + val sql = StringBuilder("SELECT" + + " t.projectId as id, " + + " p.code as projectCode, " + + " p.name as projectName, " + + " te.code as team, " + + " CONCAT( c.code, ' - ' ,c.name ) as client, " + + " COALESCE(concat(sub.code, ' - ', sub.name), 'N/A') as subsidaiary, " + + " (p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8 as plannedBudget " + + " from timesheet t " + + " left join project p on p.id = t.projectId " + + " left join team te on te.teamLead = p.teamLead " + + " left join customer c ON p.customerId = c.id " + + " left join subsidiary sub on sub.id = p.customerSubsidiaryId " + + " where t.projectid is not NULL " + + " and p.deleted = false " + + " and p.status = 'On-going' " + ) + if (args != null) { + if (args.containsKey("teamId")) + sql.append(" and tm.id = :teamId") + if (args.containsKey("custId")) + sql.append(" and c.id = :custId") + if (args.containsKey("subsidiaryId")) + sql.append(" and ss.id = :subsidiaryId") + } + sql.append(" group by t.projectId; ") + return jdbcDao.queryForList(sql.toString(), args) + } + + open fun getProjectResourceOverconsumptionReport_timesheetInfo(): List> { + val sql = StringBuilder("SELECT" + + " t.*, " + + " sal.hourlyRate " + + " from timesheet t " + + " left join staff s on s.id = t.staffId " + + " left join salary sal on sal.salaryPoint = s.salaryId " + + " where t.deleted = false " + ) + return jdbcDao.queryForList(sql.toString()) + } + open fun getProjectResourceOverconsumptionReport(args: Map): List> { val sql = StringBuilder( - "SELECT" - + " p.code, " - + " p.name, " - + " tm.code as team, " - + " concat(c.code, ' - ',c.name) as client, " - + " COALESCE(concat(ss.code, ' - ', ss.name), 'N/A') as subsidiary, " - + " (p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8 as plannedBudget, " - + " sum(t.consumedBudget) as actualConsumedBudget, " - + " COALESCE(p.totalManhour, 0) as plannedManhour, " - + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as actualConsumedManhour, " - + " sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) as budgetConsumptionRate, " - + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate, " - + " case " - + " when sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) >= 1 " - + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= 1 " - + " then 'Overconsumption' " - + " when sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) >= :lowerLimit and sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) <= 1 " - + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) <= 1 " - + " then 'Potential Overconsumption' " - + " else 'Within Budget' " - + " END as status " - + " from " - + " (SELECT " - + " t.*, " - + " (t.normalConsumed + COALESCE(t.otConsumed, 0)) * sal.hourlyRate as consumedBudget " - + " from timesheet t " - + " left join staff s on s.id = t.staffId " - + " left join salary sal on sal.salaryPoint = s.salaryId ) t " - + " left join project p on p.id = t.projectId " - + " left join team tm on p.teamLead = tm.teamLead " - + " left join customer c on c.id = p.customerId " - + " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " - + " WHERE p.deleted = false " - + " and p.status = 'On-going' " + "SELECT" + + " p.code, " + + " p.name, " + + " tm.code as team, " + + " concat(c.code, ' - ',c.name) as client, " + + " COALESCE(concat(ss.code, ' - ', ss.name), 'N/A') as subsidiary, " + + " (p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8 as plannedBudget, " + + " sum(t.consumedBudget) as actualConsumedBudget, " + + " COALESCE(p.totalManhour, 0) as plannedManhour, " + + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as actualConsumedManhour, " + + " sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) as budgetConsumptionRate, " + + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate, " + + " case " + + " when sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) >= 1 " + + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= 1 " + + " then 'Overconsumption' " + + " when sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) >= :lowerLimit and sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) <= 1 " + + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) <= 1 " + + " then 'Potential Overconsumption' " + + " else 'Within Budget' " + + " END as status " + + " from " + + " (SELECT t.*, (t.normalConsumed + COALESCE(t.otConsumed, 0)) * sal.hourlyRate as consumedBudget " + + " FROM " + + " ( " + + " SELECT t.id AS tid, max(se.date) AS max_date " + + " FROM timesheet t " + + " INNER JOIN salary_effective se ON se.staffId = t.staffId AND se.date <= t.recordDate " + + " GROUP BY t.id " + + " ) max_se " + + " LEFT JOIN timesheet t ON t.id = max_se.tid " + + " LEFT JOIN staff s on s.id = t.staffId " + + " inner join salary_effective se on se.date = max_se.max_date " + + " left join salary sal on se.salaryId = sal.salaryPoint) t " + + " left join project p on p.id = t.projectId " + + " left join team tm on p.teamLead = tm.teamLead " + + " left join customer c on c.id = p.customerId " + + " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " + + " WHERE p.deleted = false " + + " and p.status = 'On-going' " ) var statusFilter: String = "" if (args != null) { if (args.containsKey("teamId")) - sql.append(" and t.id = :teamId") + sql.append(" and tm.id = :teamId") if (args.containsKey("custId")) sql.append(" and c.id = :custId") if (args.containsKey("subsidiaryId")) @@ -2388,10 +2464,14 @@ open class ReportService( } } - fun updateFinancialYear(financialYear: FinancialYear, salaryDataList: List?, staffInfo: Map): FinancialYear { + fun updateFinancialYear( + financialYear: FinancialYear, + salaryDataList: List?, + staffInfo: Map + ): FinancialYear { // println("====================== staffInfo: $staffInfo ===============================") - if(salaryDataList == null){ + if (salaryDataList == null) { return financialYear.copy( hourlyRate = (staffInfo["hourlyRate"] as BigDecimal).toDouble(), salaryPoint = (staffInfo["salaryPoint"] as Long).toInt() @@ -2414,15 +2494,19 @@ open class ReportService( val previousHourlyRate = findPreviousValue(salaryDataList, financialYear.start) { it.hourlyRate } val previousSalaryPoint = findPreviousValue(salaryDataList, financialYear.start) { it.salaryPoint } -// println("====================== staffInfo: $staffInfo ===============================") + println("====================== staffInfo: $staffInfo ===============================") return financialYear.copy( - hourlyRate = previousHourlyRate?.toDouble() ?: financialYear.hourlyRate, - salaryPoint = previousSalaryPoint ?: financialYear.salaryPoint + hourlyRate = previousHourlyRate?.toDouble() ?: (staffInfo["hourlyRate"] as BigDecimal).toDouble(), + salaryPoint = previousSalaryPoint ?: (staffInfo["salaryPoint"] as Long).toInt() ) } } - fun findPreviousValue(salaryDataList: List, currentStart: YearMonth, valueSelector: (SalaryEffectiveService.SalaryData) -> T): T? { + fun findPreviousValue( + salaryDataList: List, + currentStart: YearMonth, + valueSelector: (SalaryEffectiveService.SalaryData) -> T + ): T? { return salaryDataList .filter { YearMonth.from(it.financialYear) < currentStart } .maxByOrNull { it.financialYear } @@ -2548,12 +2632,13 @@ open class ReportService( return financialYearDates } - private fun getOrCreateRow(sheet: Sheet, startRow: Int): Row{ + + 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{ + private fun getOrCreateCell(row: Row, columnIndex: Int): Cell { val cell: Cell = row.getCell(columnIndex) ?: row.createCell(columnIndex) return cell } @@ -2693,7 +2778,7 @@ open class ReportService( // Average Hourly Rate by Pay Scale Point rowNum = 8 val row8: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) - sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 2, (financialYears.size+1)*2-1)) + sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 2, (financialYears.size + 1) * 2 - 1)) val row8Cell = row8.getCell(2) ?: row8.createCell(2) row8Cell.apply { setCellValue("Average Hourly Rate by Pay Scale Point") @@ -2710,9 +2795,15 @@ open class ReportService( var column = 2 financialYears.indices.forEach { i -> val row9Cell = row9.getCell(column) ?: row9.createCell(column) - val row9Cell2 = row9.getCell(column+1) ?: row9.createCell(column+1) + val row9Cell2 = row9.getCell(column + 1) ?: row9.createCell(column + 1) sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, column, column + 1)) - row9Cell.setCellValue("${financialYears[i].start.format(monthFormat)} - ${financialYears[i].end.format(monthFormat)}") + row9Cell.setCellValue( + "${financialYears[i].start.format(monthFormat)} - ${ + financialYears[i].end.format( + monthFormat + ) + }" + ) CellUtil.setAlignment(row9Cell, HorizontalAlignment.CENTER); CellUtil.setVerticalAlignment(row9Cell, VerticalAlignment.CENTER); CellUtil.setCellStyleProperty(row9Cell, "borderBottom", BorderStyle.THIN) @@ -2746,7 +2837,7 @@ open class ReportService( var salaryColumn = 2 for (year in years) { val salaryPointCell = row.getCell(salaryColumn) ?: row.createCell(salaryColumn) - sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, salaryColumn, salaryColumn+1)) + sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, salaryColumn, salaryColumn + 1)) salaryPointCell.apply { setCellValue("${year.salaryPoint} (${year.hourlyRate})") } @@ -3230,6 +3321,7 @@ open class ReportService( timesheets: List, teams: List, grades: List, + monthlyStaffSalaryEffective: List, teamId: String, templatePath: String, ): Workbook { @@ -3304,13 +3396,19 @@ open class ReportService( mutableMapOf().apply { this["manHour"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) this["salary"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) - .times(timesheet.staff!!.salary.hourlyRate.toDouble()) +// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) + .times(monthlyStaffSalaryEffective.find { + it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month + }?.hourlyRate ?: 0.0) } } else if (timesheet.otConsumed != null) { mutableMapOf().apply { this["manHour"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) this["salary"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) - .times(timesheet.staff!!.salary.hourlyRate.toDouble()) +// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) + .times(monthlyStaffSalaryEffective.find { + it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month + }?.hourlyRate ?: 0.0) } } else { mutableMapOf().apply { @@ -3556,13 +3654,19 @@ open class ReportService( mutableMapOf().apply { this["manHour"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) this["salary"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) - .times(timesheet.staff!!.salary.hourlyRate.toDouble()) +// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) + .times(monthlyStaffSalaryEffective.find { + it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month + }?.hourlyRate ?: 0.0) } } else if (timesheet.otConsumed != null) { mutableMapOf().apply { this["manHour"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) this["salary"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) - .times(timesheet.staff!!.salary.hourlyRate.toDouble()) +// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) + .times(monthlyStaffSalaryEffective.find { + it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month + }?.hourlyRate ?: 0.0) } } else { mutableMapOf().apply { @@ -3667,61 +3771,61 @@ open class ReportService( projects.forEach { project: Project -> if (teamId.lowercase() == "all" || teamId.toLong() == project.teamLead?.team?.id || teamId.toLong() == team.id) { // if (team.id == project.teamLead?.team?.id) { - endRow++ - sheet.createRow(rowIndex++).apply { - columnIndex = 0 - createCell(columnIndex++).apply { - setCellValue("${project.code}: ${project.name}") - val cloneStyle = workbook.createCellStyle() - cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) - cellStyle = cloneStyle.apply { - alignment = HorizontalAlignment.LEFT - } + endRow++ + sheet.createRow(rowIndex++).apply { + columnIndex = 0 + createCell(columnIndex++).apply { + setCellValue("${project.code}: ${project.name}") + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.LEFT } + } - var totalSalary = 0.0 - staffs.forEach { staff: Staff -> - logger.info("Staff: ${staff.staffId}") - createCell(columnIndex++).apply { - setCellValue( - groupedTimesheetsIndividual[Pair( - project.id, - staff.id, - )]?.sumOf { it.getValue("manHour") } ?: 0.0) - - totalSalary += groupedTimesheetsIndividual[Pair( + var totalSalary = 0.0 + staffs.forEach { staff: Staff -> + logger.info("Staff: ${staff.staffId}") + createCell(columnIndex++).apply { + setCellValue( + groupedTimesheetsIndividual[Pair( project.id, - staff.id - )]?.sumOf { it.getValue("salary") } ?: 0.0 - - val cloneStyle = workbook.createCellStyle() - cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) - cellStyle = cloneStyle.apply { - dataFormat = accountingStyle - } - } - } + staff.id, + )]?.sumOf { it.getValue("manHour") } ?: 0.0) - createCell(columnIndex++).apply { - val lastCellLetter = CellReference.convertNumToColString(this.columnIndex - 1) - cellFormula = "sum(B${this.rowIndex + 1}:${lastCellLetter}${this.rowIndex + 1})" + totalSalary += groupedTimesheetsIndividual[Pair( + project.id, + staff.id + )]?.sumOf { it.getValue("salary") } ?: 0.0 val cloneStyle = workbook.createCellStyle() - cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) cellStyle = cloneStyle.apply { dataFormat = accountingStyle } } + } - createCell(columnIndex).apply { - setCellValue(totalSalary) - val cloneStyle = workbook.createCellStyle() - cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) - cellStyle = cloneStyle.apply { - dataFormat = accountingStyle - } + createCell(columnIndex++).apply { + val lastCellLetter = CellReference.convertNumToColString(this.columnIndex - 1) + cellFormula = "sum(B${this.rowIndex + 1}:${lastCellLetter}${this.rowIndex + 1})" + + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } + } + + createCell(columnIndex).apply { + setCellValue(totalSalary) + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle } } + } // } } } diff --git a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt index 7fa7aed..9179bd4 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt @@ -6,6 +6,7 @@ import com.ffii.tsms.modules.common.SecurityUtils import com.ffii.tsms.modules.data.entity.* //import com.ffii.tsms.modules.data.entity.projections.FinancialStatusReportInfo import com.ffii.tsms.modules.data.service.CustomerService +import com.ffii.tsms.modules.data.service.SalaryEffectiveService import com.ffii.tsms.modules.data.service.StaffsService import com.ffii.tsms.modules.data.service.TeamService import com.ffii.tsms.modules.project.entity.* @@ -61,7 +62,8 @@ class ReportController( private val subsidiaryService: SubsidiaryService, private val invoiceService: InvoiceService, private val gradeAllocationRepository: GradeAllocationRepository, private val subsidiaryRepository: SubsidiaryRepository, private val staffAllocationRepository: StaffAllocationRepository, - private val gradeRepository: GradeRepository + private val gradeRepository: GradeRepository, + private val salaryEffectiveService: SalaryEffectiveService, ) { private val logger: Log = LogFactory.getLog(javaClass) @@ -86,8 +88,9 @@ class ReportController( // val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) val invoices = invoiceRepository.findAllByProjectCodeAndPaidAmountIsNotNull(project.code!!) val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) + val monthlyStaffSalaryEffective = salaryEffectiveService.getMonthlyStaffSalaryData(timesheets.minByOrNull { it.recordDate!! }?.recordDate ?: LocalDate.parse("2012-01-01"), timesheets.maxByOrNull { it.recordDate!! }?.recordDate ?: LocalDate.now()) - val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices, timesheets, request.dateType) + val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices, timesheets, monthlyStaffSalaryEffective, request.dateType) // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") return ResponseEntity.ok() // .contentType(mediaType) @@ -337,8 +340,9 @@ class ReportController( (it.recordDate!!.isEqual(startDate) || it.recordDate!!.isEqual(endDate) || (it.recordDate!!.isAfter(startDate) && it.recordDate!!.isBefore(endDate)))} val teams = teamRepository.findAll().filter { it.deleted == false } val grades = gradeRepository.findAll().filter { it.deleted == false } + val monthlyStaffSalaryEffective = salaryEffectiveService.getMonthlyStaffSalaryData(startDate, endDate) - val reportResult: ByteArray = excelReportService.generateCrossTeamChargeReport(request.month, timesheets, teams, grades, request.teamId) + val reportResult: ByteArray = excelReportService.generateCrossTeamChargeReport(request.month, timesheets, teams, grades, monthlyStaffSalaryEffective, request.teamId) return ResponseEntity.ok() .header("filename", "Cross Team Charge Report - " + LocalDate.now() + ".xlsx") .body(ByteArrayResource(reportResult)) diff --git a/src/main/java/com/ffii/tsms/modules/settings/entity/SettingsRepository.java b/src/main/java/com/ffii/tsms/modules/settings/entity/SettingsRepository.java index fdf27ef..ddf1a19 100644 --- a/src/main/java/com/ffii/tsms/modules/settings/entity/SettingsRepository.java +++ b/src/main/java/com/ffii/tsms/modules/settings/entity/SettingsRepository.java @@ -1,5 +1,6 @@ package com.ffii.tsms.modules.settings.entity; +import java.util.List; import java.util.Optional; import org.springframework.data.repository.query.Param; @@ -9,4 +10,6 @@ import com.ffii.core.support.AbstractRepository; public interface SettingsRepository extends AbstractRepository { Optional findByName(@Param("name") String name); + + List findAllByCategory(@Param("category") String category); } diff --git a/src/main/java/com/ffii/tsms/modules/settings/service/SettingsService.java b/src/main/java/com/ffii/tsms/modules/settings/service/SettingsService.java index c108690..bf01f9a 100644 --- a/src/main/java/com/ffii/tsms/modules/settings/service/SettingsService.java +++ b/src/main/java/com/ffii/tsms/modules/settings/service/SettingsService.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.List; import java.util.Optional; import org.apache.commons.lang3.StringUtils; @@ -29,6 +30,10 @@ public class SettingsService extends AbstractIdEntityService findAllByCategory(String category) { + return this.repository.findAllByCategory(category); + } + public boolean validateType(String type, String value) { if (StringUtils.isBlank(type)) return true;