|
|
@@ -23,6 +23,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 |
|
|
@@ -33,6 +37,8 @@ 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.* |
|
|
|
import kotlin.jvm.optionals.getOrElse |
|
|
@@ -279,19 +285,33 @@ 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() |
|
|
|
} |
|
|
|
|
|
|
@@ -1384,81 +1404,210 @@ open class ReportService( |
|
|
|
return workbook |
|
|
|
} |
|
|
|
|
|
|
|
//createLateStartReport |
|
|
|
private fun createLateStartReport( |
|
|
|
team: Team, |
|
|
|
customer: Customer, |
|
|
|
project: List<Project>, |
|
|
|
templatePath: String |
|
|
|
): Workbook { |
|
|
|
//createLateStartReport |
|
|
|
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() |
|
|
|
|
|
|
|
val resource = ClassPathResource(templatePath) |
|
|
|
val templateInputStream = resource.inputStream |
|
|
|
val workbook: Workbook = XSSFWorkbook(templateInputStream) |
|
|
|
val sheet = workbook.getSheetAt(0) |
|
|
|
// Apply standard and custom fonts |
|
|
|
applyStandardAndCustomFonts(workbook, sheet) |
|
|
|
|
|
|
|
// 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) |
|
|
|
// Formatting the date in cell C2 |
|
|
|
setDateInCellC2(workbook, sheet) |
|
|
|
|
|
|
|
setTeamAndClientIds(sheet, team, teamId, customer, clientId) |
|
|
|
|
|
|
|
// Start populating project data starting at row 7 |
|
|
|
val startRow = 6 // 0-based index for row 7 |
|
|
|
val projectDataRow = sheet.createRow(startRow) |
|
|
|
// Set data and conditional formatting in columns |
|
|
|
setDataAndConditionalFormatting(workbook, sheet, lateStartData, evaluator) |
|
|
|
|
|
|
|
// Styling for cell A1: Font size 16 and bold |
|
|
|
val headerFont = workbook.createFont().apply { |
|
|
|
// Auto-size columns to fit content |
|
|
|
autoSizeColumns(sheet) |
|
|
|
|
|
|
|
return workbook |
|
|
|
} |
|
|
|
|
|
|
|
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>> { |
|
|
@@ -2340,4 +2489,41 @@ open class ReportService( |
|
|
|
|
|
|
|
return outputStream.toByteArray() |
|
|
|
} |
|
|
|
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) |
|
|
|
} |
|
|
|
} |