Kaynağa Gözat

update work hour report

tags/Baseline_30082024_BACKEND_UAT
MSI\derek 1 yıl önce
ebeveyn
işleme
417f197fef
5 değiştirilmiş dosya ile 243 ekleme ve 40 silme
  1. +15
    -0
      src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectResourceReport.kt
  2. +179
    -26
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  3. +44
    -14
      src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt
  4. +5
    -0
      src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt
  5. BIN
      src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx

+ 15
- 0
src/main/java/com/ffii/tsms/modules/project/entity/projections/ProjectResourceReport.kt Dosyayı Görüntüle

@@ -0,0 +1,15 @@
package com.ffii.tsms.modules.project.entity.projections

data class ProjectResourceReport (
val code: String,
val name: String,
val team: String,
val client: String,
val plannedBudget: Double,
val actualConsumedBudget: Double,
val plannedManhour: Double,
val actualConsumedManhour: Double,
val budgetConsumptionRate: Double,
val manhourConsumptionRate: Double,
val status: String
)

+ 179
- 26
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt Dosyayı Görüntüle

@@ -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)
}
}

+ 44
- 14
src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt Dosyayı Görüntüle

@@ -1,33 +1,34 @@
package com.ffii.tsms.modules.report.web

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.ffii.tsms.modules.data.entity.Customer
import com.ffii.tsms.modules.data.entity.StaffRepository
//import com.ffii.tsms.modules.data.entity.projections.FinancialStatusReportInfo
import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo
import com.ffii.tsms.modules.data.entity.Team
import com.ffii.tsms.modules.data.service.CustomerService
import com.ffii.tsms.modules.data.service.TeamService
import com.ffii.tsms.modules.project.entity.*
import com.ffii.tsms.modules.report.service.ReportService
import com.ffii.tsms.modules.project.entity.projections.ProjectResourceReport
import com.ffii.tsms.modules.project.service.InvoiceService
import com.ffii.tsms.modules.report.service.ReportService
import com.ffii.tsms.modules.report.web.model.FinancialStatusReportRequest
import com.ffii.tsms.modules.report.web.model.ProjectCashFlowReportRequest
import com.ffii.tsms.modules.report.web.model.ProjectResourceOverconsumptionReport
import com.ffii.tsms.modules.report.web.model.StaffMonthlyWorkHourAnalysisReportRequest
import com.ffii.tsms.modules.report.web.model.LateStartReportRequest
import com.ffii.tsms.modules.project.entity.ProjectRepository
import com.ffii.tsms.modules.timesheet.entity.LeaveRepository
import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository
import com.ffii.tsms.modules.timesheet.entity.projections.ProjectMonthlyHoursWithDate
import jakarta.validation.Valid
import org.springframework.core.io.ByteArrayResource
import org.springframework.core.io.Resource
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.ServletRequestBindingException
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.ServletRequestBindingException
import org.springframework.web.bind.annotation.*
import java.io.IOException
import java.time.LocalDate
import java.net.URLEncoder
@@ -36,7 +37,6 @@ import org.springframework.stereotype.Controller
import com.ffii.tsms.modules.data.entity.TeamRepository
import com.ffii.tsms.modules.data.entity.CustomerRepository
import org.springframework.data.jpa.repository.JpaRepository
import com.ffii.tsms.modules.data.entity.Customer
import com.ffii.tsms.modules.project.entity.Project

@RestController
@@ -52,6 +52,8 @@ class ReportController(
private val customerRepository: CustomerRepository,
private val staffRepository: StaffRepository,
private val leaveRepository: LeaveRepository,
private val teamService: TeamService,
private val customerService: CustomerService,
private val invoiceService: InvoiceService) {

@PostMapping("/fetchProjectsFinancialStatusReport")
@@ -90,13 +92,16 @@ class ReportController(
val nextMonth = request.yearMonth.plusMonths(1).atDay(1)

val staff = staffRepository.findById(request.id).orElseThrow()
println(thisMonth)
println(nextMonth)
val args: Map<String, Any> = mutableMapOf(
"staffId" to request.id,
"startDate" to thisMonth,
"endDate" to nextMonth,
)
val timesheets= excelReportService.getTimesheet(args)
val leaves= excelReportService.getLeaves(args)
val timesheets = excelReportService.getTimesheet(args)
val leaves = excelReportService.getLeaves(args)
println(leaves)

val reportResult: ByteArray = excelReportService.generateStaffMonthlyWorkHourAnalysisReport(thisMonth, staff, timesheets, leaves)
// val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@@ -105,6 +110,31 @@ class ReportController(
.header("filename", "Monthly Work Hours Analysis Report - " + staff.name + " - " + LocalDate.now() + ".xlsx")
.body(ByteArrayResource(reportResult))
}
private val mapper = ObjectMapper().registerModule(KotlinModule())

@PostMapping("/ProjectResourceOverconsumptionReport")
@Throws(ServletRequestBindingException::class, IOException::class)
fun ProjectResourceOverconsumptionReport(@RequestBody @Valid request: ProjectResourceOverconsumptionReport): ResponseEntity<Resource> {

// val staff = staffRepository.findById(request.id).orElseThrow()
val args: Map<String, Any> = mutableMapOf(
"teamId" to request.teamId,
"custId" to request.custId,
"status" to request.status
)
val team: String = teamService.find(request.teamId).orElseThrow().name
val customer: String = customerService.find(request.custId).orElseThrow().name
val result: List<Map<String, Any>> = excelReportService.getProjectResourceOverconsumptionReport(args);
// val obj: ProjectResourceReport = mapper.readValue(mapper.writeValueAsBytes(result))


val reportResult: ByteArray = excelReportService.generateProjectResourceOverconsumptionReport(team, customer, result)
// val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
return ResponseEntity.ok()
// .contentType(mediaType)
.header("filename", "Project Resource Overconsumption Report - " + " - " + LocalDate.now() + ".xlsx")
.body(ByteArrayResource(reportResult))
}

@GetMapping("/test/{id}")
fun test(@PathVariable id: Long): List<Invoice> {


+ 5
- 0
src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt Dosyayı Görüntüle

@@ -23,4 +23,9 @@ data class LateStartReportRequest (
val remainedDateTo: LocalDate,
val customer: String,
val reportDate: LocalDate
)
data class ProjectResourceOverconsumptionReport (
val teamId: Long,
val custId: Long,
val status: String,
)

BIN
src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx Dosyayı Görüntüle


Yükleniyor…
İptal
Kaydet