From 3f3cd024817a7fc5c198d70bcdf0c0f3b2614b1d Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Fri, 4 Oct 2024 15:47:09 +0800 Subject: [PATCH] cross team individual 2 --- .../modules/report/service/ReportService.kt | 495 +++++++++++++++--- .../report/Cross Team Charge Report.xlsx | Bin 19433 -> 19400 bytes 2 files changed, 420 insertions(+), 75 deletions(-) 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 75eea41ed65fcd6df8c180c406fad501da4dc130..63ea78aa7eddb459fe19734c2ab1e8a00d520991 100644 GIT binary patch delta 4228 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 delta 4250 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