|
@@ -2,8 +2,8 @@ package com.ffii.tsms.modules.report.service |
|
|
|
|
|
|
|
|
import com.ffii.core.support.JdbcDao |
|
|
import com.ffii.core.support.JdbcDao |
|
|
import com.ffii.tsms.modules.data.entity.* |
|
|
import com.ffii.tsms.modules.data.entity.* |
|
|
import com.ffii.tsms.modules.data.entity.projections.SalaryEffectiveInfo |
|
|
|
|
|
import com.ffii.tsms.modules.data.service.SalaryEffectiveService |
|
|
import com.ffii.tsms.modules.data.service.SalaryEffectiveService |
|
|
|
|
|
import com.ffii.tsms.modules.data.service.SalaryEffectiveService.MonthlyStaffSalaryData |
|
|
import com.ffii.tsms.modules.project.entity.Invoice |
|
|
import com.ffii.tsms.modules.project.entity.Invoice |
|
|
import com.ffii.tsms.modules.project.entity.Milestone |
|
|
import com.ffii.tsms.modules.project.entity.Milestone |
|
|
import com.ffii.tsms.modules.project.entity.Project |
|
|
import com.ffii.tsms.modules.project.entity.Project |
|
@@ -156,11 +156,19 @@ open class ReportService( |
|
|
project: Project, |
|
|
project: Project, |
|
|
invoices: List<Invoice>, |
|
|
invoices: List<Invoice>, |
|
|
timesheets: List<Timesheet>, |
|
|
timesheets: List<Timesheet>, |
|
|
|
|
|
monthlyStaffSalaryEffective: List<MonthlyStaffSalaryData>, |
|
|
dateType: String |
|
|
dateType: String |
|
|
): ByteArray { |
|
|
): ByteArray { |
|
|
// Generate the Excel report with query results |
|
|
// Generate the Excel report with query results |
|
|
val workbook: Workbook = |
|
|
val workbook: Workbook = |
|
|
createProjectCashFlowReport(project, invoices, timesheets, dateType, PROJECT_CASH_FLOW_REPORT) |
|
|
|
|
|
|
|
|
createProjectCashFlowReport( |
|
|
|
|
|
project, |
|
|
|
|
|
invoices, |
|
|
|
|
|
timesheets, |
|
|
|
|
|
monthlyStaffSalaryEffective, |
|
|
|
|
|
dateType, |
|
|
|
|
|
PROJECT_CASH_FLOW_REPORT |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
// Write the workbook to a ByteArrayOutputStream |
|
|
// Write the workbook to a ByteArrayOutputStream |
|
|
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() |
|
|
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() |
|
@@ -319,11 +327,20 @@ open class ReportService( |
|
|
timesheets: List<Timesheet>, |
|
|
timesheets: List<Timesheet>, |
|
|
teams: List<Team>, |
|
|
teams: List<Team>, |
|
|
grades: List<Grade>, |
|
|
grades: List<Grade>, |
|
|
|
|
|
monthlyStaffSalaryEffective: List<MonthlyStaffSalaryData>, |
|
|
teamId: String, |
|
|
teamId: String, |
|
|
): ByteArray { |
|
|
): ByteArray { |
|
|
// Generate the Excel report with query results |
|
|
// Generate the Excel report with query results |
|
|
val workbook: Workbook = |
|
|
val workbook: Workbook = |
|
|
createCrossTeamChargeReport(month, timesheets, teams, grades, teamId, CROSS_TEAM_CHARGE_REPORT) |
|
|
|
|
|
|
|
|
createCrossTeamChargeReport( |
|
|
|
|
|
month, |
|
|
|
|
|
timesheets, |
|
|
|
|
|
teams, |
|
|
|
|
|
grades, |
|
|
|
|
|
monthlyStaffSalaryEffective, |
|
|
|
|
|
teamId, |
|
|
|
|
|
CROSS_TEAM_CHARGE_REPORT |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
// Write the workbook to a ByteArrayOutputStream |
|
|
// Write the workbook to a ByteArrayOutputStream |
|
|
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() |
|
|
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() |
|
@@ -431,7 +448,9 @@ open class ReportService( |
|
|
endDateCell.setCellValue(if (item["planEnd"] != null) item.getValue("planEnd").toString() else "N/A") |
|
|
endDateCell.setCellValue(if (item["planEnd"] != null) item.getValue("planEnd").toString() else "N/A") |
|
|
|
|
|
|
|
|
val totalFeeCell = row.createCell(7) |
|
|
val totalFeeCell = row.createCell(7) |
|
|
val totalFee = (item["expectedTotalFee"]?.let { it as Double } ?: 0.0) - (item["subContractFee"]?.let { it as Double } ?: 0.0) |
|
|
|
|
|
|
|
|
val totalFee = |
|
|
|
|
|
(item["expectedTotalFee"]?.let { it as Double } ?: 0.0) - (item["subContractFee"]?.let { it as Double } |
|
|
|
|
|
?: 0.0) |
|
|
totalFeeCell.apply { |
|
|
totalFeeCell.apply { |
|
|
setCellValue(totalFee) |
|
|
setCellValue(totalFee) |
|
|
cellStyle.dataFormat = accountingStyle |
|
|
cellStyle.dataFormat = accountingStyle |
|
@@ -693,6 +712,7 @@ open class ReportService( |
|
|
project: Project, |
|
|
project: Project, |
|
|
invoices: List<Invoice>, |
|
|
invoices: List<Invoice>, |
|
|
timesheets: List<Timesheet>, |
|
|
timesheets: List<Timesheet>, |
|
|
|
|
|
monthlyStaffSalaryEffective: List<SalaryEffectiveService.MonthlyStaffSalaryData>, |
|
|
dateType: String, |
|
|
dateType: String, |
|
|
templatePath: String, |
|
|
templatePath: String, |
|
|
): Workbook { |
|
|
): Workbook { |
|
@@ -763,8 +783,10 @@ open class ReportService( |
|
|
rowIndex = 11 |
|
|
rowIndex = 11 |
|
|
val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! } |
|
|
val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! } |
|
|
val actualExpenditure = timesheets.sumOf { timesheet -> |
|
|
val actualExpenditure = timesheets.sumOf { timesheet -> |
|
|
timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed |
|
|
|
|
|
?: 0.0)) |
|
|
|
|
|
|
|
|
// timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed ?: 0.0)) |
|
|
|
|
|
(monthlyStaffSalaryEffective.find { |
|
|
|
|
|
it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month |
|
|
|
|
|
}?.hourlyRate ?: 0.0) * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed ?: 0.0)) |
|
|
} |
|
|
} |
|
|
sheet.getRow(rowIndex).apply { |
|
|
sheet.getRow(rowIndex).apply { |
|
|
createCell(1).apply { |
|
|
createCell(1).apply { |
|
@@ -805,10 +827,16 @@ open class ReportService( |
|
|
timesheetEntries.map { timesheet -> |
|
|
timesheetEntries.map { timesheet -> |
|
|
if (timesheet.normalConsumed != null) { |
|
|
if (timesheet.normalConsumed != null) { |
|
|
timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) |
|
|
timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) |
|
|
.times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
|
|
|
// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
.times(monthlyStaffSalaryEffective.find { |
|
|
|
|
|
it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month |
|
|
|
|
|
}?.hourlyRate ?: 0.0) |
|
|
} else if (timesheet.otConsumed != null) { |
|
|
} else if (timesheet.otConsumed != null) { |
|
|
timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) |
|
|
timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) |
|
|
.times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
|
|
|
// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
.times(monthlyStaffSalaryEffective.find { |
|
|
|
|
|
it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month |
|
|
|
|
|
}?.hourlyRate ?: 0.0) |
|
|
} else { |
|
|
} else { |
|
|
0.0 |
|
|
0.0 |
|
|
} |
|
|
} |
|
@@ -2043,47 +2071,95 @@ open class ReportService( |
|
|
return jdbcDao.queryForList(sql.toString(), args) |
|
|
return jdbcDao.queryForList(sql.toString(), args) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
open fun getProjectResourceOverconsumptionReport_projectInfo(args: Map<String, Any>): List<Map<String, Any>> { |
|
|
|
|
|
val sql = StringBuilder("SELECT" |
|
|
|
|
|
+ " t.projectId as id, " |
|
|
|
|
|
+ " p.code as projectCode, " |
|
|
|
|
|
+ " p.name as projectName, " |
|
|
|
|
|
+ " te.code as team, " |
|
|
|
|
|
+ " CONCAT( c.code, ' - ' ,c.name ) as client, " |
|
|
|
|
|
+ " COALESCE(concat(sub.code, ' - ', sub.name), 'N/A') as subsidaiary, " |
|
|
|
|
|
+ " (p.expectedTotalFee - ifnull(p.subContractFee, 0)) * 0.8 as plannedBudget " |
|
|
|
|
|
+ " from timesheet t " |
|
|
|
|
|
+ " left join project p on p.id = t.projectId " |
|
|
|
|
|
+ " left join team te on te.teamLead = p.teamLead " |
|
|
|
|
|
+ " left join customer c ON p.customerId = c.id " |
|
|
|
|
|
+ " left join subsidiary sub on sub.id = p.customerSubsidiaryId " |
|
|
|
|
|
+ " where t.projectid is not NULL " |
|
|
|
|
|
+ " and p.deleted = false " |
|
|
|
|
|
+ " and p.status = 'On-going' " |
|
|
|
|
|
) |
|
|
|
|
|
if (args != null) { |
|
|
|
|
|
if (args.containsKey("teamId")) |
|
|
|
|
|
sql.append(" and tm.id = :teamId") |
|
|
|
|
|
if (args.containsKey("custId")) |
|
|
|
|
|
sql.append(" and c.id = :custId") |
|
|
|
|
|
if (args.containsKey("subsidiaryId")) |
|
|
|
|
|
sql.append(" and ss.id = :subsidiaryId") |
|
|
|
|
|
} |
|
|
|
|
|
sql.append(" group by t.projectId; ") |
|
|
|
|
|
return jdbcDao.queryForList(sql.toString(), args) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
open fun getProjectResourceOverconsumptionReport_timesheetInfo(): List<Map<String, Any>> { |
|
|
|
|
|
val sql = StringBuilder("SELECT" |
|
|
|
|
|
+ " t.*, " |
|
|
|
|
|
+ " sal.hourlyRate " |
|
|
|
|
|
+ " from timesheet t " |
|
|
|
|
|
+ " left join staff s on s.id = t.staffId " |
|
|
|
|
|
+ " left join salary sal on sal.salaryPoint = s.salaryId " |
|
|
|
|
|
+ " where t.deleted = false " |
|
|
|
|
|
) |
|
|
|
|
|
return jdbcDao.queryForList(sql.toString()) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
open fun getProjectResourceOverconsumptionReport(args: Map<String, Any>): List<Map<String, Any>> { |
|
|
open fun getProjectResourceOverconsumptionReport(args: Map<String, Any>): List<Map<String, Any>> { |
|
|
val sql = StringBuilder( |
|
|
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 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' " |
|
|
|
|
|
|
|
|
"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' " |
|
|
) |
|
|
) |
|
|
var statusFilter: String = "" |
|
|
var statusFilter: String = "" |
|
|
if (args != null) { |
|
|
if (args != null) { |
|
|
if (args.containsKey("teamId")) |
|
|
if (args.containsKey("teamId")) |
|
|
sql.append(" and t.id = :teamId") |
|
|
|
|
|
|
|
|
sql.append(" and tm.id = :teamId") |
|
|
if (args.containsKey("custId")) |
|
|
if (args.containsKey("custId")) |
|
|
sql.append(" and c.id = :custId") |
|
|
sql.append(" and c.id = :custId") |
|
|
if (args.containsKey("subsidiaryId")) |
|
|
if (args.containsKey("subsidiaryId")) |
|
@@ -2388,10 +2464,14 @@ open class ReportService( |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fun updateFinancialYear(financialYear: FinancialYear, salaryDataList: List<SalaryEffectiveService.SalaryData>?, staffInfo: Map<String, Any>): FinancialYear { |
|
|
|
|
|
|
|
|
fun updateFinancialYear( |
|
|
|
|
|
financialYear: FinancialYear, |
|
|
|
|
|
salaryDataList: List<SalaryEffectiveService.SalaryData>?, |
|
|
|
|
|
staffInfo: Map<String, Any> |
|
|
|
|
|
): FinancialYear { |
|
|
// println("====================== staffInfo: $staffInfo ===============================") |
|
|
// println("====================== staffInfo: $staffInfo ===============================") |
|
|
|
|
|
|
|
|
if(salaryDataList == null){ |
|
|
|
|
|
|
|
|
if (salaryDataList == null) { |
|
|
return financialYear.copy( |
|
|
return financialYear.copy( |
|
|
hourlyRate = (staffInfo["hourlyRate"] as BigDecimal).toDouble(), |
|
|
hourlyRate = (staffInfo["hourlyRate"] as BigDecimal).toDouble(), |
|
|
salaryPoint = (staffInfo["salaryPoint"] as Long).toInt() |
|
|
salaryPoint = (staffInfo["salaryPoint"] as Long).toInt() |
|
@@ -2414,15 +2494,19 @@ open class ReportService( |
|
|
val previousHourlyRate = findPreviousValue(salaryDataList, financialYear.start) { it.hourlyRate } |
|
|
val previousHourlyRate = findPreviousValue(salaryDataList, financialYear.start) { it.hourlyRate } |
|
|
val previousSalaryPoint = findPreviousValue(salaryDataList, financialYear.start) { it.salaryPoint } |
|
|
val previousSalaryPoint = findPreviousValue(salaryDataList, financialYear.start) { it.salaryPoint } |
|
|
|
|
|
|
|
|
// println("====================== staffInfo: $staffInfo ===============================") |
|
|
|
|
|
|
|
|
println("====================== staffInfo: $staffInfo ===============================") |
|
|
return financialYear.copy( |
|
|
return financialYear.copy( |
|
|
hourlyRate = previousHourlyRate?.toDouble() ?: financialYear.hourlyRate, |
|
|
|
|
|
salaryPoint = previousSalaryPoint ?: financialYear.salaryPoint |
|
|
|
|
|
|
|
|
hourlyRate = previousHourlyRate?.toDouble() ?: (staffInfo["hourlyRate"] as BigDecimal).toDouble(), |
|
|
|
|
|
salaryPoint = previousSalaryPoint ?: (staffInfo["salaryPoint"] as Long).toInt() |
|
|
) |
|
|
) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fun <T> findPreviousValue(salaryDataList: List<SalaryEffectiveService.SalaryData>, currentStart: YearMonth, valueSelector: (SalaryEffectiveService.SalaryData) -> T): T? { |
|
|
|
|
|
|
|
|
fun <T> findPreviousValue( |
|
|
|
|
|
salaryDataList: List<SalaryEffectiveService.SalaryData>, |
|
|
|
|
|
currentStart: YearMonth, |
|
|
|
|
|
valueSelector: (SalaryEffectiveService.SalaryData) -> T |
|
|
|
|
|
): T? { |
|
|
return salaryDataList |
|
|
return salaryDataList |
|
|
.filter { YearMonth.from(it.financialYear) < currentStart } |
|
|
.filter { YearMonth.from(it.financialYear) < currentStart } |
|
|
.maxByOrNull { it.financialYear } |
|
|
.maxByOrNull { it.financialYear } |
|
@@ -2548,12 +2632,13 @@ open class ReportService( |
|
|
|
|
|
|
|
|
return financialYearDates |
|
|
return financialYearDates |
|
|
} |
|
|
} |
|
|
private fun getOrCreateRow(sheet: Sheet, startRow: Int): Row{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun getOrCreateRow(sheet: Sheet, startRow: Int): Row { |
|
|
val row: Row = sheet.getRow(startRow) ?: sheet.createRow(startRow) |
|
|
val row: Row = sheet.getRow(startRow) ?: sheet.createRow(startRow) |
|
|
return row |
|
|
return row |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private fun getOrCreateCell(row: Row, columnIndex: Int): Cell{ |
|
|
|
|
|
|
|
|
private fun getOrCreateCell(row: Row, columnIndex: Int): Cell { |
|
|
val cell: Cell = row.getCell(columnIndex) ?: row.createCell(columnIndex) |
|
|
val cell: Cell = row.getCell(columnIndex) ?: row.createCell(columnIndex) |
|
|
return cell |
|
|
return cell |
|
|
} |
|
|
} |
|
@@ -2693,7 +2778,7 @@ open class ReportService( |
|
|
// Average Hourly Rate by Pay Scale Point |
|
|
// Average Hourly Rate by Pay Scale Point |
|
|
rowNum = 8 |
|
|
rowNum = 8 |
|
|
val row8: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) |
|
|
val row8: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) |
|
|
sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 2, (financialYears.size+1)*2-1)) |
|
|
|
|
|
|
|
|
sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 2, (financialYears.size + 1) * 2 - 1)) |
|
|
val row8Cell = row8.getCell(2) ?: row8.createCell(2) |
|
|
val row8Cell = row8.getCell(2) ?: row8.createCell(2) |
|
|
row8Cell.apply { |
|
|
row8Cell.apply { |
|
|
setCellValue("Average Hourly Rate by Pay Scale Point") |
|
|
setCellValue("Average Hourly Rate by Pay Scale Point") |
|
@@ -2710,9 +2795,15 @@ open class ReportService( |
|
|
var column = 2 |
|
|
var column = 2 |
|
|
financialYears.indices.forEach { i -> |
|
|
financialYears.indices.forEach { i -> |
|
|
val row9Cell = row9.getCell(column) ?: row9.createCell(column) |
|
|
val row9Cell = row9.getCell(column) ?: row9.createCell(column) |
|
|
val row9Cell2 = row9.getCell(column+1) ?: row9.createCell(column+1) |
|
|
|
|
|
|
|
|
val row9Cell2 = row9.getCell(column + 1) ?: row9.createCell(column + 1) |
|
|
sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, column, column + 1)) |
|
|
sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, column, column + 1)) |
|
|
row9Cell.setCellValue("${financialYears[i].start.format(monthFormat)} - ${financialYears[i].end.format(monthFormat)}") |
|
|
|
|
|
|
|
|
row9Cell.setCellValue( |
|
|
|
|
|
"${financialYears[i].start.format(monthFormat)} - ${ |
|
|
|
|
|
financialYears[i].end.format( |
|
|
|
|
|
monthFormat |
|
|
|
|
|
) |
|
|
|
|
|
}" |
|
|
|
|
|
) |
|
|
CellUtil.setAlignment(row9Cell, HorizontalAlignment.CENTER); |
|
|
CellUtil.setAlignment(row9Cell, HorizontalAlignment.CENTER); |
|
|
CellUtil.setVerticalAlignment(row9Cell, VerticalAlignment.CENTER); |
|
|
CellUtil.setVerticalAlignment(row9Cell, VerticalAlignment.CENTER); |
|
|
CellUtil.setCellStyleProperty(row9Cell, "borderBottom", BorderStyle.THIN) |
|
|
CellUtil.setCellStyleProperty(row9Cell, "borderBottom", BorderStyle.THIN) |
|
@@ -2746,7 +2837,7 @@ open class ReportService( |
|
|
var salaryColumn = 2 |
|
|
var salaryColumn = 2 |
|
|
for (year in years) { |
|
|
for (year in years) { |
|
|
val salaryPointCell = row.getCell(salaryColumn) ?: row.createCell(salaryColumn) |
|
|
val salaryPointCell = row.getCell(salaryColumn) ?: row.createCell(salaryColumn) |
|
|
sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, salaryColumn, salaryColumn+1)) |
|
|
|
|
|
|
|
|
sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, salaryColumn, salaryColumn + 1)) |
|
|
salaryPointCell.apply { |
|
|
salaryPointCell.apply { |
|
|
setCellValue("${year.salaryPoint} (${year.hourlyRate})") |
|
|
setCellValue("${year.salaryPoint} (${year.hourlyRate})") |
|
|
} |
|
|
} |
|
@@ -3230,6 +3321,7 @@ open class ReportService( |
|
|
timesheets: List<Timesheet>, |
|
|
timesheets: List<Timesheet>, |
|
|
teams: List<Team>, |
|
|
teams: List<Team>, |
|
|
grades: List<Grade>, |
|
|
grades: List<Grade>, |
|
|
|
|
|
monthlyStaffSalaryEffective: List<MonthlyStaffSalaryData>, |
|
|
teamId: String, |
|
|
teamId: String, |
|
|
templatePath: String, |
|
|
templatePath: String, |
|
|
): Workbook { |
|
|
): Workbook { |
|
@@ -3304,13 +3396,19 @@ open class ReportService( |
|
|
mutableMapOf<String, Double>().apply { |
|
|
mutableMapOf<String, Double>().apply { |
|
|
this["manHour"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) |
|
|
this["manHour"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) |
|
|
this["salary"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) |
|
|
this["salary"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) |
|
|
.times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
|
|
|
// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
.times(monthlyStaffSalaryEffective.find { |
|
|
|
|
|
it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month |
|
|
|
|
|
}?.hourlyRate ?: 0.0) |
|
|
} |
|
|
} |
|
|
} else if (timesheet.otConsumed != null) { |
|
|
} else if (timesheet.otConsumed != null) { |
|
|
mutableMapOf<String, Double>().apply { |
|
|
mutableMapOf<String, Double>().apply { |
|
|
this["manHour"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) |
|
|
this["manHour"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) |
|
|
this["salary"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) |
|
|
this["salary"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) |
|
|
.times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
|
|
|
// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
.times(monthlyStaffSalaryEffective.find { |
|
|
|
|
|
it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month |
|
|
|
|
|
}?.hourlyRate ?: 0.0) |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
mutableMapOf<String, Double>().apply { |
|
|
mutableMapOf<String, Double>().apply { |
|
@@ -3556,13 +3654,19 @@ open class ReportService( |
|
|
mutableMapOf<String, Double>().apply { |
|
|
mutableMapOf<String, Double>().apply { |
|
|
this["manHour"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) |
|
|
this["manHour"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) |
|
|
this["salary"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) |
|
|
this["salary"] = timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0) |
|
|
.times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
|
|
|
// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
.times(monthlyStaffSalaryEffective.find { |
|
|
|
|
|
it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month |
|
|
|
|
|
}?.hourlyRate ?: 0.0) |
|
|
} |
|
|
} |
|
|
} else if (timesheet.otConsumed != null) { |
|
|
} else if (timesheet.otConsumed != null) { |
|
|
mutableMapOf<String, Double>().apply { |
|
|
mutableMapOf<String, Double>().apply { |
|
|
this["manHour"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) |
|
|
this["manHour"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) |
|
|
this["salary"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) |
|
|
this["salary"] = timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0) |
|
|
.times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
|
|
|
// .times(timesheet.staff!!.salary.hourlyRate.toDouble()) |
|
|
|
|
|
.times(monthlyStaffSalaryEffective.find { |
|
|
|
|
|
it.staff.staffId == timesheet.staff?.staffId && it.date.year == timesheet.recordDate?.year && it.date.month == timesheet.recordDate?.month |
|
|
|
|
|
}?.hourlyRate ?: 0.0) |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
mutableMapOf<String, Double>().apply { |
|
|
mutableMapOf<String, Double>().apply { |
|
@@ -3667,61 +3771,61 @@ open class ReportService( |
|
|
projects.forEach { project: Project -> |
|
|
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) { |
|
|
// if (team.id == project.teamLead?.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 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
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( |
|
|
|
|
|
|
|
|
var totalSalary = 0.0 |
|
|
|
|
|
staffs.forEach { staff: Staff -> |
|
|
|
|
|
logger.info("Staff: ${staff.staffId}") |
|
|
|
|
|
createCell(columnIndex++).apply { |
|
|
|
|
|
setCellValue( |
|
|
|
|
|
groupedTimesheetsIndividual[Pair( |
|
|
project.id, |
|
|
project.id, |
|
|
staff.id |
|
|
|
|
|
)]?.sumOf { it.getValue("salary") } ?: 0.0 |
|
|
|
|
|
|
|
|
|
|
|
val cloneStyle = workbook.createCellStyle() |
|
|
|
|
|
cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) |
|
|
|
|
|
cellStyle = cloneStyle.apply { |
|
|
|
|
|
dataFormat = accountingStyle |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
staff.id, |
|
|
|
|
|
)]?.sumOf { it.getValue("manHour") } ?: 0.0) |
|
|
|
|
|
|
|
|
createCell(columnIndex++).apply { |
|
|
|
|
|
val lastCellLetter = CellReference.convertNumToColString(this.columnIndex - 1) |
|
|
|
|
|
cellFormula = "sum(B${this.rowIndex + 1}:${lastCellLetter}${this.rowIndex + 1})" |
|
|
|
|
|
|
|
|
totalSalary += groupedTimesheetsIndividual[Pair( |
|
|
|
|
|
project.id, |
|
|
|
|
|
staff.id |
|
|
|
|
|
)]?.sumOf { it.getValue("salary") } ?: 0.0 |
|
|
|
|
|
|
|
|
val cloneStyle = workbook.createCellStyle() |
|
|
val cloneStyle = workbook.createCellStyle() |
|
|
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) |
|
|
|
|
|
|
|
|
cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) |
|
|
cellStyle = cloneStyle.apply { |
|
|
cellStyle = cloneStyle.apply { |
|
|
dataFormat = accountingStyle |
|
|
dataFormat = accountingStyle |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
createCell(columnIndex).apply { |
|
|
|
|
|
setCellValue(totalSalary) |
|
|
|
|
|
val cloneStyle = workbook.createCellStyle() |
|
|
|
|
|
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) |
|
|
|
|
|
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 |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
// } |
|
|
// } |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|