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 02d2844..5d35b09 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 @@ -35,11 +35,16 @@ import java.math.RoundingMode import java.time.Year import javax.swing.plaf.synth.Region import kotlin.collections.ArrayList +import kotlin.collections.HashMap data class DayInfo(val date: String?, val weekday: String?) data class TsData(var manhour: Double, var cost: Double) data class InOut(var In: TsData, var Out: TsData) +data class CrossTeamP4InOut( + // String = "${staffCode} ${staffName} (${teamCode})" + var In: MutableMap>, +) @Service open class ReportService( @@ -71,7 +76,14 @@ open class ReportService( private fun cellBorderArgs(top: Int, bottom: Int, left: Int, right: Int): MutableMap { var cellBorderArgs = mutableMapOf() - when (top) { +// val thicknessMap = mapOf( +// 1 to BorderStyle.THIN, +// 2 to BorderStyle.THICK, +// ) +// if (top > 0) { +// cellBorderArgs.put(CellUtil.BORDER_TOP, thicknessMap[top]) +// } + when (top ) { 1 -> cellBorderArgs.put(CellUtil.BORDER_TOP, BorderStyle.THIN) 2 -> cellBorderArgs.put(CellUtil.BORDER_TOP, BorderStyle.THICK) } @@ -90,22 +102,30 @@ open class ReportService( return cellBorderArgs } private fun setRegionBorders(top: Int, bottom: Int, left: Int, right: Int, region: CellRangeAddress, sheet: Sheet) { - when (top) { - 1 -> RegionUtil.setBorderTop(BorderStyle.THIN, region, sheet) - 2 -> RegionUtil.setBorderTop(BorderStyle.THICK, region, sheet) - } - when (bottom) { - 1 -> RegionUtil.setBorderBottom(BorderStyle.THIN, region, sheet) - 2 -> RegionUtil.setBorderBottom(BorderStyle.THICK, region, sheet) - } - when (left) { - 1 -> RegionUtil.setBorderLeft(BorderStyle.THIN, region, sheet) - 2 -> RegionUtil.setBorderLeft(BorderStyle.THICK, region, sheet) - } - when (right) { - 1 -> RegionUtil.setBorderRight(BorderStyle.THIN, region, sheet) - 2 -> RegionUtil.setBorderRight(BorderStyle.THICK, region, sheet) - } + val thicknessMap = mapOf( + 1 to BorderStyle.THIN, + 2 to BorderStyle.THICK, + ) + RegionUtil.setBorderTop(thicknessMap[top], region, sheet) + RegionUtil.setBorderBottom(thicknessMap[bottom], region, sheet) + RegionUtil.setBorderLeft(thicknessMap[left], region, sheet) + RegionUtil.setBorderRight(thicknessMap[right], region, sheet) +// when (top) { +// 1 -> RegionUtil.setBorderTop(thicknessMap[1], region, sheet) +// 2 -> RegionUtil.setBorderTop(BorderStyle.THICK, region, sheet) +// } +// when (bottom) { +// 1 -> RegionUtil.setBorderBottom(BorderStyle.THIN, region, sheet) +// 2 -> RegionUtil.setBorderBottom(BorderStyle.THICK, region, sheet) +// } +// when (left) { +// 1 -> RegionUtil.setBorderLeft(BorderStyle.THIN, region, sheet) +// 2 -> RegionUtil.setBorderLeft(BorderStyle.THICK, region, sheet) +// } +// when (right) { +// 1 -> RegionUtil.setBorderRight(BorderStyle.THIN, region, sheet) +// 2 -> RegionUtil.setBorderRight(BorderStyle.THICK, region, sheet) +// } } private val chargeFee = 1.15 @@ -3600,9 +3620,9 @@ open class ReportService( return jdbcDao.queryForList(sql.toString(), args) } - private fun generateTeamBlock( + private fun generateTeamsInOutMap( teams: List, - sortedTeams: MutableList, + desiredTeam: MutableList, grades: List, timesheets: List, gradeLog: List, @@ -3632,63 +3652,60 @@ open class ReportService( }.toMutableMap() } } - println("teamsMap") - println(gradeMap) -// teamsMap["TW"]!!["1"]!!.In.manhour += 1000 - println(teamsMap) - sortedTeams.forEach { team -> - val currentTeam = team.code - val _timesheets = timesheets.filter { ts -> -// for after team log is implemented -// val thisStaffTeam = teamlog.filter { -// it.from.isBefore(ts.recordDate) -// && it.to != null -// && it.staff.id == ts.staff!!.id -// }.maxByOrNull { it.from } -// val thisProjectTeam = teamlog.filter { -// it.from.isBefore(ts.recordDate) -// && it.to != null -// && it.staff.id == ts.project!!.teamLead!!.team.id -// }.maxByOrNull { it.from } + var _timesheets: List + // if have target team, desiredTeams should only be that team + if (desiredTeam.size == 1) { + val team = desiredTeam[0] + val targetTeam = team.code + _timesheets = timesheets.filter { ts -> val staffTeam = ts.staff!!.team.code val projectTeam = ts.project!!.teamLead!!.team.code - (staffTeam == currentTeam && projectTeam != staffTeam) - || (projectTeam == currentTeam && projectTeam != staffTeam) // check isCrossTeam -// ts.project!!.teamLead!!.team.id != thisStaffTeam!!.team.id // for team log + projectTeam != staffTeam && + (staffTeam == targetTeam || projectTeam == targetTeam ) } - _timesheets.forEach {ts -> - // this team charging others - // get the grade and salary data of the record - val _grade = gradeLog.find { it.staff.id == ts.staff!!.id } - val gradeCode = _grade!!.grade.code - val otMultiplier = 1.15 - val thisSE = salaryEffective.filter { - it.staff.id == ts.staff!!.id - && - it.date.isBefore(ts.recordDate) - }.maxByOrNull { it.date } - val normalHour = ts.normalConsumed ?: 0.0 - val otHour = ts.otConsumed ?: 0.0 - val normalCost = normalHour.times(thisSE!!.salary.hourlyRate.toDouble()) - val otCost = otHour.times(thisSE.salary.hourlyRate.toDouble()).times(otMultiplier) - //assigning data - val projectTeam = ts.project!!.teamLead!!.team.code + } else { + _timesheets = timesheets.filter { ts -> val staffTeam = ts.staff!!.team.code - // write in - println("putting in") - var tsInData = teamsMap[projectTeam]!![gradeCode]!!.In - println(tsInData) - tsInData.manhour += normalHour + otHour - tsInData.cost += normalCost + otCost - // write out - println("putting out") - val tsOutData = teamsMap[staffTeam]!!.get(gradeCode)!!.Out - tsOutData.manhour += normalHour + otHour - tsOutData.cost += normalCost + otCost + val projectTeam = ts.project!!.teamLead!!.team.code + projectTeam != staffTeam } } - println("all team - gradeMap") - println(teamsMap) + _timesheets.forEach {ts -> + // this team charging others + // get the grade and salary data of the record + val _grade = gradeLog.find { + it.staff.id == ts.staff!!.id + && + it.from.isBefore(ts.recordDate) && (it.to == null || it.to.isAfter(ts.recordDate)) + && + it.deleted == false + } + val gradeCode = _grade!!.grade.code + val otMultiplier = 1.15 + val thisSE = salaryEffective.find { + it.staff.id == ts.staff!!.id + && + it.startDate.isBefore(ts.recordDate) && (it.endDate.isAfter(ts.recordDate) || it.endDate == null) + } + val normalHour = ts.normalConsumed ?: 0.0 + val otHour = ts.otConsumed ?: 0.0 + val normalCost = normalHour.times(thisSE!!.salary.hourlyRate.toDouble()) + val otCost = otHour.times(thisSE.salary.hourlyRate.toDouble()).times(otMultiplier) + //assigning data + val projectTeam = ts.project!!.teamLead!!.team.code + val staffTeam = ts.staff!!.team.code + // write in + //println("putting in") + var tsInData = teamsMap[projectTeam]!![gradeCode]!!.In + tsInData.manhour += normalHour + otHour + tsInData.cost += normalCost + otCost + // write out + //println("putting out") + val tsOutData = teamsMap[staffTeam]!!.get(gradeCode)!!.Out + tsOutData.manhour += normalHour + otHour + tsOutData.cost += normalCost + otCost + } + return teamsMap } @@ -3697,6 +3714,23 @@ open class ReportService( CellUtil.setVerticalAlignment(tempCell, VerticalAlignment.TOP) CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) } + + private fun setAlignment(cell: Cell, verticalAlignment: String, horizontalAlignment: String) { + val verticalAlignmentMap = mapOf( + "top" to VerticalAlignment.TOP, + "center" to VerticalAlignment.CENTER, + "bottom" to VerticalAlignment.BOTTOM, + ) + val horizontalAlignmentMap = mapOf( + "left" to HorizontalAlignment.LEFT, + "center" to HorizontalAlignment.CENTER, + "right" to HorizontalAlignment.RIGHT, + "fill" to HorizontalAlignment.FILL, + "justify" to HorizontalAlignment.JUSTIFY, + ) + CellUtil.setVerticalAlignment(cell, verticalAlignmentMap[verticalAlignment]) + CellUtil.setAlignment(cell, horizontalAlignmentMap[horizontalAlignment]) + } private fun createCrossTeamForm( workbook: Workbook, sheet: Sheet, @@ -3914,15 +3948,15 @@ open class ReportService( sheet.getRow(rowIndex).getCell(columnIndex).apply { setCellValue(convertReportMonth) } - var sortedTeams = teams.toMutableList() + var desiredTeam = teams.toMutableList() if (teamId.lowercase() != "all") { -// sortedTeams = teams.sortedWith(compareBy { if (it.id == teamId.toLong()) 0 else 1 }).toMutableList() - sortedTeams = mutableListOf(teams.find { teamId.toLong() == it.id }!!) +// desiredTeam = teams.sortedWith(compareBy { if (it.id == teamId.toLong()) 0 else 1 }).toMutableList() + desiredTeam = mutableListOf(teams.find { teamId.toLong() == it.id }!!) } //// generate info map ///// - val teamsInOutMap: MutableMap> = generateTeamBlock( + val teamsInOutMap: MutableMap> = generateTeamsInOutMap( teams, - sortedTeams, + desiredTeam, grades, timesheets, gradeLog, @@ -3942,6 +3976,306 @@ open class ReportService( sheet.setColumnWidth(i, 20 * 256) } } + private fun createCrossTeamForm4TH( + workbook: Workbook, + sheet: Sheet, + teamsMap: MutableMap + ) { + var rowIndex = 2 + var columnIndex = 0 + var tempRow: Row + var tempCell: Cell + val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") + fun dataFormatArgs(accountingStyle: Short): MutableMap { + val dataFormatArgs = mutableMapOf( + CellUtil.DATA_FORMAT to accountingStyle + ) + return dataFormatArgs + } + fun fontArgs(fontName: String, isBold: Boolean, isWrap: Boolean): MutableMap{ + val font = sheet.workbook.createFont() + font.bold = isBold + font.fontName = fontName + val fontArgs = mutableMapOf( + CellUtil.FONT to font.index, + CellUtil.WRAP_TEXT to isWrap, + ) + return fontArgs + } + for ((teamCode, crossTeamP4InOut) in teamsMap) { + columnIndex = 0 + rowIndex++ + tempRow = getOrCreateRow(sheet, rowIndex) + tempCell = getOrCreateCell(tempRow, columnIndex) + tempCell.setCellValue("Team") + alignTopCenter(tempCell) + CellUtil.setCellStyleProperties( + tempCell, + fontArgs("Times New Roman", true, true) + + cellBorderArgs(2,2,1,1) + ) + columnIndex = 1 + tempCell = getOrCreateCell(tempRow, columnIndex) + tempCell.setCellValue("Cross Team Staffs") + alignTopCenter(tempCell) + CellUtil.setCellStyleProperties(tempCell, + fontArgs("Times New Roman", true, true) + + cellBorderArgs(2,2,1,1) + ) + ////// getting unique header list with columnIndex /////// + var projectList = mutableListOf() + for ((_, projectTsDataMap) in crossTeamP4InOut.In) { + for(key in projectTsDataMap.keys) { + if (key !in projectList) { + projectList.add(key) + } + } + } + tempRow = getOrCreateRow(sheet, rowIndex) + columnIndex = 2 + projectList.forEach{ str -> + tempCell = getOrCreateCell(tempRow, columnIndex) + tempCell.setCellValue(str) + CellUtil.setCellStyleProperties(tempCell, fontArgs("Times New Roman", true, true)) + alignTopCenter(tempCell) + val titleRegion = CellRangeAddress(rowIndex, rowIndex,columnIndex,columnIndex+1) + sheet.addMergedRegion(titleRegion) + setRegionBorders(2,2,1,1,titleRegion, sheet) + columnIndex += 2 + } + tempCell = getOrCreateCell(tempRow, columnIndex) + tempCell.setCellValue("Total By Staff") + CellUtil.setCellStyleProperties(tempCell, fontArgs("Times New Roman", true, true)) + alignTopCenter(tempCell) + val titleRegion = CellRangeAddress(rowIndex, rowIndex,columnIndex,columnIndex+1) + sheet.addMergedRegion(titleRegion) + setRegionBorders(2,2,2,2,titleRegion, sheet) + alignTopCenter(tempCell) + /////////////////////////////////////////////////////////////////////////////////// + // total column = columnIndex + list.size + rowIndex++ + columnIndex = 0 + tempRow = getOrCreateRow(sheet, rowIndex) + tempCell = getOrCreateCell(tempRow, columnIndex) + tempCell.setCellValue(teamCode) // team column + CellUtil.setCellStyleProperties(tempCell, + fontArgs("Times New Roman", false, false) + ) + setAlignment(tempCell, "top", "center") + + var startingRow = rowIndex + for ((staff, projectTsDataMap) in crossTeamP4InOut.In) { + columnIndex = 1 + tempRow = getOrCreateRow(sheet, rowIndex) + tempCell = getOrCreateCell(tempRow, columnIndex) + tempCell.setCellValue(staff) // team column + setAlignment(tempCell, "top", "center") + CellUtil.setCellStyleProperties(tempCell, + cellBorderArgs(1,1,1,1) + + fontArgs("Times New Roman", false, false) + ) + var pidx = 0 + var manhourFormula = mutableListOf() + var costFormula = mutableListOf() + while (pidx < projectList.size) { + var manhour = 0.0 + var cost = 0.0 + if (projectTsDataMap.containsKey(projectList[pidx])) { + manhour = projectTsDataMap[projectList[pidx]]?.manhour ?: 0.0 + cost = projectTsDataMap[projectList[pidx]]?.cost ?: 0.0 + } + columnIndex++ + val manhourLetter = CellReference.convertNumToColString(columnIndex) + tempCell = getOrCreateCell(tempRow, columnIndex) + tempCell.setCellValue(manhour) + CellUtil.setCellStyleProperties(tempCell, + cellBorderArgs(1,1,1,1) + + fontArgs("Times New Roman", false, false) + + dataFormatArgs(accountingStyle) + ) + manhourFormula.add("${manhourLetter}${rowIndex+1}") + columnIndex++ + val costLetter = CellReference.convertNumToColString(columnIndex) + tempCell = getOrCreateCell(tempRow, columnIndex) + tempCell.setCellValue(cost) + CellUtil.setCellStyleProperties(tempCell, + cellBorderArgs(1,1,1,1) + + fontArgs("Times New Roman", false, false) + + dataFormatArgs(accountingStyle) + ) + costFormula.add("${costLetter}${rowIndex+1}") + pidx++ + } + // total by staff + columnIndex++ + tempCell = getOrCreateCell(tempRow, columnIndex) + CellUtil.setCellStyleProperties(tempCell, + cellBorderArgs(1,1,2,1) + + fontArgs("Times New Roman", false, false) + + dataFormatArgs(accountingStyle) + ) + tempCell.cellFormula = manhourFormula.joinToString(separator = "+") + columnIndex++ + tempCell = getOrCreateCell(tempRow, columnIndex) + CellUtil.setCellStyleProperties(tempCell, + cellBorderArgs(1,1,1,1) + + fontArgs("Times New Roman", false, false) + + dataFormatArgs(accountingStyle) + ) + tempCell.cellFormula = costFormula.joinToString(separator = "+") + rowIndex++ + } + columnIndex = 0 + val teamCodeRegion = CellRangeAddress(startingRow, rowIndex, columnIndex, columnIndex) + sheet.addMergedRegion(teamCodeRegion) + setRegionBorders(2,2,1,1, teamCodeRegion, sheet) + columnIndex = 1 + tempRow = getOrCreateRow(sheet, rowIndex) + tempCell = getOrCreateCell(tempRow, columnIndex) + tempCell.setCellValue("Total by Project:") + setAlignment(tempCell, "top", "center") + CellUtil.setCellStyleProperties(tempCell, + cellBorderArgs(1,2,1,1) + + fontArgs("Times New Roman", true, false) + ) + + // (project title + total by staff) * 2 + columnIndex = 2 + val numberOfColumns = (projectList.size + 1) * 2 + var i = 0 + while (i < numberOfColumns) { + var leftBorder = 1 + if (i == numberOfColumns - 2) { + leftBorder = 2 + } + tempCell = getOrCreateCell(tempRow, columnIndex) + val columnLetter = CellReference.convertNumToColString(columnIndex) + tempCell.cellFormula = "SUM(${columnLetter}${startingRow+1}:${columnLetter}${rowIndex})" + CellUtil.setCellStyleProperties(tempCell, + cellBorderArgs(1,2,leftBorder,1) + + fontArgs("Times New Roman", false, false) + + dataFormatArgs(accountingStyle) + ) + columnIndex++ + i++ + } + rowIndex++ + } + + + } + private fun generatePageFourTeamsInOutMap( + teams: List, + desiredTeams: MutableList, + grades: List, + timesheets: List, + gradeLog: List, + salaryEffective: List, + ): MutableMap { + var teamsMap: MutableMap = mutableMapOf() +// teams.forEach { team -> +// val key = team.code +// if (key !in teamsMap) { +// teamsMap[key] = CrossTeamP4InOut( +// In = mutableMapOf(), +// ) +// } +// } + var _timesheets: List + // if have target team, desiredTeams should only be that team + if (desiredTeams.size == 1) { + val team = desiredTeams[0] + val targetTeam = team.code + _timesheets = timesheets.filter { ts -> + val staffTeam = ts.staff!!.team.code + val projectTeam = ts.project!!.teamLead!!.team.code + projectTeam != staffTeam && + (staffTeam == targetTeam || projectTeam == targetTeam ) + } + } else { + _timesheets = timesheets.filter { ts -> + val staffTeam = ts.staff!!.team.code + val projectTeam = ts.project!!.teamLead!!.team.code + projectTeam != staffTeam + } + } + _timesheets.forEach { ts -> + val otMultiplier = 1.15 + val thisSE = salaryEffective.filter { + it.staff.id == ts.staff!!.id + && + it.date.isBefore(ts.recordDate) + }.maxByOrNull { it.date } + val normalHour = ts.normalConsumed ?: 0.0 + val otHour = ts.otConsumed ?: 0.0 + val normalCost = normalHour.times(thisSE!!.salary.hourlyRate.toDouble()) + val otCost = otHour.times(thisSE.salary.hourlyRate.toDouble()).times(otMultiplier) + val staff = ts.staff!! + val project = ts.project!! + val projectNameCode = "${project.code}\n${project.name}" + val projectTeam = project.teamLead!!.team.code + val inOut_Key = "${staff.staffId} ${staff.name} (${staff.team.code})" + if (!teamsMap.containsKey(projectTeam)) { + teamsMap.put(projectTeam, CrossTeamP4InOut( + In = mutableMapOf(), + )) + } + val tsInData = teamsMap[projectTeam]!!.In + + + if (!tsInData.containsKey(inOut_Key)) { + tsInData.put(inOut_Key, mutableMapOf(projectNameCode to TsData(0.0, 0.0))) + } + if (!tsInData[inOut_Key]!!.containsKey(projectNameCode)) { + tsInData[inOut_Key]!!.put(projectNameCode, TsData(0.0, 0.0)) + } + tsInData[inOut_Key]!![projectNameCode]!!.manhour += normalHour + otHour + tsInData[inOut_Key]!![projectNameCode]!!.cost += normalCost + otCost + } + + return teamsMap + } + private fun createFourthSheetTeamChargeReport( + month: String, + workbook: Workbook, + timesheets: List, + teams: List, + grades: List, + teamId: String, + gradeLog: List, + salaryEffective: List, + ) { + var sheet: Sheet = workbook.getSheetAt(3) + val rowIndex = 1 // Assuming the location is in (1,2), which is the report date field + val columnIndex = 2 + val monthFormat = DateTimeFormatter.ofPattern("MMMM yyyy", Locale.ENGLISH) + val reportMonth = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM")) + val convertReportMonth = YearMonth.of(reportMonth.year, reportMonth.month).format(monthFormat) + sheet.getRow(rowIndex).getCell(columnIndex).apply { + setCellValue(convertReportMonth) + } + val _teams = teams + var desiredTeams = teams.toMutableList() + if (teamId.lowercase() != "all") { + desiredTeams = mutableListOf(teams.find { teamId.toLong() == it.id }!!) + _teams.sortedWith(compareBy { if (it.id == teamId.toLong()) 0 else 1 }) + } + val teamsInOutMap: MutableMap = generatePageFourTeamsInOutMap( + _teams, + desiredTeams, + grades, + timesheets, + gradeLog, + salaryEffective, + ) + println("4th ------- teamsInOutMap") + println(teamsInOutMap) + createCrossTeamForm4TH( + workbook, + sheet, + teamsInOutMap + ) + } @Throws(IOException::class) private fun createCrossTeamChargeReport( month: String, @@ -4530,6 +4864,17 @@ open class ReportService( gradeLog, salaryEffective ) + // page 4 + createFourthSheetTeamChargeReport( + month, + workbook, + timesheets, + _teams, + grades, + teamId, + gradeLog, + salaryEffective + ) return workbook } // Use to Calculate cummunlative expenditure diff --git a/src/main/resources/templates/report/Cross Team Charge Report.xlsx b/src/main/resources/templates/report/Cross Team Charge Report.xlsx index 75eea41..63ea78a 100644 Binary files a/src/main/resources/templates/report/Cross Team Charge Report.xlsx and b/src/main/resources/templates/report/Cross Team Charge Report.xlsx differ