diff --git a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt index 5a03276..6c210c4 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt @@ -463,6 +463,55 @@ open class DashboardService( return jdbcDao.queryForList(sql.toString(), args) } + fun searchTeamConsumptionColorOrder(args: Map): List> { + val sql = StringBuilder( + "select" + + " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," + + " p.id as id," + + " p.id as projectId," + + " p.code as projectCode," + + " p.name as projectName," + + " te.code as team," + + " s.name as teamLead," + + " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage," + + " p.totalManhour as budgetedManhour," + + " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," + + " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour," + + " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage," + + " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone," + + " case" + + " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0" + + " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1" + + " end as alert" + + " from project p" + + " left join project_task pt on p.id = pt.project_id" + + " left join timesheet t on pt.id = t.projectTaskId" + + " left join team te on p.teamLead = te.teamLead" + + " left join staff s on te.teamLead = s.id" + + " left join milestone m on p.id = m.projectId and curdate() >= m.startDate and curdate() <= m.endDate" + + " left join task_group tg on m.taskGroupId = tg.id" + + " left join (" + + " SELECT pid, MIN(comingPaymentMilestone) AS comingPaymentMilestone" + + " FROM (" + + " SELECT p.id AS pid, mp.date AS comingPaymentMilestone" + + " FROM project p" + + " LEFT JOIN milestone m ON p.id = m.projectId" + + " LEFT JOIN milestone_payment mp ON m.id = mp.milestoneId" + + " WHERE p.teamLead in (:teamIds)" + + " AND p.status NOT IN ('Pending to Start', 'Completed', 'Deleted')" + + " AND mp.date >= CURDATE()" + + " ) AS subquery" + + " GROUP BY pid" + + " ORDER BY comingPaymentMilestone ASC" + + " ) milestonePayment on milestonePayment.pid = p.id" + + " where p.teamLead in (:teamIds)" + + " and p.status not in ('Pending to Start','Completed','Deleted')" + + " group by p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone" + + " ORDER BY te.code ASC" + ) + return jdbcDao.queryForList(sql.toString(), args) + } + fun searchFinancialSummaryCard(args: Map): List> { val sql = StringBuilder( "select" @@ -1668,7 +1717,11 @@ open class DashboardService( + " SELECT 5 as num" + " ) numbers" + " WHERE" - + " DATE(:startdate + INTERVAL numbers.num DAY) BETWEEN :startdate AND DATE_ADD(:startdate, INTERVAL 6 DAY)" + + " DATE(:startdate + INTERVAL numbers.num DAY) BETWEEN :startdate AND" + + " case" + + " when curdate() < DATE_ADD(:startdate, INTERVAL 6 DAY) then curdate()" + + " else DATE_ADD(:startdate, INTERVAL 6 DAY)" + + " end" + " AND DAYOFWEEK(DATE(:startdate + INTERVAL numbers.num DAY)) BETWEEN 2 AND 6" + " AND DATE(:startdate + INTERVAL numbers.num DAY) not in (select ch.date from company_holiday ch where ch.deleted = 0)" + " AND DATE(:startdate + INTERVAL numbers.num DAY) not in (:publicHolidayList)" @@ -1747,7 +1800,10 @@ open class DashboardService( + " WHERE" + " DATE(:startdate + INTERVAL numbers.num DAY) BETWEEN" + " :startdate AND" - + " LAST_DAY(:startdate)" + + " case" + + " when month(:startdate) >= month(curdate()) then curdate()" + + " else LAST_DAY(:startdate)" + + " end" + " AND DAYOFWEEK(DATE(:startdate + INTERVAL numbers.num DAY)) BETWEEN 2 AND 6" + " AND DATE(:startdate + INTERVAL numbers.num DAY) not in (select ch.date from company_holiday ch where ch.deleted = 0)" + " AND DATE(:startdate + INTERVAL numbers.num DAY) not in (:publicHolidayList)" diff --git a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt index e8a4d66..5820575 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt @@ -117,6 +117,22 @@ class DashboardController( result = dashboardService.searchTeamConsumption(args) return result } + @GetMapping("/searchTeamConsumptionColorOrder") + fun searchTeamConsumptionColorOrder(request: HttpServletRequest?): List> { + val args = mutableMapOf() + val teamIdList = request?.getParameter("teamIdList") + val tableSorting = request?.getParameter("tableSorting") + val teamIds = teamIdList?.split(",")?.map { it.toInt() }?.toList() + var result: List> = emptyList() + if (teamIds != null) { + args["teamIds"] = teamIds + } + if (tableSorting != null) { + args["tableSorting"] = tableSorting + } + result = dashboardService.searchTeamConsumptionColorOrder(args) + return result + } @GetMapping("/searchFinancialSummaryCard") fun searchFinancialSummaryCard(request: HttpServletRequest?): List> { val authority = dashboardService.viewDashboardAuthority() 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 0362dac..050303c 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 @@ -855,6 +855,46 @@ open class ReportService( combinedResults.forEach { result: String -> if (groupedInvoices.containsKey(result)) { + if (dateType == "Date") { + groupedInvoices[result]!!.forEachIndexed { _, invoice -> + sheet.createRow(rowIndex++).apply { + createCell(0).apply { + setCellValue(result) + } + + createCell(1).apply { + setCellValue(0.0) + cellStyle.dataFormat = accountingStyle + } + + createCell(2).apply { + setCellValue(invoice["paidAmount"] as Double? ?: 0.0) + cellStyle.dataFormat = accountingStyle + } + + createCell(3).apply { + val lastRow = rowIndex - 1 + if (lastRow == 16) { + cellFormula = + "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) + } else { + cellFormula = + "IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace( + "{currentRow}", + rowIndex.toString() + ).replace("{lastRow}", lastRow.toString()) + } + cellStyle.dataFormat = accountingStyle + } + + createCell(4)?.apply { + setCellValue( + "Invoice Receipt: " + (invoice["invoiceNo"] as String? ?: "N/A").toString() + ) + } + } + } + } else { // groupedInvoices[result]!!.forEachIndexed { _, invoice -> sheet.createRow(rowIndex++).apply { createCell(0).apply { @@ -888,9 +928,16 @@ open class ReportService( createCell(4)?.apply { // setCellValue(invoice["description"].toString()) - setCellValue("Invoice Receipt: " + (groupedInvoices[result]?.map { it["invoiceNo"] }?.joinToString() ?: "N/A").toString()) + val invoiceNos = groupedInvoices[result]?.map { it["invoiceNo"] } + if (invoiceNos?.size != null && invoiceNos.size > 1) { +// setCellValue("Invoice Receipt: " + (groupedInvoices[result]?.map { it["invoiceNo"] }?.joinToString() ?: "N/A").toString()) + setCellValue("Multiple Invoices") + } else { + setCellValue("Invoice Receipt: " + (invoiceNos?.joinToString() ?: "N/A").toString()) + } } // } + } } } @@ -1353,7 +1400,7 @@ open class ReportService( for (i in 0 until rowSize) { tempCell = sheet.getRow(8 + i).createCell(columnIndex + index) tempCell.setCellValue(0.0) - if ( 8+i in holidayIndexList) { + if (8 + i in holidayIndexList) { tempCell.cellStyle = fillOrange } tempCell.cellStyle.dataFormat = accountingStyle @@ -1365,7 +1412,7 @@ open class ReportService( dayInt = temp[index]["date"].toString().toInt() tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) tempCell.setCellValue((temp[index]["normalConsumed"] as Double) + (temp[index]["otConsumed"] as Double)) - if ( dayInt.plus(7) in holidayIndexList) { + if (dayInt.plus(7) in holidayIndexList) { tempCell.cellStyle = fillOrange tempCell.cellStyle.dataFormat = accountingStyle } @@ -1377,7 +1424,7 @@ open class ReportService( for (i in 0 until rowSize) { tempCell = sheet.getRow(8 + i).createCell(columnIndex) tempCell.setCellValue(0.0) - if ( 8+i in holidayIndexList) { + if (8 + i in holidayIndexList) { tempCell.cellStyle = fillOrange } @@ -1388,7 +1435,7 @@ open class ReportService( dayInt = leave["recordDate"].toString().toInt() tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) tempCell.setCellValue(leave["leaveHours"] as Double) - if ( dayInt.plus(7) in holidayIndexList) { + if (dayInt.plus(7) in holidayIndexList) { tempCell.cellStyle = fillOrange } @@ -1420,7 +1467,7 @@ open class ReportService( tempCell = sheet.getRow(rowIndex).createCell(columnIndex) tempCell.cellFormula = "SUM(${getColumnAlphabet(2)}${rowIndex + 1}:${getColumnAlphabet(columnIndex - 2)}${rowIndex + 1})" // should columnIndex - 2 - if ( rowIndex in holidayIndexList) { + if (rowIndex in holidayIndexList) { tempCell.cellStyle = fillOrange } @@ -1495,21 +1542,21 @@ open class ReportService( } val cell2 = getCell(2) ?: createCell(2) - cell2.cellFormula = "(B{currentRow}+D{currentRow})-1".replace("{currentRow}", (rowIndex+1).toString()) + cell2.cellFormula = "(B{currentRow}+D{currentRow})-1".replace("{currentRow}", (rowIndex + 1).toString()) CellUtil.setAlignment(cell2, HorizontalAlignment.CENTER) cell2.cellStyle.dataFormat = defaultStyle // getCell(2).cellStyle.dataFormat = accountingStyle - val cell3 = getCell(3)?:createCell(3) + val cell3 = getCell(3) ?: createCell(3) cell3.setCellValue(salary.increment.toDouble()) cell3.cellStyle = fillOrange cell3.cellStyle.dataFormat = defaultStyle CellUtil.setAlignment(cell3, HorizontalAlignment.CENTER) - val cell4 = getCell(4)?:createCell(4) + val cell4 = getCell(4) ?: createCell(4) cell4.cellFormula = - "(((C{currentRow}+B{currentRow})/2)/20)/8".replace("{currentRow}", (rowIndex+1).toString()) + "(((C{currentRow}+B{currentRow})/2)/20)/8".replace("{currentRow}", (rowIndex + 1).toString()) // getCell(4).cellStyle.dataFormat = accountingStyle cell4.cellStyle.dataFormat = accountingStyle CellUtil.setAlignment(cell4, HorizontalAlignment.CENTER) @@ -2002,7 +2049,6 @@ open class ReportService( + " COALESCE(concat(ss.code, ' - ', ss.name), 'N/A') as subsidiary," + " p.expectedTotalFee * 0.8 as plannedBudget," + " COALESCE(tns.totalBudget, 0) as actualConsumedBudget," - + " tns.totalConsumed," + " COALESCE(p.totalManhour, 0) as plannedManhour," + " COALESCE(tns.totalConsumed, 0) as actualConsumedManhour," + " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) as budgetConsumptionRate," @@ -2041,6 +2087,7 @@ open class ReportService( " and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " + " and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 " + "All" -> " and " + " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit " + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " @@ -2105,17 +2152,17 @@ open class ReportService( + " order by g.code, s.name, cte_ts.recordDate" ) val projectPlanStartEndMonth = projectRepository.findProjectPlanStartEndByIdAndDeletedFalse(projectId) - val compareStartMonth= YearMonth.parse(startMonth) - val compareEndMonth= YearMonth.parse(endMonth) + val compareStartMonth = YearMonth.parse(startMonth) + val compareEndMonth = YearMonth.parse(endMonth) val actualStartMonth: YearMonth - (if (projectPlanStartEndMonth.actualStart == null){ + (if (projectPlanStartEndMonth.actualStart == null) { YearMonth.now().plusMonths(1) - }else{ + } else { YearMonth.from(projectPlanStartEndMonth.actualStart) }).also { actualStartMonth = it } - val queryStartMonth = if(compareStartMonth.isAfter(actualStartMonth)) compareStartMonth else actualStartMonth - val queryEndMonth = if(compareEndMonth.isAfter(YearMonth.now())) YearMonth.now() else compareEndMonth + val queryStartMonth = if (compareStartMonth.isAfter(actualStartMonth)) compareStartMonth else actualStartMonth + val queryEndMonth = if (compareEndMonth.isAfter(YearMonth.now())) YearMonth.now() else compareEndMonth val args = mapOf( "projectId" to projectId, @@ -2263,7 +2310,8 @@ open class ReportService( val manhoursSpentList = getManhoursSpent(projectId, startMonth, endMonth) - val workbook: Workbook = createPandLReportWorkbook(PandL_REPORT, manhoursSpentList, startMonth, endMonth, projectId) + val workbook: Workbook = + createPandLReportWorkbook(PandL_REPORT, manhoursSpentList, startMonth, endMonth, projectId) val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() workbook.write(outputStream) @@ -2332,17 +2380,17 @@ open class ReportService( val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") val projectPlanStartEndMonth = projectRepository.findProjectPlanStartEndByIdAndDeletedFalse(projectId) - val compareStartMonth= YearMonth.parse(startMonth) - val compareEndMonth= YearMonth.parse(endMonth) + val compareStartMonth = YearMonth.parse(startMonth) + val compareEndMonth = YearMonth.parse(endMonth) val actualStartMonth: YearMonth - (if (projectPlanStartEndMonth.actualStart == null){ + (if (projectPlanStartEndMonth.actualStart == null) { YearMonth.now().plusMonths(1) - }else{ + } else { YearMonth.from(projectPlanStartEndMonth.actualStart) }).also { actualStartMonth = it } - val queryStartMonth = if(compareStartMonth.isAfter(actualStartMonth)) compareStartMonth else actualStartMonth - val queryEndMonth = if(compareEndMonth.isAfter(YearMonth.now())) YearMonth.now() else compareEndMonth + val queryStartMonth = if (compareStartMonth.isAfter(actualStartMonth)) compareStartMonth else actualStartMonth + val queryEndMonth = if (compareEndMonth.isAfter(YearMonth.now())) YearMonth.now() else compareEndMonth val monthFormat = DateTimeFormatter.ofPattern("MMM yyyy", Locale.ENGLISH) val startDate = YearMonth.parse(queryStartMonth.toString(), DateTimeFormatter.ofPattern("yyyy-MM")) @@ -2353,7 +2401,7 @@ open class ReportService( val monthRange: MutableList> = ArrayList() var currentDate = startDate - if (currentDate == endDate){ + if (currentDate == endDate) { monthRange.add( mapOf( "display" to YearMonth.of(currentDate.year, currentDate.month).format(monthFormat), @@ -2638,7 +2686,7 @@ open class ReportService( } // CellUtil.setCellStyleProperty(panlCellTitle, "borderBottom", BorderStyle.DOUBLE) // CellUtil.setCellStyleProperty(panlCell, "borderBottom", BorderStyle.DOUBLE) - val profitAndLossRowNum = rowNum+1 + val profitAndLossRowNum = rowNum + 1 rowNum += 1 val uninvoicedAmountRow: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) @@ -2648,7 +2696,7 @@ open class ReportService( } val uninvoicedAmountCell = uninvoicedAmountRow.getCell(1) ?: uninvoicedAmountRow.createCell(1) uninvoicedAmountCell.apply { - setCellValue(if ((info.getValue("expectedTotalFee") as Double) < 0.0 ) 0.0 else info.getValue("expectedTotalFee") as Double) + setCellValue(if ((info.getValue("expectedTotalFee") as Double) < 0.0) 0.0 else info.getValue("expectedTotalFee") as Double) cellStyle.dataFormat = accountingStyle } @@ -2660,7 +2708,7 @@ open class ReportService( } val projectedPnLCell = projectedPnLRow.getCell(1) ?: projectedPnLRow.createCell(1) projectedPnLCell.apply { - cellFormula = "B${profitAndLossRowNum}+B${profitAndLossRowNum+1}" + cellFormula = "B${profitAndLossRowNum}+B${profitAndLossRowNum + 1}" cellStyle.dataFormat = accountingStyle } @@ -3038,10 +3086,11 @@ open class ReportService( } // if (timesheets.isNotEmpty()) { - val combinedTeamCodeColNumber = grades.size + val combinedTeamCodeColNumber = grades.size * 2 val sortedGrades = grades.sortedBy { it.id } val sortedTeams = teams.sortedBy { it.id } + logger.info(timesheets.filter { it.project?.teamLead?.team?.id != it.staff?.team?.id }.size) val groupedTimesheets = timesheets .filter { it.project?.teamLead?.team?.id != it.staff?.team?.id } .groupBy { timesheetEntry -> @@ -3095,8 +3144,12 @@ open class ReportService( createCell(1).apply { setCellValue(team.code) - cellStyle = normalFontWithBorderStyle - CellUtil.setAlignment(this, HorizontalAlignment.CENTER) + + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } } } @@ -3113,8 +3166,22 @@ open class ReportService( sortedGrades.forEach { grade: Grade -> createCell(columnIndex++).apply { setCellValue(grade.name) - cellStyle = boldFontWithBorderStyle - CellUtil.setAlignment(this, HorizontalAlignment.CENTER) + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } + } + + createCell(columnIndex++).apply { + val cellValue = grade.name.trim().substring(0, 1) + grade.name.trim() + .substring(grade.name.length - 1) + " - Cost Adjusted by Salary Point" + setCellValue(cellValue) + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } } } @@ -3139,8 +3206,11 @@ open class ReportService( columnIndex = 0 createCell(columnIndex++).apply { setCellValue(chargedTeam.code) - cellStyle = normalFontWithBorderStyle - CellUtil.setAlignment(this, HorizontalAlignment.CENTER) + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } } var totalSalary = 0.0 @@ -3159,22 +3229,47 @@ open class ReportService( grade.id )]?.sumOf { it.getValue("salary") } ?: 0.0 - cellStyle = normalFontWithBorderStyle - cellStyle.dataFormat = accountingStyle + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } + } + + createCell(columnIndex++).apply { + setCellValue( + groupedTimesheets[Triple( + team.id, + chargedTeam.id, + grade.id + )]?.sumOf { it.getValue("manHour") * it.getValue("salary") } ?: 0.0) + + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + 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})" - cellStyle = boldFontWithBorderStyle - cellStyle.dataFormat = accountingStyle + + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } } createCell(columnIndex).apply { setCellValue(totalSalary) - cellStyle = boldFontWithBorderStyle - cellStyle.dataFormat = accountingStyle + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } } } } @@ -3195,6 +3290,13 @@ open class ReportService( cellStyle = normalFontWithBorderStyle cellStyle.dataFormat = accountingStyle } + + createCell(columnIndex++).apply { + val currentCellLetter = CellReference.convertNumToColString(this.columnIndex) + cellFormula = "sum(${currentCellLetter}${startRow}:${currentCellLetter}${endRow})" + cellStyle = normalFontWithBorderStyle + cellStyle.dataFormat = accountingStyle + } } createCell(columnIndex++).apply { diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt index 29d1c28..68ccecf 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt @@ -43,6 +43,35 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri return leaveService.saveLeave(parsedEntries) } + @PostMapping("saveTimeLeave") + fun newTimeLeave(@Valid @RequestBody recordTimeLeave: Map>): Map> { + val parsedEntries = kotlin.runCatching { + recordTimeLeave.mapKeys { (dateString) -> LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE) } + }.getOrElse { throw BadRequestException() } + + val timesheets = parsedEntries.mapValues { (_, entries) -> entries.mapNotNull { timeLeaveEntry -> timeLeaveEntry.toTimeEntry() } } + val leaves = parsedEntries.mapValues { (_, entries) -> entries.mapNotNull { timeLeaveEntry -> timeLeaveEntry.toLeaveEntry() } } + + val savedTimesheets = timesheetsService.saveTimesheet(timesheets) + val savedLeaves = leaveService.saveLeave(leaves) + + val newMap = mutableMapOf>() + savedTimesheets.forEach { (date, entries) -> + if (!newMap.containsKey(date)) { + newMap[date] = mutableListOf() + } + newMap[date]!!.addAll(entries.map { e -> e.toTimeLeaveEntry() }) + } + savedLeaves.forEach { (date, entries) -> + if (!newMap.containsKey(date)) { + newMap[date] = mutableListOf() + } + newMap[date]!!.addAll(entries.map { e -> e.toTimeLeaveEntry() }) + } + + return newMap + } + @GetMapping fun getTimesheetEntry(): Map> { return timesheetsService.getTimesheet() diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt index af3a0c5..5b5581f 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt @@ -5,4 +5,18 @@ data class LeaveEntry( val leaveTypeId: Long?, val inputHours: Double, val remark: String? -) +) { + fun toTimeLeaveEntry(): TimeLeaveEntry { + return TimeLeaveEntry( + type = "leaveEntry", + id = this.id, + leaveTypeId = this.leaveTypeId, + inputHours = this.inputHours, + remark = this.remark, + projectId = null, + taskGroupId = null, + taskId = null, + otHours = null, + ) + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeEntry.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeEntry.kt index 6af3c26..8812f75 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeEntry.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeEntry.kt @@ -9,4 +9,18 @@ data class TimeEntry( val inputHours: Double?, val otHours: Double?, val remark: String? -) \ No newline at end of file +) { + fun toTimeLeaveEntry(): TimeLeaveEntry { + return TimeLeaveEntry( + type = "timeEntry", + id = this.id, + projectId = this.projectId, + taskGroupId = this.taskGroupId, + taskId = this.taskId, + inputHours = this.inputHours, + otHours = this.otHours, + remark = this.remark, + leaveTypeId = null + ) + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeLeaveEntry.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeLeaveEntry.kt new file mode 100644 index 0000000..e929bfa --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeLeaveEntry.kt @@ -0,0 +1,49 @@ +package com.ffii.tsms.modules.timesheet.web.models + +import jakarta.validation.constraints.AssertTrue + +data class TimeLeaveEntry( + val id: Long, + val type: String, + val projectId: Long?, + val leaveTypeId: Long?, + val taskGroupId: Long?, + val taskId: Long?, + val inputHours: Double?, + val otHours: Double?, + val remark: String?, +) { + @AssertTrue + private fun isValid(): Boolean { + return type == "timeEntry" || (type == "leaveEntry" && leaveTypeId != null) + } + + fun toTimeEntry(): TimeEntry? { + if (type == "leaveEntry") { + return null + } + + return TimeEntry( + id, + projectId, + taskGroupId, + taskId, + inputHours, + otHours, + remark, + ) + } + + fun toLeaveEntry(): LeaveEntry? { + if (type == "timeEntry" || inputHours == null) { + return null + } + + return LeaveEntry( + id, + leaveTypeId, + inputHours, + remark, + ) + } +} \ No newline at end of file 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 e3c005a..1a4889b 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