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 b3756b9..53d38ed 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 @@ -63,6 +63,30 @@ open class ReportService( private val COMPLETION_PROJECT = "templates/report/AR05_Project Completion Report.xlsx" // ==============================|| GENERATE REPORT ||============================== // + fun generalCreateReportIndexed( // just loop through query records one by one, return rowIndex + sheet: Sheet, + result: List>, + startRow: Int, + startColumn: Int + ): Int { + var rowIndex = startRow + var columnIndex = startColumn + result.forEachIndexed { index, obj -> + var tempCell = sheet.getRow(rowIndex).createCell(columnIndex) + tempCell.setCellValue((index + 1).toDouble()) + val keys = obj.keys.toList() + keys.forEachIndexed { keyIndex, key -> + tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) ?: sheet.getRow(rowIndex) + .createCell(columnIndex + keyIndex + 1) + when (obj[key]) { + is Double -> tempCell.setCellValue(obj[key] as Double) + else -> tempCell.setCellValue(obj[key] as String) + } + } + rowIndex++ + } + return rowIndex + } fun genFinancialStatusReport(teamLeadId: Long): ByteArray { @@ -1298,20 +1322,8 @@ open class ReportService( rowIndex = 5 columnIndex = 0 - result.forEachIndexed { index, obj -> - tempCell = sheet.getRow(rowIndex).createCell(columnIndex) - tempCell.setCellValue((index + 1).toDouble()) - val keys = obj.keys.toList() - keys.forEachIndexed { keyIndex, key -> - tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) ?: sheet.getRow(rowIndex) - .createCell(columnIndex + keyIndex + 1) - when (obj[key]) { - is Double -> tempCell.setCellValue(obj[key] as Double) - else -> tempCell.setCellValue(obj[key] as String) - } - } - rowIndex++ - } + + generalCreateReportIndexed(sheet, result, rowIndex, columnIndex) return workbook } @@ -1350,20 +1362,9 @@ open class ReportService( rowIndex = 6 columnIndex = 0 - result.forEachIndexed { index, obj -> - tempCell = sheet.getRow(rowIndex).createCell(columnIndex) - tempCell.setCellValue((index + 1).toDouble()) - val keys = obj.keys.toList() - keys.forEachIndexed { keyIndex, key -> - tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) ?: sheet.getRow(rowIndex) - .createCell(columnIndex + keyIndex + 1) - when (obj[key]) { - is Double -> tempCell.setCellValue(obj[key] as Double) - else -> tempCell.setCellValue(obj[key] as String) - } - } - rowIndex++ - } + +// val currRow = generalCreateReportIndexed(sheet, result, rowIndex, columnIndex) + generalCreateReportIndexed(sheet, result, rowIndex, columnIndex) val sheetCF = sheet.sheetConditionalFormatting val rule1 = sheetCF.createConditionalFormattingRule("AND(J7 >= $lowerLimit, J7 <= 1)") @@ -1529,111 +1530,107 @@ open class ReportService( ) return jdbcDao.queryForList(sql.toString(), args) } - open fun getProjectCompletionReport(args: Map): List> { - val sql = StringBuilder( - "select" - + " result.code, " - + " result.name, " - + " result.teamCode, " - + " result.custCode, " - ) + val sql = StringBuilder("select" + + " result.code, " + + " result.name, " + + " result.teamCode, " + + " result.custCode, " ) if (args.get("outstanding") as Boolean) { sql.append(" result.projectFee - (result.totalBudget - COALESCE(i.issueAmount , 0) + COALESCE(i.issueAmount, 0) - COALESCE(i.paidAmount, 0)) as `Receivable Remained`, ") } sql.append( " DATE_FORMAT(result.actualEnd, '%d/%m/%Y') as actualEnd " - + " from ( " - + " SELECT " - + " pt.project_id, " - + " min(p.code) as code, " - + " min(p.name) as name, " - + " min(t.code) as teamCode, " - + " min(c.code) as custCode, " - + " min(p.actualEnd) as actualEnd, " - + " min(p.expectedTotalFee) as projectFee, " - + " sum(COALESCE(tns.totalConsumed*sal.hourlyRate, 0)) as totalBudget " - + " FROM ( " - + " SELECT " - + " t.staffId, " - + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " - + " t.projectTaskId AS taskId " - + " FROM timesheet t " - + " LEFT JOIN staff s ON t.staffId = s.id " - + " LEFT JOIN team te on s.teamId = te.id " - + " GROUP BY t.staffId, t.projectTaskId " - + " order by t.staffId " - + " ) AS tns " - + " right join project_task pt ON tns.taskId = pt.id " - + " left join project p on p.id = pt.project_id " - + " left JOIN staff s ON p.teamLead = s.id " - + " left join salary sal on s.salaryId = sal.salaryPoint " - + " left JOIN team t ON s.teamId = t.id " - + " left join customer c on c.id = p.customerId " - + " where p.deleted = false " - + " and p.status = 'Completed' " - + " and p.actualEnd BETWEEN :startDate and :endDate " - + " group by pt.project_id " - + " ) as result " - + " left join invoice i on result.code = i.projectCode " - + " order by result.actualEnd " + + " from ( " + + " SELECT " + + " pt.project_id, " + + " min(p.code) as code, " + + " min(p.name) as name, " + + " min(t.code) as teamCode, " + + " min(c.code) as custCode, " + + " min(p.actualEnd) as actualEnd, " + + " min(p.expectedTotalFee) as projectFee, " + + " sum(COALESCE(tns.totalConsumed*sal.hourlyRate, 0)) as totalBudget " + + " FROM ( " + + " SELECT " + + " t.staffId, " + + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " + + " t.projectTaskId AS taskId " + + " FROM timesheet t " + + " LEFT JOIN staff s ON t.staffId = s.id " + + " LEFT JOIN team te on s.teamId = te.id " + + " GROUP BY t.staffId, t.projectTaskId " + + " order by t.staffId " + + " ) AS tns " + + " right join project_task pt ON tns.taskId = pt.id " + + " left join project p on p.id = pt.project_id " + + " left JOIN staff s ON p.teamLead = s.id " + + " left join salary sal on s.salaryId = sal.salaryPoint " + + " left JOIN team t ON s.teamId = t.id " + + " left join customer c on c.id = p.customerId " + + " where p.deleted = false " + + " and p.status = 'Completed' " + + " and p.actualEnd BETWEEN :startDate and :endDate " + + " group by pt.project_id " + + " ) as result " + + " left join invoice i on result.code = i.projectCode " + + " order by result.actualEnd " ) return jdbcDao.queryForList(sql.toString(), args) } open fun getProjectResourceOverconsumptionReport(args: Map): List> { - val sql = StringBuilder( - "WITH teamNormalConsumed AS (" - + " SELECT " - + " s.teamId, " - + " pt.project_id, " - + " SUM(tns.totalConsumed) AS totalConsumed, " - + " sum(tns.totalBudget) as totalBudget " - + " FROM ( " - + " SELECT " - + " t.staffId, " - + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " - + " t.projectTaskId AS taskId, " - + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) * min(sal.hourlyRate) as totalBudget " - + " FROM timesheet t " - + " LEFT JOIN staff s ON t.staffId = s.id " - + " left join salary sal on sal.salaryPoint = s.salaryId " - + " GROUP BY t.staffId, t.projectTaskId " - + " ) AS tns " - + " INNER JOIN project_task pt ON tns.taskId = pt.id " - + " JOIN staff s ON tns.staffId = s.id " - + " JOIN team t ON s.teamId = t.id " - + " GROUP BY teamId, project_id " - + " ) " - + " SELECT " - + " p.code, " - + " p.name, " - + " t.code as team, " - + " c.code as client, " - + " p.expectedTotalFee * 0.8 as plannedBudget, " - + " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, " - + " COALESCE(p.totalManhour, 0) as plannedManhour, " - + " COALESCE(tns.totalConsumed, 0) as actualConsumedManhour, " - + " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) as budgetConsumptionRate, " - + " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) as manhourConsumptionRate, " - + " CASE " - + " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit 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 " - + " then 'Potential Overconsumption' " - + " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " - + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1 " - + " then 'Overconsumption' " - + " else 'Within Budget' " - + " END as status " - + " FROM project p " - + " LEFT JOIN team t ON p.teamLead = t.teamLead " - + " LEFT JOIN staff s ON p.teamLead = s.id " - + " LEFT JOIN salary sa ON s.salaryId = sa.salaryPoint " - + " LEFT JOIN customer c ON p.customerId = c.id " - + " left join teamNormalConsumed tns on tns.project_id = p.id " - + " WHERE p.deleted = false " - + " and p.status = 'On-going' " + val sql = StringBuilder("WITH teamNormalConsumed AS (" + + " SELECT " + + " s.teamId, " + + " pt.project_id, " + + " SUM(tns.totalConsumed) AS totalConsumed, " + + " sum(tns.totalBudget) as totalBudget " + + " FROM ( " + + " SELECT " + + " t.staffId, " + + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " + + " t.projectTaskId AS taskId, " + + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) * min(sal.hourlyRate) as totalBudget " + + " FROM timesheet t " + + " LEFT JOIN staff s ON t.staffId = s.id " + + " left join salary sal on sal.salaryPoint = s.salaryId " + + " GROUP BY t.staffId, t.projectTaskId " + + " ) AS tns " + + " INNER JOIN project_task pt ON tns.taskId = pt.id " + + " JOIN staff s ON tns.staffId = s.id " + + " JOIN team t ON s.teamId = t.id " + + " GROUP BY teamId, project_id " + + " ) " + + " SELECT " + + " p.code, " + + " p.name, " + + " t.code as team, " + + " c.code as client, " + + " p.expectedTotalFee * 0.8 as plannedBudget, " + + " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, " + + " COALESCE(p.totalManhour, 0) as plannedManhour, " + + " COALESCE(tns.totalConsumed, 0) as actualConsumedManhour, " + + " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) as budgetConsumptionRate, " + + " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) as manhourConsumptionRate, " + + " CASE " + + " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit 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 " + + " then 'Potential Overconsumption' " + + " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1 " + + " then 'Overconsumption' " + + " else 'Within Budget' " + + " END as status " + + " FROM project p " + + " LEFT JOIN team t ON p.teamLead = t.teamLead " + + " LEFT JOIN staff s ON p.teamLead = s.id " + + " LEFT JOIN salary sa ON s.salaryId = sa.salaryPoint " + + " LEFT JOIN customer c ON p.customerId = c.id " + + " left join teamNormalConsumed tns on tns.project_id = p.id " + + " WHERE p.deleted = false " + + " and p.status = 'On-going' " ) if (args != null) { var statusFilter: String = "" @@ -2126,10 +2123,10 @@ open class ReportService( return workbook } - fun getCostAndExpense(clientId: Long?, teamId: Long?): List> { + fun getCostAndExpense(clientId: Long?, teamId: Long?): List>{ val sql = StringBuilder( " with cte_timesheet as ( " - + " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.salaryPoint, s2.hourlyRate, t.staffId," + + " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.salaryPoint, s2.hourlyRate, t.staffId," + " t.recordDate" + " from timesheet t" + " left join project_task pt on pt.id = t.projectTaskId" @@ -2151,13 +2148,13 @@ open class ReportService( + " left join team t2 on t2.id = s.teamId" + " where ISNULL(p.code) = False" ) - if (clientId != null) { + if(clientId != null){ sql.append( " and c.id = :clientId " ) } - if (teamId != null) { + if(teamId != null){ sql.append( " and p.teamLead = :teamId " ) @@ -2176,9 +2173,9 @@ open class ReportService( val queryList = jdbcDao.queryForList(sql.toString(), args) val costAndExpenseList = mutableListOf>() - for (item in queryList) { + for(item in queryList){ val hourlyRate = (item.getValue("hourlyRate") as BigDecimal).toDouble() - if (item["code"] !in costAndExpenseList) { + if(item["code"] !in costAndExpenseList){ costAndExpenseList.add( mapOf( "code" to item["code"], @@ -2186,27 +2183,22 @@ open class ReportService( "client" to item["client"], "teamLead" to item["teamLead"], "budget" to item["expectedTotalFee"], - "totalManhours" to item["normalConsumed"] as Double + item["otConsumed"] as Double, - "manhourExpenditure" to (hourlyRate * item["normalConsumed"] as Double) - + (hourlyRate * item["otConsumed"] as Double * otFactor) + "totalManhours" to item["normalConsumed"] as Double + item["otConsumed"] as Double, + "manhourExpenditure" to (hourlyRate * item["normalConsumed"] as Double ) + + (hourlyRate * item["otConsumed"]as Double * otFactor) ) ) - } else { + }else{ val existingMap = costAndExpenseList.find { it.containsValue(item["code"]) }!! costAndExpenseList[costAndExpenseList.indexOf(existingMap)] = existingMap.toMutableMap().apply { - put( - "totalManhours", - get("manhours") as Double + (item["normalConsumed"] as Double + item["otConsumed"] as Double) - ) - put( - "manhourExpenditure", - get("manhourExpenditure") as Double + ((hourlyRate * item["normalConsumed"] as Double) - + (hourlyRate * item["otConsumed"] as Double * otFactor)) - ) + put("totalManhours", get("manhours") as Double + (item["normalConsumed"] as Double + item["otConsumed"] as Double)) + put("manhourExpenditure", get("manhourExpenditure") as Double + ((hourlyRate * item["normalConsumed"] as Double ) + + (hourlyRate * item["otConsumed"]as Double * otFactor))) } } } - val result = costAndExpenseList.map { item -> + val result = costAndExpenseList.map { + item -> val budget = (item["budget"] as? Double)?.times(0.8) ?: 0.0 val budgetRemain = budget - (item["manhourExpenditure"] as? Double ?: 0.0) val remainingPercent = (budgetRemain / budget) @@ -2224,7 +2216,7 @@ open class ReportService( teamId: Long?, clientId: Long?, budgetPercentage: Double? - ): Workbook { + ): Workbook{ val resource = ClassPathResource(templatePath) val templateInputStream = resource.inputStream val workbook: Workbook = XSSFWorkbook(templateInputStream) @@ -2242,9 +2234,9 @@ open class ReportService( rowNum = 2 val row2: Row = sheet.getRow(rowNum) val row2Cell = row2.getCell(2) - if (teamId == null) { + if(teamId == null){ row2Cell.setCellValue("All") - } else { + }else{ val sql = StringBuilder( " select t.id, t.code, t.name, concat(t.code, \" - \" ,t.name) as teamLead from team t where t.id = :teamId " ) @@ -2255,9 +2247,9 @@ open class ReportService( rowNum = 3 val row3: Row = sheet.getRow(rowNum) val row3Cell = row3.getCell(2) - if (clientId == null) { + if(clientId == null){ row3Cell.setCellValue("All") - } else { + }else{ val sql = StringBuilder( " select c.id, c.name from customer c where c.id = :clientId " ) @@ -2267,15 +2259,15 @@ open class ReportService( val filterList: List> - if (budgetPercentage != null) { + if(budgetPercentage != null){ filterList = costAndExpenseList.filter { ((it["budgetPercentage"] as? Double) ?: 0.0) > budgetPercentage } - } else { + }else{ filterList = costAndExpenseList } rowNum = 6 - for (item in filterList) { + for(item in filterList){ val index = filterList.indexOf(item) val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) val cell = row.getCell(0) ?: row.createCell(0) @@ -2320,13 +2312,13 @@ open class ReportService( val cell7 = row.getCell(7) ?: row.createCell(7) cell7.apply { - cellFormula = "F${rowNum + 1}-G${rowNum + 1}" + cellFormula = "F${rowNum+1}-G${rowNum+1}" } CellUtil.setCellStyleProperty(cell7, "dataFormat", accountingStyle) val cell8 = row.getCell(8) ?: row.createCell(8) cell8.apply { - cellFormula = "H${rowNum + 1}/F${rowNum + 1}" + cellFormula = "H${rowNum+1}/F${rowNum+1}" } CellUtil.setCellStyleProperty(cell8, "dataFormat", percentStyle) @@ -2336,17 +2328,11 @@ open class ReportService( return workbook } - fun genCostAndExpenseReport(request: costAndExpenseRequest): ByteArray { + fun genCostAndExpenseReport(request: costAndExpenseRequest): ByteArray{ val costAndExpenseList = getCostAndExpense(request.clientId, request.teamId) - val workbook: Workbook = createCostAndExpenseWorkbook( - COSTANDEXPENSE_REPORT, - costAndExpenseList, - request.teamId, - request.clientId, - request.budgetPercentage - ) + val workbook: Workbook = createCostAndExpenseWorkbook(COSTANDEXPENSE_REPORT, costAndExpenseList, request.teamId, request.clientId, request.budgetPercentage) val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() workbook.write(outputStream)