|
|
@@ -66,6 +66,7 @@ open class ReportService( |
|
|
|
private val COMPLETION_PROJECT = "templates/report/AR05_Project Completion Report.xlsx" |
|
|
|
private val CROSS_TEAM_CHARGE_REPORT = "templates/report/Cross Team Charge Report.xlsx" |
|
|
|
|
|
|
|
private val chargeFee = 1.15 |
|
|
|
private fun conditionalFormattingNegative(sheet: Sheet) { |
|
|
|
// Create a conditional formatting rule |
|
|
|
val sheetCF = sheet.sheetConditionalFormatting |
|
|
@@ -778,7 +779,7 @@ open class ReportService( |
|
|
|
} |
|
|
|
|
|
|
|
createCell(2).apply { |
|
|
|
setCellValue((project.expectedTotalFee ?: 0.0) - (project.subContractFee ?: 0.0)) |
|
|
|
setCellValue(project.expectedTotalFee ?: 0.0) |
|
|
|
cellStyle.dataFormat = accountingStyle |
|
|
|
} |
|
|
|
} |
|
|
@@ -2117,46 +2118,31 @@ 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 - ifnull(p.subContractFee, 0)) * 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 - ifnull(p.subContractFee, 0)) * 0.8) as budgetConsumptionRate, " |
|
|
|
+ " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate, " |
|
|
|
+ " case " |
|
|
|
+ " when sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) >= 1 " |
|
|
|
+ " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= 1 " |
|
|
|
+ " then 'Overconsumption' " |
|
|
|
+ " when sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) >= :lowerLimit and sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) <= 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' " |
|
|
|
+ " else 'Within Budget' " |
|
|
|
+ " END as status " |
|
|
|
+ " from " |
|
|
|
+ " (SELECT t.*, (t.normalConsumed + COALESCE(t.otConsumed, 0)) * sal.hourlyRate as consumedBudget " |
|
|
|
+ " FROM " |
|
|
|
+ " ( " |
|
|
|
+ " SELECT t.id AS tid, max(se.date) AS max_date " |
|
|
|
+ " FROM timesheet t " |
|
|
|
+ " INNER JOIN salary_effective se ON se.staffId = t.staffId AND se.date <= t.recordDate " |
|
|
|
+ " GROUP BY t.id " |
|
|
|
+ " ) max_se " |
|
|
|
+ " LEFT JOIN timesheet t ON t.id = max_se.tid " |
|
|
|
+ " LEFT JOIN staff s on s.id = t.staffId " |
|
|
|
+ " inner join salary_effective se on se.date = max_se.max_date " |
|
|
|
+ " left join salary sal on se.salaryId = sal.salaryPoint) 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' " |
|
|
|
" 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 - ifnull(p.subContractFee, 0)) * 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 - ifnull(p.subContractFee, 0)) * 0.8) as budgetConsumptionRate " |
|
|
|
+ " , sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate " |
|
|
|
+ " , case when sum(t.consumedBudget) / ((p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8) >= 1 " |
|
|
|
+ " or (sum(t.normalConsumed + COALESCE(t.otConsumed,0)) / COALESCE(p.totalManhour,0)) >= 1 " |
|
|
|
+ " then'Overconsumption' when sum(t.consumedBudget)/ ((p.expectedTotalFee - ifnull(p.subContractFee,0)) * 0.8) >= :lowerLimit " |
|
|
|
+ " and sum(t.consumedBudget)/ ((p.expectedTotalFee - ifnull(p.subContractFee,0)) * 0.8) <= 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' else'Within Budget' END as status " |
|
|
|
+ " from " |
|
|
|
+ " ( " |
|
|
|
+ " SELECT t.*, (t.normalConsumed+COALESCE(t.otConsumed, 0)) * sal.hourlyRate as consumedBudget " |
|
|
|
+ " FROM timesheet t " |
|
|
|
+ " INNER JOIN salary_effective se ON se.staffId = t.staffId AND t.recordDate BETWEEN se.startDate AND se.endDate " |
|
|
|
+ " LEFT JOIN salary sal on se.salaryId = sal.salaryPoint " |
|
|
|
+ " ) 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 = "" |
|
|
|
if (args != null) { |
|
|
@@ -3699,21 +3685,38 @@ open class ReportService( |
|
|
|
rowIndex = 3 |
|
|
|
sortedTeams.forEach { team: Team -> |
|
|
|
// not his/her team staffs |
|
|
|
val staffs = timesheets |
|
|
|
var staffs = timesheets |
|
|
|
.filter { it.project?.teamLead?.team?.id == team.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 == team.id && it.project?.teamLead?.team?.id != it.staff?.team?.id } |
|
|
|
var tempTimesheets = timesheets |
|
|
|
.filter { |
|
|
|
it.project?.teamLead?.team?.id == team.id |
|
|
|
&& it.project?.teamLead?.team?.id != it.staff?.team?.id |
|
|
|
} |
|
|
|
|
|
|
|
if (teamId.lowercase() != "all" && teamId.toLong() != team.id) { |
|
|
|
staffs = staffs.filter { |
|
|
|
it.team.id == teamId.toLong() |
|
|
|
} |
|
|
|
|
|
|
|
tempTimesheets = tempTimesheets.filter { |
|
|
|
it.staff?.team?.id == teamId.toLong() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
val projects = tempTimesheets |
|
|
|
.mapNotNull { it.project } |
|
|
|
.sortedByDescending { it.code } |
|
|
|
.distinct() |
|
|
|
|
|
|
|
// Team |
|
|
|
if (projects.isNotEmpty()) { |
|
|
|
if (projects.isNotEmpty() && staffs.isNotEmpty()) { |
|
|
|
sheet.createRow(rowIndex++).apply { |
|
|
|
createCell(0).apply { |
|
|
|
setCellValue("Team to be charged:") |
|
|
@@ -3787,7 +3790,7 @@ open class ReportService( |
|
|
|
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 (teamId.lowercase() == "all" || teamId.toLong() == project.teamLead?.team?.id || teamId.toLong() == team.id || team.id == project.teamLead?.team?.id) { |
|
|
|
// if (team.id == project.teamLead?.team?.id) { |
|
|
|
endRow++ |
|
|
|
sheet.createRow(rowIndex++).apply { |
|
|
@@ -3836,7 +3839,7 @@ open class ReportService( |
|
|
|
} |
|
|
|
|
|
|
|
createCell(columnIndex).apply { |
|
|
|
setCellValue(totalSalary) |
|
|
|
setCellValue(totalSalary * chargeFee) |
|
|
|
val cloneStyle = workbook.createCellStyle() |
|
|
|
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) |
|
|
|
cellStyle = cloneStyle.apply { |
|
|
|