瀏覽代碼

report update

tags/Baseline_30082024_BACKEND_UAT
leoho2fi 1 年之前
父節點
當前提交
58dffe7af0
共有 3 個文件被更改,包括 301 次插入102 次删除
  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 查看文件

@@ -20,6 +20,10 @@ import org.apache.poi.ss.util.CellAddress
import org.apache.poi.ss.util.CellRangeAddress
import org.apache.poi.ss.util.CellUtil
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.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Service
@@ -30,6 +34,9 @@ import java.sql.Time
import java.time.LocalDate
import java.time.YearMonth
import java.time.format.DateTimeFormatter
import java.time.ZoneId
import java.time.format.DateTimeParseException
import java.time.temporal.ChronoUnit
import java.util.*


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

@Throws(IOException::class)
fun generateLateStartReport(
team: Team,
customer: Customer,
project: List<Project>,
teamId: Long,
clientId: Long,
remainedDate:LocalDate,
remainedDateTo:LocalDate,
teams: List<Team>,
customers: List<Customer>,
): 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(
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()
workbook.write(outputStream)
workbook.close()

return outputStream.toByteArray()
}

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

//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
}
val timesNewRomanLarge = workbook.createFont().apply {
fontName = "Times New Roman"
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)
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 ->
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
}

// Apply styles from A6 to J6 (bold, bottom border, center alignment)
val styleA6ToJ6 = workbook.createCellStyle().apply {
setFont(boldFont)
setFont(timesNewRoman) // Use the standard bold font for consistency
alignment = HorizontalAlignment.CENTER
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
}
}
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>> {
@@ -1989,4 +2141,41 @@ open class ReportService(

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 查看文件

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

@PostMapping("/downloadLateStartReport")
@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 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"
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")

return ResponseEntity.ok()
.headers(headers)
.contentLength(reportResult.size.toLong())
@@ -225,6 +204,39 @@ fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest)
.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
@GetMapping("/financialReport/{id}")
fun getFinancialReport(@PathVariable id: Long): List<Map<String, Any>> {


+ 1
- 3
src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt 查看文件

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


Loading…
取消
儲存