Browse Source

report update

tags/Baseline_30082024_BACKEND_UAT
leoho2fi 1 year ago
parent
commit
58dffe7af0
3 changed files with 301 additions and 102 deletions
  1. +249
    -60
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  2. +51
    -39
      src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt
  3. +1
    -3
      src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt

+ 249
- 60
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt View File

@@ -20,6 +20,10 @@ import org.apache.poi.ss.util.CellAddress
import org.apache.poi.ss.util.CellRangeAddress import org.apache.poi.ss.util.CellRangeAddress
import org.apache.poi.ss.util.CellUtil import org.apache.poi.ss.util.CellUtil
import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.apache.poi.ss.usermodel.FormulaEvaluator
import org.apache.poi.ss.usermodel.CellStyle
import org.apache.poi.ss.usermodel.DateUtil
import org.apache.poi.ss.usermodel.CellType
import org.hibernate.jdbc.Work import org.hibernate.jdbc.Work
import org.springframework.core.io.ClassPathResource import org.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@@ -30,6 +34,9 @@ import java.sql.Time
import java.time.LocalDate import java.time.LocalDate
import java.time.YearMonth import java.time.YearMonth
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.ZoneId
import java.time.format.DateTimeParseException
import java.time.temporal.ChronoUnit
import java.util.* import java.util.*




@@ -215,18 +222,34 @@ open class ReportService(


@Throws(IOException::class) @Throws(IOException::class)
fun generateLateStartReport( fun generateLateStartReport(
team: Team,
customer: Customer,
project: List<Project>,
teamId: Long,
clientId: Long,
remainedDate:LocalDate,
remainedDateTo:LocalDate,
teams: List<Team>,
customers: List<Customer>,
): ByteArray { ): ByteArray {
val projectDetails = getLateStartDetails(teamId, clientId, remainedDate, remainedDateTo)

// Find team and customer names based on teamId and clientId for the Excel report headers
// val team = teamRepository.findById(teamId).orElse(null)
// val teams = if (team == null) listOf() else listOf(team)
// val customer = customerRepository.findById(clientId).orElse(null)
// val customers = if (customer == null) listOf() else listOf(customer)

val workbook: Workbook = createLateStartReport( val workbook: Workbook = createLateStartReport(
team,
customer,
project,
LATE_START_REPORT)
teams,
customers,
LATE_START_REPORT, // Ensure you provide the correct path to your template
teamId,
clientId,
projectDetails
)

val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
workbook.write(outputStream) workbook.write(outputStream)
workbook.close() workbook.close()

return outputStream.toByteArray() return outputStream.toByteArray()
} }


@@ -1197,80 +1220,209 @@ open class ReportService(
} }


//createLateStartReport //createLateStartReport
private fun createLateStartReport(
team: Team,
customer: Customer,
project: List<Project>,
templatePath: String
):Workbook{
val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream
val workbook: Workbook = XSSFWorkbook(templateInputStream)
val sheet = workbook.getSheetAt(0)

// Formatting the current date to "YYYY/MM/DD" and setting it to cell C2
val formattedToday = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))
val dateCell = sheet.getRow(1)?.getCell(2) ?: sheet.getRow(1).createCell(2)
dateCell.setCellValue(formattedToday)

// Start populating project data starting at row 7
val startRow = 6 // 0-based index for row 7
val projectDataRow = sheet.createRow(startRow)
private fun createLateStartReport(
team: List<Team>,
customer: List<Customer>,
templatePath: String,
teamId: Long,
clientId: Long,
lateStartData: List<Map<String, Any>>
): Workbook {
val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream
val workbook: Workbook = XSSFWorkbook(templateInputStream)
val sheet = workbook.getSheetAt(0)
val evaluator: FormulaEvaluator = workbook.creationHelper.createFormulaEvaluator()

// Apply standard and custom fonts
applyStandardAndCustomFonts(workbook, sheet)

// Formatting the date in cell C2
setDateInCellC2(workbook, sheet)

setTeamAndClientIds(sheet, team, teamId, customer, clientId)

// Set data and conditional formatting in columns
setDataAndConditionalFormatting(workbook, sheet, lateStartData, evaluator)

// Auto-size columns to fit content
autoSizeColumns(sheet)

return workbook
}


// Styling for cell A1: Font size 16 and bold
val headerFont = workbook.createFont().apply {
private fun applyStandardAndCustomFonts(workbook: Workbook, sheet: Sheet) {
val timesNewRoman = workbook.createFont().apply {
fontName = "Times New Roman"
fontHeightInPoints = 12
bold = true bold = true
}
val timesNewRomanLarge = workbook.createFont().apply {
fontName = "Times New Roman"
fontHeightInPoints = 16 fontHeightInPoints = 16
bold = true
} }
val headerCellStyle = workbook.createCellStyle().apply {
setFont(headerFont)
// Apply custom font size for A1
val rowA1 = sheet.getRow(0) ?: sheet.createRow(0)
val cellA1 = rowA1.getCell(0) ?: rowA1.createCell(0)
val cellStyleA1 = workbook.createCellStyle().apply {
cloneStyleFrom(cellA1.cellStyle)
setFont(timesNewRomanLarge)
} }
val headerCell = sheet.getRow(0)?.getCell(0) ?: sheet.getRow(0).createCell(0)
headerCell.cellStyle = headerCellStyle
//headerCell.setCellValue("Report Title")

cellA1.cellStyle = cellStyleA1
// Apply styles from A2 to A4 (bold) // Apply styles from A2 to A4 (bold)
val boldFont = workbook.createFont().apply { bold = true }
val boldCellStyle = workbook.createCellStyle().apply { setFont(boldFont) }
val boldCellStyle = workbook.createCellStyle().apply { setFont(timesNewRoman) }
listOf(1, 2, 3).forEach { rowIndex -> listOf(1, 2, 3).forEach { rowIndex ->
val row = sheet.getRow(rowIndex)
val cell = row?.getCell(0) ?: row.createCell(0)
val row = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex)
val cell = row.getCell(0) ?: row.createCell(0)
cell.cellStyle = boldCellStyle cell.cellStyle = boldCellStyle
} }


// Apply styles from A6 to J6 (bold, bottom border, center alignment) // Apply styles from A6 to J6 (bold, bottom border, center alignment)
val styleA6ToJ6 = workbook.createCellStyle().apply { val styleA6ToJ6 = workbook.createCellStyle().apply {
setFont(boldFont)
setFont(timesNewRoman) // Use the standard bold font for consistency
alignment = HorizontalAlignment.CENTER alignment = HorizontalAlignment.CENTER
borderBottom = BorderStyle.THIN borderBottom = BorderStyle.THIN
} }
for (colIndex in 0..9) {
val cellAddress = CellAddress(5, colIndex) // Row 6 (0-based index), Columns A to J
val row = sheet.getRow(cellAddress.row)
val cell = row?.getCell(cellAddress.column) ?: row.createCell(cellAddress.column)
// Apply styleA6ToJ6 to cells A6 to J6
val rowA6 = sheet.getRow(5) ?: sheet.createRow(5)
(0..9).forEach { colIndex ->
val cell = rowA6.getCell(colIndex) ?: rowA6.createCell(colIndex)
cell.cellStyle = styleA6ToJ6 cell.cellStyle = styleA6ToJ6
} }
}
private fun setDateInCellC2(workbook: Workbook, sheet: Sheet) {
val formattedToday = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))
val dateCell = sheet.getRow(1)?.getCell(2) ?: sheet.getRow(1).createCell(2)
dateCell.setCellValue(formattedToday)
}


// Setting column widths dynamically based on content length (example logic)
val maxContentWidths = IntArray(10) { 8 } // Initial widths for A to J
for (rowIndex in 0..sheet.lastRowNum) {
val row = sheet.getRow(rowIndex)
for (colIndex in 0..9) {
val cell = row.getCell(colIndex)
if (cell != null) {
val length = cell.toString().length
if (length > maxContentWidths[colIndex]) {
maxContentWidths[colIndex] = length
}
}
}
private fun setDataAndConditionalFormatting(
workbook: Workbook,
sheet: Sheet,
lateStartData: List<Map<String, Any>>,
evaluator: FormulaEvaluator
) {
var dataRowIndex = 6 // Starting from row 7
lateStartData.forEachIndexed { index, data ->
val row = sheet.getRow(dataRowIndex) ?: sheet.createRow(dataRowIndex)
populateDataRow(workbook, sheet, row, data, index)
dataRowIndex++
} }
for (colIndex in 0..9) {
sheet.setColumnWidth(colIndex, (maxContentWidths[colIndex] + 2) * 256) // Set the width for each column

// Update conditional formatting to apply to the new range of column J
val sheetCF = sheet.sheetConditionalFormatting
val rule1 = sheetCF.createConditionalFormattingRule("J7 < 60")
val pattern1 = rule1.createPatternFormatting().apply {
fillBackgroundColor = IndexedColors.RED.index
fillPattern = PatternFormatting.SOLID_FOREGROUND
}
val rule2 = sheetCF.createConditionalFormattingRule("J7 >= 60")
val pattern2 = rule2.createPatternFormatting().apply {
fillBackgroundColor = IndexedColors.YELLOW.index
fillPattern = PatternFormatting.SOLID_FOREGROUND
}
val region = CellRangeAddress(dataRowIndex - lateStartData.size, dataRowIndex - 1, 9, 9) // Column J
sheetCF.addConditionalFormatting(arrayOf(region), arrayOf(rule1, rule2))
}
private fun setTeamAndClientIds(sheet: Sheet, team: List<Team>, teamId: Long, customer: List<Customer>, clientId: Long) {
// Set the 'teamId' value in cell C3
val teamIdRow = sheet.getRow(2) ?: sheet.createRow(2)
val teamIdCell = teamIdRow.getCell(2) ?: teamIdRow.createCell(2)
teamIdCell.setCellValue(if (teamId == 0L) "All" else team.getOrNull(0)?.name ?: "Unknown")

// Set the 'clientId' value in cell C4
val clientIdRow = sheet.getRow(3) ?: sheet.createRow(3)
val clientIdCell = clientIdRow.getCell(2) ?: clientIdRow.createCell(2)
clientIdCell.setCellValue(if (clientId == 0L) "All" else customer.getOrNull(0)?.name ?: "Unknown")
}


private fun populateDataRow(workbook: Workbook, sheet: Sheet, row: Row, data: Map<String, Any>, index: Int) {
val dateFormat = workbook.creationHelper.createDataFormat().getFormat("yyyy/MM/dd")
// Define the font for general use (if not already defined elsewhere)
val generalFont = workbook.createFont().apply {
fontName = "Times New Roman"
fontHeightInPoints = 12 // Standard size
}
val dateCellStyle = workbook.createCellStyle().apply {
dataFormat = dateFormat
setFont(generalFont) // Apply the font here along with the date format
}
// Create a style for center alignment for column G
val centerAlignedStyle = workbook.createCellStyle().apply {
alignment = HorizontalAlignment.CENTER
setFont(generalFont) // Maintain font consistency
} }


return workbook
// Column A: Sequential ID
val idCell = row.createCell(0)
idCell.setCellValue((index + 1).toDouble()) // Set as double

// Column B: Project Code
val projectCodeCell = row.createCell(1)
projectCodeCell.setCellValue(data["project_code"] as String)

// Column C: Project Name
val projectNameCell = row.createCell(2)
projectNameCell.setCellValue(data["project_name"] as String)

// Column D: Team Name
val teamNameCell = row.createCell(3)
teamNameCell.setCellValue(data["team_name"] as String)

// Column E: Customer Name
val customerNameCell = row.createCell(4)
customerNameCell.setCellValue(data["customer_name"] as String)

// // Column F: Project Plan Start Date
// val projectPlanStartCell = row.createCell(5)
// projectPlanStartCell.setCellValue(data["project_plan_start"].toString())

// Parse the project start date and apply the date format
val projectPlanStartCell = row.createCell(5)
try {
val date = LocalDate.parse(data["project_plan_start"].toString(), DateTimeFormatter.ISO_LOCAL_DATE)
projectPlanStartCell.setCellValue(Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant()))
} catch (e: DateTimeParseException) {
projectPlanStartCell.setCellValue("Invalid date")
}
projectPlanStartCell.cellStyle = dateCellStyle

// Column G: Calculated Date Difference (days since project start from today)
val daysDifferenceCell = row.createCell(6)
daysDifferenceCell.setCellFormula("C$2-F${row.rowNum + 1}")
daysDifferenceCell.cellStyle = centerAlignedStyle

// Column H: Task Group Name
val taskGroupNameCell = row.createCell(7)
taskGroupNameCell.setCellValue(data["task_group_name"] as String)

// // Column I: Milestone End Date
val dateCell = row.createCell(8) // Milestone end date in column I
try {
val date = LocalDate.parse(data["milestone_end_date"].toString())
dateCell.setCellValue(Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant()))
} catch (e: DateTimeParseException) {
dateCell.setCellValue("Invalid date")
}
dateCell.cellStyle = dateCellStyle

// Get the current date and parse the milestone end date
val today = LocalDate.now()
val endDate = LocalDate.parse(data["milestone_end_date"].toString(), DateTimeFormatter.ISO_LOCAL_DATE)
val daysBetween = ChronoUnit.DAYS.between(today, endDate).toInt() // Calculate the difference in days

val daysDifferenceCellJ = row.createCell(9)
daysDifferenceCellJ.setCellValue(daysBetween.toDouble()) // Set as double for consistency
daysDifferenceCellJ.cellStyle = centerAlignedStyle

}

private fun autoSizeColumns(sheet: Sheet) {
for (colIndex in 0..9) {
sheet.autoSizeColumn(colIndex)
}
} }


open fun getFinancialStatus(teamLeadId: Long?): List<Map<String, Any>> { open fun getFinancialStatus(teamLeadId: Long?): List<Map<String, Any>> {
@@ -1989,4 +2141,41 @@ open class ReportService(


return costAndExpenseList return costAndExpenseList
} }
open fun getLateStartDetails(teamId: Long?, clientId: Long?, remainedDate: LocalDate, remainedDateTo: LocalDate): List<Map<String, Any>> {
val sql = StringBuilder("""
SELECT
p.code AS project_code,
p.name AS project_name,
t.name AS team_name,
c.name AS customer_name,
p.planStart AS project_plan_start,
tg.name AS task_group_name,
m.endDate AS milestone_end_date
FROM
project p
LEFT JOIN team t ON p.teamLead = t.teamLead
LEFT JOIN customer c ON p.customerId = c.id
LEFT JOIN task_group tg ON tg.id = (SELECT taskGroupId FROM milestone WHERE projectId = p.id LIMIT 1)
LEFT JOIN milestone m ON m.taskGroupId = tg.id AND m.projectId = p.id
WHERE
p.status = 'Pending to Start'
AND p.planStart < CURRENT_DATE
AND m.endDate BETWEEN :remainedDate AND :remainedDateTo
""".trimIndent())

if (teamId != null && teamId > 0) {
sql.append(" AND t.id = :teamId")
}
if (clientId != null && clientId > 0) {
sql.append(" AND c.id = :clientId")
}

val args = mutableMapOf(
"teamId" to teamId,
"clientId" to clientId,
"remainedDate" to remainedDate,
"remainedDateTo" to remainedDateTo
)
return jdbcDao.queryForList(sql.toString(), args)
}
} }

+ 51
- 39
src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt View File

@@ -173,51 +173,30 @@ class ReportController(


@PostMapping("/downloadLateStartReport") @PostMapping("/downloadLateStartReport")
@Throws(ServletRequestBindingException::class, IOException::class) @Throws(ServletRequestBindingException::class, IOException::class)
// fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest): ResponseEntity<ByteArrayResource> {
// //val reportDate = projectRepository.findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual().orElseThrow()//to do
// val team = teamRepository.findById(request.teamId).orElseThrow()
// //val customer = customerRepository.findById(request.clientId).orElseThrow()
//ReportController.kt
fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest): ResponseEntity<ByteArrayResource> {
val team = teamRepository.findById(request.teamId).orElse(null)
val teams = if (team == null) teamRepository.findAll() else listOf(team)
val customer = customerRepository.findById(request.clientId).orElse(null)
val customers = if (customer == null) customerRepository.findAll() else listOf(customer)
val projects = projectRepository.findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual(request.remainedDate, request.remainedDateTo)


// val reportResult: ByteArray = excelReportService.generateLateStartReport(team,customer,reportDate)
// val headers = HttpHeaders()
// val filename = "Late_Start_Report_${LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))}.xlsx"
// headers.add("filename", "${URLEncoder.encode(filename, "UTF-8")}")
// // Logging the filename to ensure it's correctly formatted
// println("Generated filename for download: $filename")

// return ResponseEntity.ok()
// .headers(headers)
// .contentLength(reportResult.size.toLong())
// .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
// .body(ByteArrayResource(reportResult))
// }

fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest): ResponseEntity<ByteArrayResource> {
val team = teamRepository.findById(request.teamId).orElseThrow {
Exception("Team not found with ID: ${request.teamId}")
}
val customer = customerRepository.findByName(request.customer).orElseThrow()
// ?: throw Exception("Customer not found with name: ${request.customer}")
val projects = projectRepository.findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual(request.remainedDateFrom, request.remainedDateTo)
if (projects.isEmpty()) {
throw Exception("No projects found for the given date: ${request.reportDate}")
val reportResult: ByteArray = try {
excelReportService.generateLateStartReport(request.teamId, request.clientId,
request.remainedDate, request.remainedDateTo,teams,customers)
} catch (e: Exception) {
throw Exception("Error generating report", e)
} }


val reportResult: ByteArray = try {
excelReportService.generateLateStartReport(team, customer, projects)
} catch (e: Exception) {
throw Exception("Error generating report", e)
}
val headers = HttpHeaders() val headers = HttpHeaders()
val formattedDate = request.reportDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
val currentDate = LocalDate.now() // Get the current date
val formattedDate = currentDate.format(DateTimeFormatter.ofPattern("yyyy_MM_dd")) // Format the current date
val filename = "Late_Start_Report_$formattedDate.xlsx" val filename = "Late_Start_Report_$formattedDate.xlsx"
headers.add("Content-Disposition", "attachment; filename=${URLEncoder.encode(filename, "UTF-8")}")
//headers.add("Content-Disposition", "attachment; filename=filename}")
headers.add("filename", "${URLEncoder.encode(filename, "UTF-8")}")

println("Generated filename for download: $filename") println("Generated filename for download: $filename")

return ResponseEntity.ok() return ResponseEntity.ok()
.headers(headers) .headers(headers)
.contentLength(reportResult.size.toLong()) .contentLength(reportResult.size.toLong())
@@ -225,6 +204,39 @@ fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest)
.body(ByteArrayResource(reportResult)) .body(ByteArrayResource(reportResult))
} }


//fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest): ResponseEntity<ByteArrayResource> {
// val team = teamRepository.findById(request.teamId).orElseThrow {
// Exception("Team not found with ID: ${request.teamId}")
// }
//
// val customer = customerRepository.findByName(request.customer).orElseThrow()
// // ?: throw Exception("Customer not found with name: ${request.customer}")
//
// val projects = projectRepository.findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual(request.remainedDateFrom, request.remainedDateTo)
// if (projects.isEmpty()) {
// throw Exception("No projects found for the given date: ${request.reportDate}")
// }
//
// val reportResult: ByteArray = try {
// excelReportService.generateLateStartReport(team, customer, projects)
// } catch (e: Exception) {
// throw Exception("Error generating report", e)
// }
//
// val headers = HttpHeaders()
// val formattedDate = request.reportDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
// val filename = "Late_Start_Report_$formattedDate.xlsx"
// headers.add("Content-Disposition", "attachment; filename=${URLEncoder.encode(filename, "UTF-8")}")
//
// println("Generated filename for download: $filename")
//
// return ResponseEntity.ok()
// .headers(headers)
// .contentLength(reportResult.size.toLong())
// .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
// .body(ByteArrayResource(reportResult))
// }

// For API TESTING // For API TESTING
@GetMapping("/financialReport/{id}") @GetMapping("/financialReport/{id}")
fun getFinancialReport(@PathVariable id: Long): List<Map<String, Any>> { fun getFinancialReport(@PathVariable id: Long): List<Map<String, Any>> {


+ 1
- 3
src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt View File

@@ -26,10 +26,8 @@ data class StaffMonthlyWorkHourAnalysisReportRequest (
data class LateStartReportRequest ( data class LateStartReportRequest (
val teamId: Long, val teamId: Long,
val clientId: Long, val clientId: Long,
val remainedDateFrom: LocalDate,
val remainedDate: LocalDate,
val remainedDateTo: LocalDate, val remainedDateTo: LocalDate,
val customer: String,
val reportDate: LocalDate
) )
data class ProjectResourceOverconsumptionReport ( data class ProjectResourceOverconsumptionReport (
val teamId: Long?, val teamId: Long?,


Loading…
Cancel
Save