Sfoglia il codice sorgente

Merge branch 'master' of https://git.2fi-solutions.com/davidhui/TSMS-backend

# Conflicts:
#	src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
tags/Baseline_30082024_BACKEND_UAT
leoho2fi 1 anno fa
parent
commit
ba0aaf717b
9 ha cambiato i file con 301 aggiunte e 115 eliminazioni
  1. +2
    -1
      src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java
  2. +20
    -0
      src/main/java/com/ffii/tsms/modules/data/entity/projections/FinancialStatusReportInfo.kt
  3. +55
    -10
      src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt
  4. +1
    -1
      src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt
  5. +7
    -1
      src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt
  6. +1
    -1
      src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt
  7. +1
    -1
      src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt
  8. +212
    -99
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  9. +2
    -1
      src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt

+ 2
- 1
src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java Vedi File

@@ -5,6 +5,7 @@ import com.ffii.core.support.AbstractRepository;
import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo;
import org.springframework.data.repository.query.Param;

import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -13,7 +14,7 @@ public interface StaffRepository extends AbstractRepository<Staff, Long> {
List<StaffSearchInfo> findStaffSearchInfoByAndDeletedFalse();
List<StaffSearchInfo> findStaffSearchInfoByAndDeletedFalseAndTeamIdIsNull();

List<StaffSearchInfo> findAllStaffSearchInfoByIdIn(List<Long> ids);
List<StaffSearchInfo> findAllStaffSearchInfoByIdIn(List<Serializable> ids);
Optional<Staff> findByStaffId(@Param("staffId") String staffId);

Optional<StaffSearchInfo> findStaffSearchInfoById(@Param("id") Long id);


+ 20
- 0
src/main/java/com/ffii/tsms/modules/data/entity/projections/FinancialStatusReportInfo.kt Vedi File

@@ -0,0 +1,20 @@
package com.ffii.tsms.modules.data.entity.projections

import java.math.BigDecimal
import java.time.LocalDate

interface FinancialStatusReportInfo {
val code: String
val description: String
val client: String
val teamLead: String
val planStart: LocalDate
val planEnd: LocalDate
val expectedTotalFee: BigDecimal
val staff: String
val normalConsumed: BigDecimal
val otConsumed: BigDecimal
val hourlyRate: BigDecimal
val sumIssuedAmount: BigDecimal
val sumPaidAmount: BigDecimal
}

+ 55
- 10
src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt Vedi File

@@ -23,6 +23,9 @@ open class DashboardService(

fun CustomerSubsidiary(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder("select"
+ " row_number()OVER ("
+ " ORDER BY c.id"
+ " ) as id,"
+ " c.id as customerId,"
+ " c.name as customerName,"
+ " c.code as customerCode,"
@@ -30,18 +33,17 @@ open class DashboardService(
+ " c.district as cutomerDistrict,"
+ " c.brNo as customerBrNo,"
+ " c.typeId as customerTypeId,"
+ " s.id as subsidiaryId,"
+ " s.name as subsidiaryName,"
+ " s.code as subsidiaryCode,"
+ " s.address as subsidiaryAddress,"
+ " s.district as subsidiaryDistrict,"
+ " s.brNo as subsidiaryBrNo,"
+ " s.typeId as subsidiaryTypeId,"
+ " coalesce (s.id,'-') as subsidiaryId,"
+ " coalesce (s.name,'-') as subsidiaryName,"
+ " coalesce (s.code,'-') as subsidiaryCode,"
+ " coalesce (s.address,'-') as subsidiaryAddress,"
+ " coalesce (s.district,'-') as subsidiaryDistrict,"
+ " coalesce (s.brNo,'-') as subsidiaryBrNo,"
+ " coalesce (s.typeId,'-') as subsidiaryTypeId,"
+ " count(p.id) as projectNo"
+ " from customer c"
+ " left join customer_subsidiary cs on c.id = cs.customerId"
+ " left join subsidiary s on cs.subsidiaryId = s.id"
+ " left join project p on cs.customerId = p.customerId and cs.subsidiaryId = p.customerSubsidiaryId "
+ " left join project p on c.id = p.customerId"
+ " left join subsidiary s on p.customerSubsidiaryId = s.id"
+ " where c.deleted = 0"
)
if (args != null) {
@@ -94,4 +96,47 @@ open class DashboardService(

return jdbcDao.queryForList(sql.toString(), args)
}

fun searchCustomerNonSubsidiaryProject(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder("select"
+ " p.id as id,"
+ " p.id as projectId,"
+ " p.code as projectCode,"
+ " p.name as projectName,"
+ " te.code as team,"
+ " s.name as teamLead,"
+ " tg.name as expectedStage,"
+ " p.totalManhour as budgetedManhour,"
+ " sum(t.normalConsumed) + sum(t.otConsumed) as spentManhour,"
+ " p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed) as remainedManhour,"
+ " coalesce (round(((sum(t.normalConsumed) - sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage,"
+ " DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d') as comingPaymentMilestone"
+ " from project p"
+ " left join project_task pt on p.id = pt.project_id"
+ " left join timesheet t on pt.id = t.projectTaskId"
+ " left join team te on p.teamLead = te.teamLead"
+ " left join staff s on te.teamLead = s.id"
+ " left join milestone m on p.id = m.projectId and curdate() >= m.startDate and curdate() <= m.endDate"
+ " left join task_group tg on m.taskGroupId = tg.id"
+ " left join ("
+ " select"
+ " mp.date as comingPaymentMilestone"
+ " from project p"
+ " left join milestone m on p.id = m.projectId"
+ " left join milestone_payment mp on m.id = mp.milestoneId"
+ " where p.customerId = :customerId"
+ " and isNull(p.customerSubsidiaryId)"
+ " and mp.date >= curdate()"
+ " order by date asc"
+ " limit 1"
+ " ) milestonePayment on 1=1"
+ " where p.customerId = :customerId"
+ " and isNull(p.customerSubsidiaryId)"
+ " group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone"
)

return jdbcDao.queryForList(sql.toString(), args)
}
}



+ 1
- 1
src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt Vedi File

@@ -35,7 +35,7 @@ open class StaffsService(
) : AbstractBaseEntityService<Staff, Long, StaffRepository>(jdbcDao, staffRepository) {
open fun getTeamLeads(): List<StaffSearchInfo> {
// TODO: Replace by actual logic
val teamIds = teamRepository.findAll().map { team -> team.staff.id }
val teamIds = teamRepository.findAll().filter { team -> team.deleted == false }.map { team -> team.staff.id }
return staffRepository.findAllStaffSearchInfoByIdIn(teamIds)
}



+ 7
- 1
src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt Vedi File

@@ -54,6 +54,7 @@ class DashboardController(
val customerId = request?.getParameter("customerId")
val subsidiaryId = request?.getParameter("subsidiaryId")
val args = mutableMapOf<String, Any>()
var result: List<Map<String, Any>> = emptyList()
if (customerId != null) {
args["customerId"] = customerId
}
@@ -61,6 +62,11 @@ class DashboardController(
args["subsidiaryId"] = subsidiaryId
}

return dashboardService.searchCustomerSubsidiaryProject(args)
if (customerId != null && subsidiaryId != null) {
result = dashboardService.searchCustomerSubsidiaryProject(args)
} else if (customerId != null && subsidiaryId == null){
result = dashboardService.searchCustomerNonSubsidiaryProject(args)
}
return result
}
}

+ 1
- 1
src/main/java/com/ffii/tsms/modules/project/entity/ProjectRepository.kt Vedi File

@@ -15,5 +15,5 @@ interface ProjectRepository : AbstractRepository<Project, Long> {

fun findInvoiceInfoSearchInfoById(id: Long): List<InvoiceInfoSearchInfo>

fun findFirstByIsClpProjectAndIdNotOrderByIdDesc(isClpProject: Boolean, id: Serializable?): Project?
fun findFirstByIsClpProjectAndIdIsNotOrderByIdDesc(isClpProject: Boolean, id: Serializable?): Project?
}

+ 1
- 1
src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt Vedi File

@@ -131,7 +131,7 @@ open class ProjectsService(
val checkIsClpProject = isClpProject != null && isClpProject
val prefix = if (checkIsClpProject) "C" else "M"

val latestProjectCode = projectRepository.findFirstByIsClpProjectAndIdNotOrderByIdDesc(checkIsClpProject, project.id)
val latestProjectCode = projectRepository.findFirstByIsClpProjectAndIdIsNotOrderByIdDesc(checkIsClpProject, project.id ?: -1)

if (latestProjectCode != null) {
val lastFix = latestProjectCode.code!!.split("-")[1] //C-0001 -> 0001


+ 212
- 99
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt Vedi File

@@ -1,12 +1,17 @@
package com.ffii.tsms.modules.report.service

import com.ffii.core.support.JdbcDao
import com.ffii.tsms.modules.data.entity.Salary
import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.project.entity.Invoice
import com.ffii.tsms.modules.project.entity.Project
import com.ffii.tsms.modules.timesheet.entity.Leave
import com.ffii.tsms.modules.timesheet.entity.Timesheet
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.xssf.usermodel.XSSFWorkbook
import org.springframework.core.io.ClassPathResource
@@ -16,11 +21,15 @@ import java.io.IOException
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*
import org.apache.poi.ss.util.CellAddress
import kotlin.time.times

data class DayInfo(val date: String?, val weekday: String?)

@Service
open class ReportService {
open class ReportService(
private val jdbcDao: JdbcDao,
) {
private val logger: Log = LogFactory.getLog(javaClass)
private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd")
private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER)

@@ -32,8 +41,8 @@ open class ReportService {

// ==============================|| GENERATE REPORT ||============================== //

fun genFinancialStatusReport(): ByteArray {
val workbook: Workbook = createFinancialStatusReport(FINANCIAL_STATUS_REPORT)
fun genFinancialStatusReport(projectId: Long): ByteArray {
val workbook: Workbook = createFinancialStatusReport(FINANCIAL_STATUS_REPORT, projectId)

val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
workbook.write(outputStream)
@@ -41,8 +50,13 @@ open class ReportService {

return outputStream.toByteArray()
}

@Throws(IOException::class)
fun generateProjectCashFlowReport(project: Project, invoices: List<Invoice>, timesheets: List<Timesheet>): ByteArray {
fun generateProjectCashFlowReport(
project: Project,
invoices: List<Invoice>,
timesheets: List<Timesheet>
): ByteArray {
// Generate the Excel report with query results
val workbook: Workbook = createProjectCashFlowReport(project, invoices, timesheets, PROJECT_CASH_FLOW_REPORT)

@@ -55,9 +69,22 @@ open class ReportService {
}

@Throws(IOException::class)
fun generateStaffMonthlyWorkHourAnalysisReport(month: LocalDate, staff: Staff, timesheets: List<Timesheet>, leaves: List<Leave>, projectList: List<String>): ByteArray {
fun generateStaffMonthlyWorkHourAnalysisReport(
month: LocalDate,
staff: Staff,
timesheets: List<Timesheet>,
leaves: List<Leave>,
projectList: List<String>
): ByteArray {
// Generate the Excel report with query results
val workbook: Workbook = createStaffMonthlyWorkHourAnalysisReport(month, staff, timesheets, leaves, projectList, MONTHLY_WORK_HOURS_ANALYSIS_REPORT)
val workbook: Workbook = createStaffMonthlyWorkHourAnalysisReport(
month,
staff,
timesheets,
leaves,
projectList,
MONTHLY_WORK_HOURS_ANALYSIS_REPORT
)

// Write the workbook to a ByteArrayOutputStream
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
@@ -82,7 +109,7 @@ open class ReportService {

@Throws(IOException::class)
fun generateLateStartReport(project: Project): ByteArray {
val workbook: Workbook = createLateStartReport(project,LATE_START_REPORT)
val workbook: Workbook = createLateStartReport(project, LATE_START_REPORT)
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
workbook.write(outputStream)
workbook.close()
@@ -94,7 +121,8 @@ open class ReportService {
// EX01 Financial Report
private fun createFinancialStatusReport(
templatePath: String,
) : Workbook {
projectId: Long,
): Workbook {
val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream
val workbook: Workbook = XSSFWorkbook(templateInputStream)
@@ -117,42 +145,42 @@ open class ReportService {
val sheet: Sheet = workbook.getSheetAt(0)

// accounting style + comma style
val accountingStyle = workbook.createDataFormat() .getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)")
val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)")

var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field
var columnIndex = 2
sheet.getRow(rowIndex).getCell(columnIndex).apply {
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(FORMATTED_TODAY)
}

rowIndex = 2
sheet.getRow(rowIndex).getCell(columnIndex).apply {
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(project.code)
}

rowIndex = 3
sheet.getRow(rowIndex).getCell(columnIndex).apply {
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(project.name)
}

rowIndex = 4
sheet.getRow(rowIndex).getCell(columnIndex).apply {
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(if (project.customer?.name == null) "N/A" else project.customer?.name)
}

rowIndex = 5
sheet.getRow(rowIndex).getCell(columnIndex).apply {
sheet.getRow(rowIndex).createCell(columnIndex).apply {
setCellValue(if (project.teamLead?.team?.name == null) "N/A" else project.teamLead?.team?.name)
}

rowIndex = 9
sheet.getRow(rowIndex).apply {
getCell(1).apply {
createCell(1).apply {
setCellValue(project.expectedTotalFee!!)
cellStyle.dataFormat = accountingStyle
}

getCell(2).apply {
createCell(2).apply {
setCellValue(project.expectedTotalFee!! / 0.8)
cellStyle.dataFormat = accountingStyle
}
@@ -160,15 +188,17 @@ open class ReportService {

rowIndex = 10
val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! }
val actualExpenditure = timesheets.sumOf { timesheet -> timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed ?: 0.0)) }
val actualExpenditure = timesheets.sumOf { timesheet ->
timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed
?: 0.0))
}
sheet.getRow(rowIndex).apply {
getCell(1).apply {
// TODO: Replace by actual expenditure
createCell(1).apply {
setCellValue(actualExpenditure)
cellStyle.dataFormat = accountingStyle
}

getCell(2).apply {
createCell(2).apply {
setCellValue(actualIncome.toDouble())
cellStyle.dataFormat = accountingStyle
}
@@ -176,88 +206,137 @@ open class ReportService {

rowIndex = 11
sheet.getRow(rowIndex).apply {
getCell(1).apply {
// TODO: Replace by actual expenditure
createCell(1).apply {
cellFormula = "B10-B11"
cellStyle.dataFormat = accountingStyle
}

getCell(2).apply {
createCell(2).apply {
cellFormula = "C10-C11"
cellStyle.dataFormat = accountingStyle
}
}

// TODO: Add expenditure
rowIndex = 15
val combinedResults = (invoices.map { it.receiptDate } + timesheets.map { it.recordDate }).filterNotNull().sortedBy { it }

val dateFormatter = DateTimeFormatter.ofPattern("MMM YYYY")
combinedResults.forEach { result: LocalDate ->
val invoice = invoices.find { invoice: Invoice -> invoice.receiptDate == result }
val timesheet = timesheets.find { timesheet: Timesheet -> timesheet.recordDate == result}
val combinedResults =
(invoices.map { it.receiptDate } + timesheets.map { it.recordDate }).filterNotNull().sortedBy { it }
.map { it.format(dateFormatter) }.distinct()
val groupedTimesheets = timesheets.sortedBy { it.recordDate }
.groupBy { timesheetEntry -> timesheetEntry.recordDate?.format(dateFormatter).toString() }
.mapValues { (_, timesheetEntries) ->
timesheetEntries.map { timesheet ->
if (timesheet.normalConsumed != null) {
timesheet.normalConsumed!!.plus(timesheet.otConsumed ?: 0.0).times(timesheet.staff!!.salary.hourlyRate.toDouble())
} else if (timesheet.otConsumed != null) {
timesheet.otConsumed!!.plus(timesheet.normalConsumed ?: 0.0).times(timesheet.staff!!.salary.hourlyRate.toDouble())
} else {
0.0
}
}
}

if (invoice != null) {
sheet.getRow(rowIndex++).apply {
val groupedInvoices = invoices.sortedBy { it.receiptDate }.filter { it.paidAmount != null }
.groupBy { invoiceEntry -> invoiceEntry.invoiceDate?.format(dateFormatter).toString() }
.mapValues { (_, invoiceEntries) ->
invoiceEntries.map { invoice ->
mapOf(
"paidAmount" to invoice.paidAmount?.toDouble(),
"description" to invoice.milestonePayment?.description
)
}
}

getCell(0).apply {
setCellValue(result.format(dateFormatter))
}
groupedTimesheets.entries.forEach { (key, value) ->
logger.info("key: $key")
logger.info("value: " + value.sumOf { it })
}

getCell(1).apply {
setCellValue(0.0)
cellStyle.dataFormat = accountingStyle
}
groupedInvoices.entries.forEach { (key, value) ->
logger.info("key: $key")
logger.info("value: " + value)
groupedInvoices[key]!!.forEachIndexed { index, invoice ->
logger.info("index: $index")
logger.info("invoice: $invoice")
invoice.get("paidAmount")
}
}

getCell(2).apply {
setCellValue(invoice.paidAmount!!.toDouble())
cellStyle.dataFormat = accountingStyle
}
combinedResults.forEach { result: String ->

getCell(3).apply {
val lastRow = rowIndex - 1
if (lastRow == 15) {
cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString())
} else {
cellFormula = "IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace("{currentRow}", rowIndex.toString()).replace("{lastRow}", lastRow.toString())
if (groupedInvoices.containsKey(result)) {
groupedInvoices[result]!!.forEachIndexed { _, invoice ->
sheet.getRow(rowIndex++).apply {
createCell(0).apply {
setCellValue(result)
}
cellStyle.dataFormat = accountingStyle
}

getCell(4).apply {
setCellValue(invoice.milestonePayment!!.description!!)
createCell(1).apply {
setCellValue(0.0)
cellStyle.dataFormat = accountingStyle
}

createCell(2).apply {
setCellValue(invoice["paidAmount"] as Double)
cellStyle.dataFormat = accountingStyle
}

createCell(3).apply {
val lastRow = rowIndex - 1
if (lastRow == 15) {
cellFormula =
"C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString())
} else {
cellFormula =
"IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace(
"{currentRow}",
rowIndex.toString()
).replace("{lastRow}", lastRow.toString())
}
cellStyle.dataFormat = accountingStyle
}

createCell(4)?.apply {
setCellValue(invoice["description"].toString())
}
}
}
}

if (timesheet != null) {
if (groupedTimesheets.containsKey(result)) {
sheet.getRow(rowIndex++).apply {

getCell(0).apply {
createCell(0).apply {
setCellValue(result.format(dateFormatter))
}

getCell(1).apply {
setCellValue(timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed ?: 0.0)))
createCell(1).apply {
setCellValue(groupedTimesheets[result]!!.sum())
cellStyle.dataFormat = accountingStyle
}

getCell(2).apply {
createCell(2).apply {
setCellValue(0.0)
cellStyle.dataFormat = accountingStyle
}

getCell(3).apply {
createCell(3).apply {
val lastRow = rowIndex - 1
if (lastRow == 15) {
cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString())
} else {
cellFormula = "IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace("{currentRow}", rowIndex.toString()).replace("{lastRow}", lastRow.toString())
cellFormula =
"IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace(
"{currentRow}",
rowIndex.toString()
).replace("{lastRow}", lastRow.toString())
}
cellStyle.dataFormat = accountingStyle
}

getCell(4).apply {
createCell(4).apply {
setCellValue("Monthly Manpower Expenditure")
}
}
@@ -370,7 +449,7 @@ open class ReportService {
// cellStyle.borderBottom = BorderStyle.DOUBLE
cellStyle.alignment = HorizontalAlignment.CENTER
}
sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1))
sheet.addMergedRegion(CellRangeAddress(rowIndex, rowIndex, 0, 1))
//
rowIndex += 1
sheet.getRow(rowIndex).getCell(0).apply {
@@ -392,7 +471,7 @@ open class ReportService {
cellStyle.alignment = HorizontalAlignment.CENTER
cellStyle.dataFormat = accountingStyle
}
sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1))
sheet.addMergedRegion(CellRangeAddress(rowIndex, rowIndex, 0, 1))
//
rowIndex += 1
sheet.getRow(rowIndex).getCell(0).apply {
@@ -406,7 +485,7 @@ open class ReportService {
cellStyle.dataFormat = accountingStyle
}

sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1))
sheet.addMergedRegion(CellRangeAddress(rowIndex, rowIndex, 0, 1))

rowIndex += 1
sheet.getRow(rowIndex).getCell(0).apply {
@@ -431,12 +510,12 @@ open class ReportService {
// cellStyle.borderBottom = BorderStyle.DOUBLE
}
sheet.getRow(rowIndex).getCell(2).apply {
cellFormula = "C${rowIndex-2}+C${rowIndex-1}"
cellFormula = "C${rowIndex - 2}+C${rowIndex - 1}"
cellStyle.dataFormat = accountingStyle
// cellStyle.borderTop = BorderStyle.THIN
// cellStyle.borderBottom = BorderStyle.DOUBLE
}
sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1))
sheet.addMergedRegion(CellRangeAddress(rowIndex, rowIndex, 0, 1))
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
rowIndex = 7
columnIndex = 2
@@ -465,21 +544,21 @@ open class ReportService {
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sheet.getRow(rowIndex).apply {
getCell(columnIndex).setCellValue("Leave Hours")
getCell(columnIndex).cellStyle.alignment = HorizontalAlignment.CENTER
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sheet.getRow(rowIndex).apply {
getCell(columnIndex).setCellValue("Leave Hours")
getCell(columnIndex).cellStyle.alignment = HorizontalAlignment.CENTER

columnIndex += 1
getCell(columnIndex).setCellValue("Daily Manhour Spent\n" + "(Excluding Leave Hours)")
getCell(columnIndex).cellStyle.alignment = HorizontalAlignment.CENTER
columnSize = columnIndex
}
sheet.addMergedRegion(CellRangeAddress(6,6 , 2, columnIndex))
columnIndex += 1
getCell(columnIndex).setCellValue("Daily Manhour Spent\n" + "(Excluding Leave Hours)")
getCell(columnIndex).cellStyle.alignment = HorizontalAlignment.CENTER
columnSize = columnIndex
}
sheet.addMergedRegion(CellRangeAddress(6, 6, 2, columnIndex))
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for (i in 2 until columnIndex) {
for (k in 0 until rowSize) {
sheet.getRow(8+k).getCell(i).apply {
sheet.getRow(8 + k).getCell(i).apply {
setCellValue(0.0)
cellStyle.dataFormat = accountingStyle
}
@@ -490,14 +569,17 @@ open class ReportService {
if (sheet.getRow(rowIndex - 1).getCell(2).stringCellValue != "Leave Hours") {
// cal daily spent manhour
for (i in 0 until rowSize) {
val cell = sheet.getRow(rowIndex)?.getCell(columnIndex) ?: sheet.getRow(rowIndex + i)?.createCell(columnIndex)
cell?.cellFormula = "SUM(${getColumnAlphabet(2)}${rowIndex+1}:${getColumnAlphabet(columnIndex)}${rowIndex+1})" // should columnIndex - 2
val cell =
sheet.getRow(rowIndex)?.getCell(columnIndex) ?: sheet.getRow(rowIndex + i)?.createCell(columnIndex)
cell?.cellFormula =
"SUM(${getColumnAlphabet(2)}${rowIndex + 1}:${getColumnAlphabet(columnIndex)}${rowIndex + 1})" // should columnIndex - 2
rowIndex++
}
// cal subtotal
for (i in 0 until columnSize) { // minus last col
val cell = sheet.getRow(rowIndex)?.getCell(2) ?: sheet.getRow(rowIndex)?.createCell(2)
cell?.cellFormula = "SUM(${getColumnAlphabet(2)}${rowIndex}:${getColumnAlphabet(columnIndex + i)}${rowIndex})"
cell?.cellFormula =
"SUM(${getColumnAlphabet(2)}${rowIndex}:${getColumnAlphabet(columnIndex + i)}${rowIndex})"
cell?.cellStyle?.dataFormat = accountingStyle
cell?.cellStyle?.setFont(boldFont)
// cell?.cellStyle?.borderTop = BorderStyle.THIN
@@ -506,7 +588,8 @@ open class ReportService {
} else { // just for preview when no data
// cal daily spent manhour
for (i in 0 until rowSize) {
val cell = sheet.getRow(rowIndex)?.getCell(columnIndex) ?: sheet.getRow(rowIndex + i)?.createCell(columnIndex)
val cell =
sheet.getRow(rowIndex)?.getCell(columnIndex) ?: sheet.getRow(rowIndex + i)?.createCell(columnIndex)
cell?.setCellValue("daily spent manhour")
rowIndex++
}
@@ -526,7 +609,7 @@ open class ReportService {

@Throws(IOException::class)
private fun createSalaryList(
salarys : List<Salary>,
salarys: List<Salary>,
templatePath: String,
): Workbook {

@@ -567,29 +650,30 @@ open class ReportService {
project: Project,
templatePath: String
):Workbook{

project
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)
// Populate the row with project data
projectDataRow.createCell(1).setCellValue(project.code) // Column B
projectDataRow.createCell(2).setCellValue(project.name) // Column C
projectDataRow.createCell(3).setCellValue(project.teamLead?.name) // Column D
projectDataRow.createCell(4).setCellValue(project.custLeadName) // Column E
projectDataRow.createCell(5).setCellValue(
project.planStart?.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) ?: "N/A" // Column F
)
// // Start populating project data starting at row 7
// val startRow = 6 // 0-based index for row 7
// val projectDataRow = sheet.createRow(startRow)
// // Populate the row with project data
// projectDataRow.createCell(1).setCellValue(project.code) // Column B
// projectDataRow.createCell(2).setCellValue(project.name) // Column C
// projectDataRow.createCell(3).setCellValue(project.teamLead?.name) // Column D
// projectDataRow.createCell(4).setCellValue(project.custLeadName) // Column E
// projectDataRow.createCell(5).setCellValue(
// project.planStart?.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) ?: "N/A" // Column F
// )
// Styling for cell A1: Font size 16 and bold
val headerFont = workbook.createFont().apply {
@@ -602,7 +686,7 @@ open class ReportService {
val headerCell = sheet.getRow(0)?.getCell(0) ?: sheet.getRow(0).createCell(0)
headerCell.cellStyle = headerCellStyle
headerCell.setCellValue("Report Title")
// Apply styles from A2 to A4 (bold)
val boldFont = workbook.createFont().apply { bold = true }
val boldCellStyle = workbook.createCellStyle().apply { setFont(boldFont) }
@@ -611,7 +695,7 @@ open class ReportService {
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)
@@ -624,7 +708,7 @@ open class ReportService {
val cell = row?.getCell(cellAddress.column) ?: row.createCell(cellAddress.column)
cell.cellStyle = styleA6ToJ6
}
// 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) {
@@ -642,8 +726,37 @@ open class ReportService {
for (colIndex in 0..9) {
sheet.setColumnWidth(colIndex, (maxContentWidths[colIndex] + 2) * 256) // Set the width for each column
}
return workbook
}

open fun getFinancialStatus(projectId: Long?): List<Map<String, Any>> {
val sql = StringBuilder(
"with cte_invoice as (select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount"
+ "from invoice i"
+ "left join project p on p.code = i.projectCode"
+ "group by p.code"
+ ")"
+ " Select p.code, p.description, c.name, t2.name, p.planStart , p.planEnd , p.expectedTotalFee ,"
+ " s.name , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate,"
+ " cte_i.sumIssuedAmount, cte_i.sumPaidAmount"
+ " from timesheet t"
+ " left join project_task pt on pt.id = t.projectTaskId"
+ " left join project p ON p.id = pt.project_id"
+ " left join staff s on s.id = t.staffId"
+ " left join salary s2 on s.salaryId = s2.salaryPoint"
+ " left join customer c on c.id = p.customerId"
+ " left join team t2 on t2.id = s.teamId"
+ " left join cte_invoice cte_i on cte_i.code = p.code"
)

if (projectId!! > 0) {
sql.append(" where p.id = :projectId ")
}
sql.append(" order by p.code")
val args = mapOf("projectId" to projectId)

return jdbcDao.queryForList(sql.toString(), args)
}

}

+ 2
- 1
src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt Vedi File

@@ -44,7 +44,8 @@ class ReportController(
@Throws(ServletRequestBindingException::class, IOException::class)
fun getFinancialStatusReport(@RequestBody @Valid request: FinancialStatusReportRequest): ResponseEntity<Resource> {

val reportResult: ByteArray = excelReportService.genFinancialStatusReport()

val reportResult: ByteArray = excelReportService.genFinancialStatusReport(request.projectId)

return ResponseEntity.ok()
.header("filename", "Financial Status Report - " + LocalDate.now() + ".xlsx")


Caricamento…
Annulla
Salva