@@ -684,7 +684,7 @@ open class ReportService(
val row10: Row = sheet.getRow(rowNum)
val cell6 = row10.createCell(2)
cell6.apply {
cellFormula = "O ${lastRowNum}"
cellFormula = "P ${lastRowNum}"
cellStyle.dataFormat = accountingStyle
}
@@ -692,7 +692,7 @@ open class ReportService(
val row11: Row = sheet.getRow(rowNum)
val cell7 = row11.createCell(2)
cell7.apply {
cellFormula = "P ${lastRowNum}"
cellFormula = "Q ${lastRowNum}"
cellStyle.dataFormat = accountingStyle
}
@@ -1182,7 +1182,7 @@ open class ReportService(
println("----leaves-----")
println(leaves)
// result = timesheet record mapped
var result: Map<String, Any> = mapOf()
var result: Map<String, Any> = mapOf()
if (timesheets.isNotEmpty()) {
projectList = timesheets.map { "${it["code"]}\n ${it["name"]}" }.toList().distinct()
result = timesheets.groupBy(
@@ -1333,7 +1333,7 @@ open class ReportService(
// }
// }
if (totalConsumed.isNotEmpty()) {
totalConsumed.forEach{ t ->
totalConsumed.forEach { t ->
val total = t["totalConsumed"] as Double
if (total > 8.0) {
normalConsumed += 8.0
@@ -1964,16 +1964,18 @@ open class ReportService(
)
return jdbcDao.queryForList(sql.toString(), args)
}
open fun getTotalConsumed(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder("SELECT"
+ " CAST(DATE_FORMAT(t.recordDate, '%d') AS SIGNED) AS recordDate, "
+ " sum(t.normalConsumed) + sum(t.otConsumed) as totalConsumed "
+ " from timesheet t "
+ " left join project p on p.id = t.projectId "
+ " where t.staffId = :staffId "
+ " and t.recordDate BETWEEN :startDate and :endDate "
+ " group by t.recordDate "
+ " order by t.recordDate; "
val sql = StringBuilder(
"SELECT"
+ " CAST(DATE_FORMAT(t.recordDate, '%d') AS SIGNED) AS recordDate, "
+ " sum(t.normalConsumed) + sum(t.otConsumed) as totalConsumed "
+ " from timesheet t "
+ " left join project p on p.id = t.projectId "
+ " where t.staffId = :staffId "
+ " and t.recordDate BETWEEN :startDate and :endDate "
+ " group by t.recordDate "
+ " order by t.recordDate; "
)
return jdbcDao.queryForList(sql.toString(), args)
}
@@ -2054,42 +2056,43 @@ open class ReportService(
}
open fun getProjectResourceOverconsumptionReport(args: Map<String, Any>): List<Map<String, Any>> {
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 * 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 * 0.8 as budgetConsumptionRate, "
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate, "
+ " case "
+ " when (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit and (sum(t.consumedBudget) / p.expectedTotalFee) <= 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' "
+ " when (sum(t.consumedBudget) / p.expectedTotalFee) >= 1 "
+ " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= 1 "
+ " then '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' "
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 * 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 * 0.8 as budgetConsumptionRate, "
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate, "
+ " case "
+ " when (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit and (sum(t.consumedBudget) / p.expectedTotalFee) <= 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' "
+ " when (sum(t.consumedBudget) / p.expectedTotalFee) >= 1 "
+ " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= 1 "
+ " then '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' "
)
var statusFilter: String = ""
var statusFilter: String = ""
if (args != null) {
if (args.containsKey("teamId"))
sql.append(" and t.id = :teamId")
@@ -2104,14 +2107,16 @@ open class ReportService(
" and (sum(t.consumedBudget) / p.expectedTotalFee) <= 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 "
"All" -> " having " +
" (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit " +
" or (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit "
else -> ""
}
}
sql.append(" group by p.code, p.name, tm.code, c.code, c.name, ss.code, ss.name,p.expectedTotalFee, p.totalManhour, p.expectedTotalFee ")
sql.append(statusFilter)
}
sql.append(" group by p.code, p.name, tm.code, c.code, c.name, ss.code, ss.name,p.expectedTotalFee, p.totalManhour, p.expectedTotalFee ")
sql.append(statusFilter)
return jdbcDao.queryForList(sql.toString(), args)
}
@@ -2212,7 +2217,7 @@ open class ReportService(
val staffInfoList = mutableListOf<Map<String, Any>>()
// println("manHoursSpent------- ${manHoursSpent}")
for (financialYear in financialYears){
for (financialYear in financialYears) {
// println("${financialYear.start.year}-${financialYear.start.monthValue} - ${financialYear.end.year}-${financialYear.end.monthValue}")
println("financialYear--------- ${financialYear.start} - ${financialYear.end}")
}
@@ -2336,7 +2341,11 @@ open class ReportService(
// For Calculating the Financial Year
data class FinancialYear(val start: YearMonth, val end: YearMonth, val hourlyRate: Double)
fun getFinancialYearDates(startDate: YearMonth, endDate: YearMonth, financialYearStartMonth: Int): Array<FinancialYear> {
fun getFinancialYearDates(
startDate: YearMonth,
endDate: YearMonth,
financialYearStartMonth: Int
): Array<FinancialYear> {
val financialYearDates = mutableListOf<FinancialYear>()
var currentYear = startDate.year
@@ -3101,9 +3110,9 @@ open class ReportService(
}
// if (timesheets.isNotEmpty()) {
val combinedTeamCodeColNumber = grades.size * 2
var combinedTeamCodeColNumber = grades.size * 2
val sortedGrades = grades.sortedBy { it.id }
var sortedTeams = teams.sortedBy { it.id }.toMutableList()
val sortedTeams = teams.sortedBy { it.id }.toMutableList()
if (teamId.lowercase() != "all") {
val teamIndex = sortedTeams.indexOfFirst { it.id == teamId.toLong() }
sortedTeams.add(0, sortedTeams.removeAt(teamIndex))
@@ -3205,12 +3214,20 @@ open class ReportService(
createCell(columnIndex++).apply {
setCellValue("Total Manhour by Team")
cellStyle = boldFontWithBorderStyle
val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle)
cellStyle = cloneStyle.apply {
alignment = HorizontalAlignment.CENTER
}
}
createCell(columnIndex).apply {
setCellValue("Total Cost Adjusted by Salary Point by Team")
cellStyle = boldFontWithBorderStyle
val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle)
cellStyle = cloneStyle.apply {
alignment = HorizontalAlignment.CENTER
}
}
}
@@ -3342,39 +3359,241 @@ open class ReportService(
// }
conditionalFormattingNegative(sheet)
// -------------------------- sheet 1 (By Detail) -------------------------- //
// sheet = workbook.getSheetAt(1)
//
// val groupedTimesheets2 = timesheets
// .filter { it.project?.teamLead?.team?.id != it.staff?.team?.id }
// .groupBy { timesheetEntry ->
// Pair(
// timesheetEntry.project?.id,
// timesheetEntry.staff?.id
// )
// }
// .mapValues { (_, timesheetEntries) ->
// timesheetEntries.map { timesheet ->
// if (timesheet.normalConsumed != null) {
// mutableMapOf<String, Double>().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())
// }
// } else if (timesheet.otConsumed != null) {
// mutableMapOf<String, Double>().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())
// }
// } else {
// mutableMapOf<String, Double>().apply {
// this["manHour"] = 0.0
// this["salary"] = 0.0
// }
// }
// }
// }
// -------------------------- sheet 1 (Individual) -------------------------- //
sheet = workbook.getSheetAt(1)
rowIndex = 1
columnIndex = 2
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(convertReportMonth)
}
val groupedTimesheetsIndividual = timesheets
.filter { it.project?.teamLead?.team?.id != it.staff?.team?.id }
.groupBy { timesheetEntry ->
Pair(
timesheetEntry.project?.id,
timesheetEntry.staff?.id
)
}
.mapValues { (_, timesheetEntries) ->
timesheetEntries.map { timesheet ->
if (timesheet.normalConsumed != null) {
mutableMapOf<String, Double>().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())
}
} else if (timesheet.otConsumed != null) {
mutableMapOf<String, Double>().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())
}
} else {
mutableMapOf<String, Double>().apply {
this["manHour"] = 0.0
this["salary"] = 0.0
}
}
}
}
if (sortedTeams.isNotEmpty() && sortedTeams.size > 1) {
rowIndex = 3
sortedTeams.forEach { team: Team ->
// not his/her team staffs
val staffs = timesheets
.filter { it.project?.teamLead?.team?.id != it.staff?.team?.id && (it.project?.teamLead?.id != team.staff.id || it.staff?.team?.id != team.id) }
.mapNotNull { it.staff }
.sortedBy { it.staffId }
.distinct()
// his/her team projects
val projects = timesheets
.filter { it.project?.teamLead?.team?.id != it.staff?.team?.id && it.project?.teamLead?.id == team.staff.id }
.mapNotNull { it.project }
.sortedByDescending { it.code }
.distinct()
// Team
if (!projects.isNullOrEmpty()) {
sheet.createRow(rowIndex++).apply {
createCell(0).apply {
setCellValue("Team to be charged:")
cellStyle = boldFontWithBorderStyle
CellUtil.setAlignment(this, HorizontalAlignment.LEFT)
}
val rangeAddress = CellRangeAddress(this.rowNum, this.rowNum, 1, 2 + staffs.size)
sheet.addMergedRegion(rangeAddress)
RegionUtil.setBorderTop(BorderStyle.THIN, rangeAddress, sheet)
RegionUtil.setBorderLeft(BorderStyle.THIN, rangeAddress, sheet)
RegionUtil.setBorderRight(BorderStyle.THIN, rangeAddress, sheet)
RegionUtil.setBorderBottom(BorderStyle.THIN, rangeAddress, sheet)
createCell(1).apply {
setCellValue(team.code)
val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(normalFontWithBorderStyle)
cellStyle = cloneStyle.apply {
alignment = HorizontalAlignment.CENTER
}
}
}
// Staffs
sheet.createRow(rowIndex++).apply {
columnIndex = 0
createCell(columnIndex++).apply {
setCellValue("")
cellStyle = boldFontWithBorderStyle
}
staffs.forEach { staff: Staff ->
createCell(columnIndex++).apply {
setCellValue("${staff.staffId} - ${staff.name} (${staff.team.code})")
val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle)
cellStyle = cloneStyle.apply {
alignment = HorizontalAlignment.CENTER
}
sheet.setColumnWidth(this.columnIndex, 50 * 256)
}
}
createCell(columnIndex++).apply {
setCellValue("Total Manhour by Project")
val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle)
cellStyle = cloneStyle.apply {
alignment = HorizontalAlignment.CENTER
}
sheet.setColumnWidth(this.columnIndex, 50 * 256)
}
createCell(columnIndex).apply {
setCellValue("Total Cost Adjusted by Salary Point by Project")
val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle)
cellStyle = cloneStyle.apply {
alignment = HorizontalAlignment.CENTER
}
sheet.setColumnWidth(this.columnIndex, 50 * 256)
}
}
// Project + Manhour
val startRow = rowIndex + 1
var endRow = rowIndex
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
}
}
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(
project.id,
staff.id
)]?.sumOf { 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})"
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
}
}
}
}
}
}
// Total Manhour & Cost
sheet.createRow(rowIndex).apply {
columnIndex = 0
createCell(columnIndex++).apply {
setCellValue("")
cellStyle = boldFontWithBorderStyle
}
staffs.forEach { staff: Staff ->
createCell(columnIndex++).apply {
setCellValue("")
cellStyle = boldFontWithBorderStyle
}
}
createCell(columnIndex++).apply {
val lastCellLetter = CellReference.convertNumToColString(this.columnIndex)
cellFormula = "sum(${lastCellLetter}${startRow}:${lastCellLetter}${endRow})"
val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle)
cellStyle = cloneStyle.apply {
dataFormat = accountingStyle
}
}
createCell(columnIndex).apply {
val lastCellLetter = CellReference.convertNumToColString(this.columnIndex)
cellFormula = "sum(${lastCellLetter}${startRow}:${lastCellLetter}${endRow})"
val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle)
cellStyle = cloneStyle.apply {
dataFormat = accountingStyle
}
}
}
rowIndex += 2
}
}
}
conditionalFormattingNegative(sheet)
return workbook
}