|
|
@@ -5,35 +5,23 @@ import com.ffii.tsms.modules.data.entity.Customer |
|
|
|
import com.ffii.tsms.modules.data.entity.Salary |
|
|
|
import com.ffii.tsms.modules.data.entity.Staff |
|
|
|
import com.ffii.tsms.modules.data.entity.Team |
|
|
|
import com.ffii.tsms.modules.project.entity.GradeAllocation |
|
|
|
import com.ffii.tsms.modules.project.entity.Invoice |
|
|
|
import com.ffii.tsms.modules.project.entity.Milestone |
|
|
|
import com.ffii.tsms.modules.project.entity.Project |
|
|
|
import com.ffii.tsms.modules.report.web.model.costAndExpenseRequest |
|
|
|
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.* |
|
|
|
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 |
|
|
|
import java.io.ByteArrayOutputStream |
|
|
|
import java.io.IOException |
|
|
|
import java.math.BigDecimal |
|
|
|
import java.sql.Time |
|
|
|
import java.time.LocalDate |
|
|
|
import java.time.YearMonth |
|
|
|
import java.time.format.DateTimeFormatter |
|
|
@@ -41,7 +29,6 @@ import java.time.ZoneId |
|
|
|
import java.time.format.DateTimeParseException |
|
|
|
import java.time.temporal.ChronoUnit |
|
|
|
import java.util.* |
|
|
|
import kotlin.jvm.optionals.getOrElse |
|
|
|
|
|
|
|
|
|
|
|
data class DayInfo(val date: String?, val weekday: String?) |
|
|
@@ -287,26 +274,22 @@ open class ReportService( |
|
|
|
fun generateLateStartReport( |
|
|
|
teamId: Long, |
|
|
|
clientId: Long, |
|
|
|
remainedDate:LocalDate, |
|
|
|
remainedDateTo:LocalDate, |
|
|
|
remainedDate: LocalDate, |
|
|
|
remainedDateTo: LocalDate, |
|
|
|
teams: List<Team>, |
|
|
|
customers: List<Customer>, |
|
|
|
searchedClient: String, |
|
|
|
type: String, |
|
|
|
): 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 projectDetails = getLateStartDetails(teamId, clientId, remainedDate, remainedDateTo, type) |
|
|
|
val workbook: Workbook = createLateStartReport( |
|
|
|
teams, |
|
|
|
customers, |
|
|
|
LATE_START_REPORT, // Ensure you provide the correct path to your template |
|
|
|
teamId, |
|
|
|
clientId, |
|
|
|
projectDetails |
|
|
|
projectDetails, |
|
|
|
searchedClient |
|
|
|
) |
|
|
|
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() |
|
|
|
workbook.write(outputStream) |
|
|
@@ -1448,7 +1431,8 @@ open class ReportService( |
|
|
|
templatePath: String, |
|
|
|
teamId: Long, |
|
|
|
clientId: Long, |
|
|
|
lateStartData: List<Map<String, Any>> |
|
|
|
lateStartData: List<Map<String, Any>>, |
|
|
|
searchedClient: String |
|
|
|
): Workbook { |
|
|
|
val resource = ClassPathResource(templatePath) |
|
|
|
val templateInputStream = resource.inputStream |
|
|
@@ -1463,7 +1447,7 @@ open class ReportService( |
|
|
|
setDateInCellC2(workbook, sheet) |
|
|
|
|
|
|
|
// Populate team and client information based on IDs |
|
|
|
setTeamAndClientIds(sheet, team, teamId, customer, clientId) |
|
|
|
setTeamAndClientIds(sheet, team, teamId, searchedClient) |
|
|
|
|
|
|
|
// Process late start data, and apply data and conditional formatting to the sheet |
|
|
|
setDataAndConditionalFormatting(workbook, sheet, lateStartData, evaluator) |
|
|
@@ -1505,16 +1489,16 @@ open class ReportService( |
|
|
|
cell.cellStyle = boldCellStyle |
|
|
|
} |
|
|
|
|
|
|
|
// Apply a bold, bottom-bordered, center-aligned style to header cells from A6 to J6 |
|
|
|
val styleA6ToJ6 = workbook.createCellStyle().apply { |
|
|
|
// Apply a bold, bottom-bordered, center-aligned style to header cells from A6 to K6 |
|
|
|
val styleA6ToK6 = workbook.createCellStyle().apply { |
|
|
|
setFont(timesNewRoman) // Use the standard bold font for consistency |
|
|
|
alignment = HorizontalAlignment.CENTER |
|
|
|
borderBottom = BorderStyle.THIN |
|
|
|
} |
|
|
|
val rowA6 = sheet.getRow(5) ?: sheet.createRow(5) |
|
|
|
(0..9).forEach { colIndex -> |
|
|
|
(0..10).forEach { colIndex -> |
|
|
|
val cell = rowA6.getCell(colIndex) ?: rowA6.createCell(colIndex) |
|
|
|
cell.cellStyle = styleA6ToJ6 |
|
|
|
cell.cellStyle = styleA6ToK6 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@@ -1545,32 +1529,32 @@ open class ReportService( |
|
|
|
// Only apply conditional formatting if there is data |
|
|
|
if (lateStartData.isNotEmpty()) { |
|
|
|
val sheetCF = sheet.sheetConditionalFormatting |
|
|
|
val rule1 = sheetCF.createConditionalFormattingRule("J7 < 60") |
|
|
|
val rule1 = sheetCF.createConditionalFormattingRule("K7 < 60") |
|
|
|
val pattern1 = rule1.createPatternFormatting().apply { |
|
|
|
fillBackgroundColor = IndexedColors.RED.index |
|
|
|
fillPattern = PatternFormatting.SOLID_FOREGROUND |
|
|
|
} |
|
|
|
val rule2 = sheetCF.createConditionalFormattingRule("J7 >= 60") |
|
|
|
val rule2 = sheetCF.createConditionalFormattingRule("K7 >= 60") |
|
|
|
val pattern2 = rule2.createPatternFormatting().apply { |
|
|
|
fillBackgroundColor = IndexedColors.YELLOW.index |
|
|
|
fillPattern = PatternFormatting.SOLID_FOREGROUND |
|
|
|
} |
|
|
|
val region = CellRangeAddress(6, dataRowIndex - 1, 9, 9) // Column J |
|
|
|
val region = CellRangeAddress(6, dataRowIndex - 1, 10, 10) // Column K |
|
|
|
sheetCF.addConditionalFormatting(arrayOf(region), arrayOf(rule1, rule2)) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Sets team and client IDs in the sheet to manage data visibility based on the selected team and client |
|
|
|
private fun setTeamAndClientIds(sheet: Sheet, team: List<Team>, teamId: Long, customer: List<Customer>, clientId: Long) { |
|
|
|
private fun setTeamAndClientIds(sheet: Sheet, team: List<Team>, teamId: Long, searchedClient: String) { |
|
|
|
// Insert the team name or "All" if no specific team is selected 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") |
|
|
|
|
|
|
|
// Insert the client name or "All" if no specific client is selecred in cell C4 |
|
|
|
// Insert the client name or "All" directly 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") |
|
|
|
clientIdCell.setCellValue(searchedClient) |
|
|
|
} |
|
|
|
|
|
|
|
// Populates a single row with data from a map and applies formatting and formulas |
|
|
@@ -1612,9 +1596,12 @@ open class ReportService( |
|
|
|
val customerNameCell = row.createCell(4) |
|
|
|
customerNameCell.setCellValue(data["customer_name"] as String) |
|
|
|
|
|
|
|
// // Column F: Project Plan Start Date |
|
|
|
// Parse the project start date and apply the date format |
|
|
|
val projectPlanStartCell = row.createCell(5) |
|
|
|
// NEW Column F: Subsidiary Name |
|
|
|
val subsidiaryNameCell = row.createCell(5) |
|
|
|
subsidiaryNameCell.setCellValue(data["subsidiary_name"] as String) |
|
|
|
|
|
|
|
// Column G: Project Plan Start Date |
|
|
|
val projectPlanStartCell = row.createCell(6) |
|
|
|
try { |
|
|
|
val date = LocalDate.parse(data["project_plan_start"].toString(), DateTimeFormatter.ISO_LOCAL_DATE) |
|
|
|
projectPlanStartCell.setCellValue(Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant())) |
|
|
@@ -1623,17 +1610,17 @@ open class ReportService( |
|
|
|
} |
|
|
|
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}") |
|
|
|
// Column H: Calculated Date Difference (days since project start from today) |
|
|
|
val daysDifferenceCell = row.createCell(7) |
|
|
|
daysDifferenceCell.setCellFormula("C$2-G${row.rowNum + 1}") |
|
|
|
daysDifferenceCell.cellStyle = centerAlignedStyle |
|
|
|
|
|
|
|
// Column H: Task Group Name |
|
|
|
val taskGroupNameCell = row.createCell(7) |
|
|
|
// Column I: Task Group Name |
|
|
|
val taskGroupNameCell = row.createCell(8) |
|
|
|
taskGroupNameCell.setCellValue(data["task_group_name"] as String) |
|
|
|
|
|
|
|
// Column I: Milestone End Date |
|
|
|
val dateCell = row.createCell(8) // Milestone end date in column I |
|
|
|
// Column J: Milestone End Date |
|
|
|
val dateCell = row.createCell(9) // 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())) |
|
|
@@ -1646,15 +1633,16 @@ open class ReportService( |
|
|
|
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 |
|
|
|
// Ensure days difference is not negative |
|
|
|
val adjustedDaysBetween = if (daysBetween < 0) 0 else daysBetween |
|
|
|
val daysDifferenceCellJ = row.createCell(10) |
|
|
|
daysDifferenceCellJ.setCellValue(adjustedDaysBetween.toDouble()) // Set as double for consistency |
|
|
|
daysDifferenceCellJ.cellStyle = centerAlignedStyle |
|
|
|
} |
|
|
|
|
|
|
|
// Automatically adjusts the width if all columns in the sheet to fit their content optimally. |
|
|
|
private fun autoSizeColumns(sheet: Sheet) { |
|
|
|
for (colIndex in 0..9) { // Iterate through columns A to J |
|
|
|
for (colIndex in 0..10) { // Iterate through columns A to K |
|
|
|
sheet.autoSizeColumn(colIndex) // Auto-size each column based on its content |
|
|
|
} |
|
|
|
} |
|
|
@@ -2549,33 +2537,42 @@ open class ReportService( |
|
|
|
|
|
|
|
return outputStream.toByteArray() |
|
|
|
} |
|
|
|
open fun getLateStartDetails(teamId: Long?, clientId: Long?, remainedDate: LocalDate, remainedDateTo: LocalDate): List<Map<String, Any>> { |
|
|
|
open fun getLateStartDetails(teamId: Long?, clientId: Long?, remainedDate: LocalDate, remainedDateTo: LocalDate, type: String?): 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 |
|
|
|
SELECT |
|
|
|
p.code AS project_code, |
|
|
|
p.name AS project_name, |
|
|
|
t.name AS team_name, |
|
|
|
c.name AS customer_name, |
|
|
|
s.name AS subsidiary_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 subsidiary s ON p.customerSubsidiaryId = s.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") |
|
|
|
// } |
|
|
|
if (clientId != null && clientId > 0) { |
|
|
|
sql.append(" AND c.id = :clientId") |
|
|
|
when (type) { |
|
|
|
"subsidiary" -> sql.append(" AND s.id = :clientId") |
|
|
|
"client" -> sql.append(" AND c.id = :clientId") |
|
|
|
"All" -> {} // No action needed if type is "All" |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
val args = mutableMapOf( |
|
|
|