|
|
@@ -7,12 +7,7 @@ import com.ffii.tsms.modules.data.entity.Team |
|
|
|
import com.ffii.tsms.modules.data.entity.Customer |
|
|
|
import com.ffii.tsms.modules.project.entity.Invoice |
|
|
|
import com.ffii.tsms.modules.project.entity.Project |
|
|
|
import com.ffii.tsms.modules.timesheet.entity.Leave |
|
|
|
import com.ffii.tsms.modules.timesheet.entity.Timesheet |
|
|
|
import com.ffii.tsms.modules.timesheet.entity.projections.MonthlyLeave |
|
|
|
import com.ffii.tsms.modules.timesheet.entity.projections.ProjectMonthlyHoursWithDate |
|
|
|
import com.ffii.tsms.modules.timesheet.entity.projections.TimesheetHours |
|
|
|
import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry |
|
|
|
import org.apache.commons.logging.Log |
|
|
|
import org.apache.commons.logging.LogFactory |
|
|
|
import org.apache.poi.ss.usermodel.* |
|
|
@@ -25,13 +20,17 @@ import org.springframework.stereotype.Service |
|
|
|
import java.io.ByteArrayOutputStream |
|
|
|
import java.io.IOException |
|
|
|
import java.math.BigDecimal |
|
|
|
import java.sql.Time |
|
|
|
import java.time.LocalDate |
|
|
|
import java.time.format.DateTimeFormatter |
|
|
|
import java.util.* |
|
|
|
|
|
|
|
|
|
|
|
data class DayInfo(val date: String?, val weekday: String?) |
|
|
|
|
|
|
|
//private operator fun <K, V> Map<K, V>.component2(): Any { |
|
|
|
// |
|
|
|
//} |
|
|
|
|
|
|
|
@Service |
|
|
|
open class ReportService( |
|
|
|
private val jdbcDao: JdbcDao, |
|
|
@@ -45,6 +44,7 @@ open class ReportService( |
|
|
|
private val MONTHLY_WORK_HOURS_ANALYSIS_REPORT = "templates/report/AR08_Monthly Work Hours Analysis Report.xlsx" |
|
|
|
private val SALART_LIST_TEMPLATE = "templates/report/Salary Template.xlsx" |
|
|
|
private val LATE_START_REPORT = "templates/report/AR01_Late Start Report v01.xlsx" |
|
|
|
private val RESOURCE_OVERCONSUMPTION_REPORT = "templates/report/AR03_Resource Overconsumption.xlsx" |
|
|
|
|
|
|
|
// ==============================|| GENERATE REPORT ||============================== // |
|
|
|
|
|
|
@@ -147,6 +147,27 @@ open class ReportService( |
|
|
|
return outputStream.toByteArray() |
|
|
|
} |
|
|
|
|
|
|
|
@Throws(IOException::class) |
|
|
|
fun generateProjectResourceOverconsumptionReport( |
|
|
|
team: String, |
|
|
|
customer: String, |
|
|
|
result: List<Map<String, Any>> |
|
|
|
): ByteArray { |
|
|
|
// Generate the Excel report with query results |
|
|
|
val workbook: Workbook = createProjectResourceOverconsumptionReport( |
|
|
|
team, |
|
|
|
customer, |
|
|
|
result, |
|
|
|
RESOURCE_OVERCONSUMPTION_REPORT |
|
|
|
) |
|
|
|
// Write the workbook to a ByteArrayOutputStream |
|
|
|
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() |
|
|
|
workbook.write(outputStream) |
|
|
|
workbook.close() |
|
|
|
|
|
|
|
return outputStream.toByteArray() |
|
|
|
} |
|
|
|
|
|
|
|
@Throws(IOException::class) |
|
|
|
fun exportSalaryList(salarys: List<Salary>): ByteArray { |
|
|
|
// Generate the Excel report with query results |
|
|
@@ -737,10 +758,13 @@ open class ReportService( |
|
|
|
var projectList: List<String> = listOf() |
|
|
|
println("----timesheets-----") |
|
|
|
println(timesheets) |
|
|
|
|
|
|
|
println("----leaves-----") |
|
|
|
println(leaves) |
|
|
|
// result = timesheet record mapped |
|
|
|
var result: Map<String, Any> = mapOf() |
|
|
|
if (timesheets.isNotEmpty()) { |
|
|
|
projectList = timesheets.map{ "${it["code"]}\n ${it["name"]}"}.toList() |
|
|
|
projectList = timesheets.map{ "${it["code"]}\n ${it["name"]}"}.toList().distinct() |
|
|
|
result = timesheets.groupBy( |
|
|
|
{ it["id"].toString() }, |
|
|
|
{ mapOf( |
|
|
@@ -920,41 +944,40 @@ open class ReportService( |
|
|
|
columnSize++ |
|
|
|
} |
|
|
|
println(columnSize) |
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
/////////////////////////////////////////////////////////////// main data ////////////////////////////////////////////////////////////// |
|
|
|
if (timesheets.isNotEmpty()) { |
|
|
|
projectList.forEach { _ -> |
|
|
|
println("2") |
|
|
|
projectList.forEachIndexed { index, _ -> |
|
|
|
for (i in 0 until rowSize) { |
|
|
|
tempCell = sheet.getRow(8 + i).createCell(columnIndex) |
|
|
|
tempCell = sheet.getRow(8 + i).createCell(columnIndex + index) |
|
|
|
tempCell.setCellValue(0.0) |
|
|
|
tempCell.cellStyle.dataFormat = accountingStyle |
|
|
|
} |
|
|
|
result.forEach{(id, list) -> |
|
|
|
for (i in 0 until id.toInt()) { |
|
|
|
val temp: List<Map<String, Any>> = list as List<Map<String, Any>> |
|
|
|
temp.forEachIndexed { i, _ -> |
|
|
|
dayInt = temp[i]["date"].toString().toInt() |
|
|
|
tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) |
|
|
|
tempCell.setCellValue(temp[i]["normalConsumed"] as Double) |
|
|
|
} |
|
|
|
} |
|
|
|
result.forEach{ (id, list) -> |
|
|
|
val temp: List<Map<String, Any>> = list as List<Map<String, Any>> |
|
|
|
temp.forEachIndexed { index, _ -> |
|
|
|
dayInt = temp[index]["date"].toString().toInt() |
|
|
|
tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) |
|
|
|
tempCell.setCellValue(temp[index]["normalConsumed"] as Double) |
|
|
|
} |
|
|
|
} |
|
|
|
columnIndex++ |
|
|
|
} |
|
|
|
} |
|
|
|
// leave hours data |
|
|
|
/////////////////////////////////////////////////////////////// leave hours data ////////////////////////////////////////////////////////////// |
|
|
|
for (i in 0 until rowSize) { |
|
|
|
tempCell = sheet.getRow(8 + i).createCell(columnIndex) |
|
|
|
tempCell.setCellValue(0.0) |
|
|
|
tempCell.cellStyle.dataFormat = accountingStyle |
|
|
|
} |
|
|
|
if (leaves.isNotEmpty()) { |
|
|
|
leaves.forEach { leave -> |
|
|
|
for (i in 0 until rowSize) { |
|
|
|
tempCell = sheet.getRow(8 + i).createCell(columnIndex) |
|
|
|
tempCell.setCellValue(0.0) |
|
|
|
tempCell.cellStyle.dataFormat = accountingStyle |
|
|
|
} |
|
|
|
dayInt = leave["recordDate"].toString().toInt() |
|
|
|
tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) |
|
|
|
tempCell.setCellValue(leave["leaveHours"] as Double) |
|
|
|
} |
|
|
|
} |
|
|
|
///////////////////////////////////////////////////////// Leave Hours //////////////////////////////////////////////////////////////////// |
|
|
|
///////////////////////////////////////////////////////// Leave Hours title //////////////////////////////////////////////////////////////////// |
|
|
|
tempCell = sheet.getRow(rowIndex).createCell(columnIndex) |
|
|
|
tempCell.setCellValue("Leave Hours") |
|
|
|
tempCell.cellStyle = boldStyle |
|
|
@@ -1031,6 +1054,62 @@ open class ReportService( |
|
|
|
|
|
|
|
return workbook |
|
|
|
} |
|
|
|
|
|
|
|
private fun createProjectResourceOverconsumptionReport( |
|
|
|
team: String, |
|
|
|
customer: String, |
|
|
|
result: List<Map<String, Any>>, |
|
|
|
templatePath: String |
|
|
|
):Workbook { |
|
|
|
val resource = ClassPathResource(templatePath) |
|
|
|
val templateInputStream = resource.inputStream |
|
|
|
val workbook: Workbook = XSSFWorkbook(templateInputStream) |
|
|
|
|
|
|
|
val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") |
|
|
|
|
|
|
|
val sheet: Sheet = workbook.getSheetAt(0) |
|
|
|
|
|
|
|
val alignCenterStyle = workbook.createCellStyle() |
|
|
|
alignCenterStyle.alignment = HorizontalAlignment.CENTER // Set the alignment to center |
|
|
|
|
|
|
|
var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field |
|
|
|
var columnIndex = 2 |
|
|
|
var tempRow = sheet.getRow(rowIndex) |
|
|
|
var tempCell = tempRow.getCell(columnIndex) |
|
|
|
tempCell.setCellValue(FORMATTED_TODAY) |
|
|
|
|
|
|
|
rowIndex = 2 |
|
|
|
tempCell = sheet.getRow(rowIndex).getCell(columnIndex) |
|
|
|
tempCell.setCellValue(team) |
|
|
|
|
|
|
|
rowIndex = 3 |
|
|
|
tempCell = sheet.getRow(rowIndex).getCell(columnIndex) |
|
|
|
tempCell.setCellValue(customer) |
|
|
|
|
|
|
|
rowIndex = 6 |
|
|
|
columnIndex = 0 |
|
|
|
result.forEachIndexed { index, obj -> |
|
|
|
tempCell = sheet.getRow(rowIndex).getCell(columnIndex) |
|
|
|
tempCell.setCellValue((index + 1).toDouble()) |
|
|
|
val keys = obj.keys.toList() |
|
|
|
keys.forEachIndexed { keyIndex, key -> |
|
|
|
tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) |
|
|
|
when (obj[key]) { |
|
|
|
is String -> tempCell.setCellValue(obj[key] as String) |
|
|
|
is Double -> tempCell.setCellValue(obj[key] as Double) |
|
|
|
// else -> tempCell.setCellValue(obj[key] as { |
|
|
|
|
|
|
|
// }) |
|
|
|
} |
|
|
|
} |
|
|
|
rowIndex++ |
|
|
|
} |
|
|
|
// tempCell = sheet.getRow(rowIndex).getCell(columnIndex) |
|
|
|
// tempCell.setCellValue() |
|
|
|
|
|
|
|
return workbook |
|
|
|
} |
|
|
|
|
|
|
|
//createLateStartReport |
|
|
|
private fun createLateStartReport( |
|
|
|
team: Team, |
|
|
@@ -1177,4 +1256,78 @@ open class ReportService( |
|
|
|
return jdbcDao.queryForList(sql.toString(), args) |
|
|
|
} |
|
|
|
|
|
|
|
open fun getProjectResourceOverconsumptionReport(args: Map<String, Any>): List<Map<String, Any>> { |
|
|
|
val sql = StringBuilder("WITH teamNormalConsumed AS (" |
|
|
|
+ " SELECT " |
|
|
|
+ " s.teamId, " |
|
|
|
+ " pt.project_id, " |
|
|
|
+ " SUM(tns.totalConsumed) AS totalConsumed " |
|
|
|
+ " 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 " |
|
|
|
+ " 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.status, " |
|
|
|
+ " p.name, " |
|
|
|
+ " t.code as team, " |
|
|
|
+ " c.code as client, " |
|
|
|
+ " p.expectedTotalFee as plannedBudget, " |
|
|
|
+ " COALESCE((tns.totalConsumed * sa.hourlyRate), 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) >= 0.9 and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " |
|
|
|
+ " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 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 = "" |
|
|
|
if (args.containsKey("teamId")) |
|
|
|
sql.append("and t.id = :teamId") |
|
|
|
if (args.containsKey("custId")) |
|
|
|
sql.append("and c.id = :custId") |
|
|
|
if (args.containsKey("status")) |
|
|
|
statusFilter = when (args.get("status")) { |
|
|
|
"Potential Overconsumption" -> " ( and " + |
|
|
|
"((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 0.9 " + |
|
|
|
"and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + |
|
|
|
"or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 " + |
|
|
|
"and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1)" + |
|
|
|
" ) " |
|
|
|
"Overconsumption" -> " ( and " + |
|
|
|
"((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + |
|
|
|
"or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1)" + |
|
|
|
" ) " |
|
|
|
else -> "" |
|
|
|
} |
|
|
|
sql.append(statusFilter) |
|
|
|
} |
|
|
|
return jdbcDao.queryForList(sql.toString(), args) |
|
|
|
} |
|
|
|
} |