Преглед на файлове

report add subsidiary

tags/Baseline_30082024_BACKEND_UAT
leoho2fi преди 1 година
родител
ревизия
b1207ceeac
променени са 3 файла, в които са добавени 85 реда и са изтрити 111 реда
  1. +67
    -70
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  2. +17
    -41
      src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt
  3. +1
    -0
      src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt

+ 67
- 70
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt Целия файл

@@ -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
}
}
@@ -2542,33 +2530,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(


+ 17
- 41
src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt Целия файл

@@ -4,11 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.ffii.tsms.modules.data.entity.*
//import com.ffii.tsms.modules.data.entity.projections.FinancialStatusReportInfo
import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo
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.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
@@ -19,7 +17,6 @@ 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
@@ -32,15 +29,11 @@ import java.io.IOException
import java.time.LocalDate
import java.net.URLEncoder
import java.time.format.DateTimeFormatter
import org.springframework.stereotype.Controller
import org.springframework.data.jpa.repository.JpaRepository
import com.ffii.tsms.modules.project.entity.Project
import com.ffii.tsms.modules.project.service.SubsidiaryService
import com.ffii.tsms.modules.report.web.model.*
import com.ffii.tsms.modules.timesheet.entity.Timesheet
import org.springframework.data.domain.Example
import org.springframework.data.domain.ExampleMatcher
import java.time.temporal.ChronoUnit

@RestController
@RequestMapping("/reports")
@@ -160,6 +153,7 @@ class ReportController(
val args: MutableMap<String, Any> = mutableMapOf(
"status" to request.status,
"lowerLimit" to lowerLimit

)
if (request.teamId != null) {
args["teamId"] = request.teamId
@@ -222,9 +216,24 @@ class ReportController(
val customers = if (customer == null) customerRepository.findAll() else listOf(customer)
val projects = projectRepository.findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual(request.remainedDate, request.remainedDateTo)

println(request.clientId)
println(request.type)

val client = if (request.clientId.equals(0) || request.type != "client") null else customerRepository.findById(request.clientId.toLong()).orElse(null)
val subsidiary = if (request.clientId.equals(0) || request.type != "subsidiary") null else subsidiaryRepository.findById(request.clientId.toLong()).orElse(null)
val searchedClient = if (client != null) { // For display use
client.code + " - " + client.name
} else if (subsidiary != null) {
subsidiary.code + " - " + subsidiary.name
} else {
"All"
}
println(searchedClient)

val reportResult: ByteArray = try {
excelReportService.generateLateStartReport(request.teamId, request.clientId,
request.remainedDate, request.remainedDateTo,teams,customers)
request.remainedDate, request.remainedDateTo,teams,customers, searchedClient,request.type
)
} catch (e: Exception) {
throw Exception("Error generating report", e)
}
@@ -245,39 +254,6 @@ class ReportController(
.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
- 0
src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt Целия файл

@@ -42,6 +42,7 @@ data class LateStartReportRequest (
val clientId: Long,
val remainedDate: LocalDate,
val remainedDateTo: LocalDate,
val type: String,
)
data class ProjectResourceOverconsumptionReport (
val teamId: Long?,


Зареждане…
Отказ
Запис