Selaa lähdekoodia

add export dashboard (finanical status by client) excel , update project & timesheet & authority

tags/Baseline_30082024_BACKEND_UAT
cyril.tsui 1 vuosi sitten
vanhempi
commit
fa4e9d70ba
9 muutettua tiedostoa jossa 222 lisäystä ja 6 poistoa
  1. +171
    -0
      src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt
  2. +13
    -3
      src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt
  3. +15
    -0
      src/main/java/com/ffii/tsms/modules/data/web/models/ExportDashboardExcelRequest.kt
  4. +9
    -3
      src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt
  5. +2
    -0
      src/main/java/com/ffii/tsms/modules/project/web/models/AssignedProject.kt
  6. +3
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt
  7. +9
    -0
      src/main/resources/db/changelog/changes/20240710_01_cyril/01_update_authority.sql
  8. BIN
      src/main/resources/templates/report/[Dashboard] Financial Summary for client.xlsx
  9. BIN
      src/main/resources/templates/report/[Dashboard] Financial Summary for project.xlsx

+ 171
- 0
src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt Näytä tiedosto

@@ -6,11 +6,27 @@ import com.ffii.tsms.modules.data.entity.Customer
import com.ffii.tsms.modules.data.entity.CustomerType import com.ffii.tsms.modules.data.entity.CustomerType
import com.ffii.tsms.modules.data.entity.CustomerRepository import com.ffii.tsms.modules.data.entity.CustomerRepository
import com.ffii.tsms.modules.data.entity.CustomerTypeRepository import com.ffii.tsms.modules.data.entity.CustomerTypeRepository
import com.ffii.tsms.modules.data.web.models.FinancialSummaryByClient
import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse
import com.ffii.tsms.modules.project.entity.Invoice
import com.ffii.tsms.modules.project.entity.Project
import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest
import com.ffii.tsms.modules.timesheet.entity.Timesheet
import org.apache.poi.ss.usermodel.BorderStyle
import org.apache.poi.ss.usermodel.HorizontalAlignment
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.Workbook
import org.apache.poi.ss.util.CellRangeAddress
import org.apache.poi.ss.util.CellUtil
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.beans.BeanUtils import org.springframework.beans.BeanUtils
import org.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.math.BigDecimal import java.math.BigDecimal
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Optional import java.util.Optional


@Service @Service
@@ -22,6 +38,11 @@ open class DashboardService(
private val staffsService: StaffsService, private val staffsService: StaffsService,
private val jdbcDao: JdbcDao private val jdbcDao: JdbcDao
) { ) {
private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd")
private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER)

private val FINANCIAL_SUMMARY_FOR_CLIENT = "templates/report/[Dashboard] Financial Summary for client.xlsx"
private val FINANCIAL_SUMMARY_FOR_PROJET = "templates/report/[Dashboard] Financial Summary for project.xlsx"


fun CustomerSubsidiary(args: Map<String, Any>): List<Map<String, Any>> { fun CustomerSubsidiary(args: Map<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder("select" val sql = StringBuilder("select"
@@ -2105,6 +2126,156 @@ open class DashboardService(
"no_authority" "no_authority"
} }
} }

@Throws(IOException::class)
fun exportFinancialSummaryByClientExcel(
financialSummaryByClients: List<FinancialSummaryByClient>,
): ByteArray {
// Generate the Excel report with query results
val workbook: Workbook =
createFinancialSummaryByClientExcel(financialSummaryByClients, FINANCIAL_SUMMARY_FOR_CLIENT)

// Write the workbook to a ByteArrayOutputStream
val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
workbook.write(outputStream)
workbook.close()

return outputStream.toByteArray()
}

@Throws(IOException::class)
private fun createFinancialSummaryByClientExcel(
financialSummaryByClients: List<FinancialSummaryByClient>,
templatePath: String,
): Workbook {
// please create a new function for each report template
val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream
val workbook: Workbook = XSSFWorkbook(templateInputStream)

val sheet: Sheet = workbook.getSheetAt(0)

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

// normal font
val normalFont = workbook.createFont().apply {
fontName = "Times New Roman"
}

val normalFontStyle = workbook.createCellStyle().apply {
setFont(normalFont)
}

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

rowIndex = 4
financialSummaryByClients.forEach { financialSummaryByClient: FinancialSummaryByClient ->
sheet.createRow(rowIndex++).apply {
createCell(0).apply {
setCellValue(financialSummaryByClient.customerCode)
cellStyle = normalFontStyle
CellUtil.setAlignment(this, HorizontalAlignment.LEFT)
}

createCell(1).apply {
setCellValue(financialSummaryByClient.customerName)
cellStyle = normalFontStyle
CellUtil.setAlignment(this, HorizontalAlignment.LEFT)
}

createCell(2).apply {
setCellValue(financialSummaryByClient.projectNo)
cellStyle = normalFontStyle.apply {
dataFormat = accountingStyle
}
CellUtil.setAlignment(this, HorizontalAlignment.LEFT)
}

createCell(3).apply {
cellFormula = "IF(E${rowIndex}>=1,\"Positive\",\"Negative\")"
cellStyle = normalFontStyle
CellUtil.setAlignment(this, HorizontalAlignment.CENTER)
}

createCell(4).apply {
cellFormula = "IFERROR(IF(K${rowIndex}=1, 0, K${rowIndex}/J${rowIndex}),0)"
cellStyle = normalFontStyle.apply {
dataFormat = accountingStyle
}
CellUtil.setAlignment(this, HorizontalAlignment.RIGHT)
}

createCell(5).apply {
cellFormula = "IF(G${rowIndex}>=1,\"Positive\",\"Negative\")"
cellStyle = normalFontStyle
CellUtil.setAlignment(this, HorizontalAlignment.CENTER)
}

createCell(6).apply {
cellFormula = "IFERROR(H${rowIndex}/J${rowIndex},0)"
cellStyle = normalFontStyle.apply {
dataFormat = accountingStyle
}
CellUtil.setAlignment(this, HorizontalAlignment.RIGHT)
}

createCell(7).apply {
setCellValue(financialSummaryByClient.totalFee)
cellStyle = normalFontStyle.apply {
dataFormat = accountingStyle
}
CellUtil.setAlignment(this, HorizontalAlignment.RIGHT)
}

createCell(8).apply {
cellFormula = "H${rowIndex}*80%"
cellStyle = normalFontStyle.apply {
dataFormat = accountingStyle
}
CellUtil.setAlignment(this, HorizontalAlignment.RIGHT)
}

createCell(9).apply {
setCellValue(financialSummaryByClient.cumulativeExpenditure)
cellStyle = normalFontStyle.apply {
dataFormat = accountingStyle
}
CellUtil.setAlignment(this, HorizontalAlignment.RIGHT)
}

createCell(10).apply {
setCellValue(financialSummaryByClient.totalInvoiced)
cellStyle = normalFontStyle.apply {
dataFormat = accountingStyle
}
CellUtil.setAlignment(this, HorizontalAlignment.RIGHT)
}

createCell(11).apply {
cellFormula = "IF(H${rowIndex}-K${rowIndex}<0,0,H${rowIndex}-K${rowIndex})"
cellStyle = normalFontStyle.apply {
dataFormat = accountingStyle
}
CellUtil.setAlignment(this, HorizontalAlignment.RIGHT)
}

createCell(12).apply {
setCellValue(financialSummaryByClient.totalReceived)
cellStyle = normalFontStyle.apply {
dataFormat = accountingStyle
}
CellUtil.setAlignment(this, HorizontalAlignment.RIGHT)
}
}
}

return workbook
}
} }





+ 13
- 3
src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt Näytä tiedosto

@@ -2,9 +2,6 @@ package com.ffii.tsms.modules.data.web


import com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.data.entity.Customer
import com.ffii.tsms.modules.data.entity.CustomerType import com.ffii.tsms.modules.data.entity.CustomerType
import com.ffii.tsms.modules.data.web.models.CustomerResponse
import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse
import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@@ -19,6 +16,11 @@ import org.springframework.web.bind.annotation.ResponseStatus
import com.ffii.core.response.RecordsRes import com.ffii.core.response.RecordsRes
import com.ffii.core.utils.CriteriaArgsBuilder import com.ffii.core.utils.CriteriaArgsBuilder
import com.ffii.tsms.modules.data.service.* import com.ffii.tsms.modules.data.service.*
import com.ffii.tsms.modules.data.web.models.ExportFinancialSummaryByClientExcelRequest
import org.springframework.core.io.ByteArrayResource
import org.springframework.core.io.Resource
import org.springframework.http.ResponseEntity
import java.time.LocalDate


@RestController @RestController
@RequestMapping("/dashboard") @RequestMapping("/dashboard")
@@ -440,4 +442,12 @@ class DashboardController(
val args = mutableMapOf<String, Any>() val args = mutableMapOf<String, Any>()
return dashboardService.staffCombo(args) return dashboardService.staffCombo(args)
} }

@PostMapping("/exportFinancialSummaryByClientExcel")
fun exportFinancialSummaryByClientExcel(@Valid @RequestBody request: ExportFinancialSummaryByClientExcelRequest): ResponseEntity<Resource> {
val reportResult: ByteArray = dashboardService.exportFinancialSummaryByClientExcel(request.financialSummaryByClients)
return ResponseEntity.ok()
.header("filename", "Financial Summary for Client - " + LocalDate.now() + ".xlsx")
.body(ByteArrayResource(reportResult))
}
} }

+ 15
- 0
src/main/java/com/ffii/tsms/modules/data/web/models/ExportDashboardExcelRequest.kt Näytä tiedosto

@@ -0,0 +1,15 @@
package com.ffii.tsms.modules.data.web.models

data class FinancialSummaryByClient (
val customerCode: String,
val customerName: String,
val projectNo: Double,
val totalFee: Double,
val cumulativeExpenditure: Double,
val totalInvoiced: Double,
val totalReceived: Double,
)

data class ExportFinancialSummaryByClientExcelRequest (
val financialSummaryByClients: List<FinancialSummaryByClient>
)

+ 9
- 3
src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt Näytä tiedosto

@@ -58,6 +58,7 @@ open class ProjectsService(
private val customerRepository: CustomerRepository, private val customerRepository: CustomerRepository,
private val subsidiaryRepository: SubsidiaryRepository, private val subsidiaryRepository: SubsidiaryRepository,
private val customerSubsidiaryService: CustomerSubsidiaryService, private val customerSubsidiaryService: CustomerSubsidiaryService,
private val staffsService: StaffsService,
) { ) {
open fun allProjects(): List<ProjectSearchInfo> { open fun allProjects(): List<ProjectSearchInfo> {
return projectRepository.findProjectSearchInfoByDeletedIsFalseOrderByCodeDesc() return projectRepository.findProjectSearchInfoByDeletedIsFalseOrderByCodeDesc()
@@ -83,12 +84,15 @@ open class ProjectsService(
} }


open fun allAssignedProjects(): List<AssignedProject> { open fun allAssignedProjects(): List<AssignedProject> {
val currentStaff = staffsService.currentStaff()
return SecurityUtils.getUser().getOrNull()?.let { user -> return SecurityUtils.getUser().getOrNull()?.let { user ->
staffRepository.findByUserId(user.id).getOrNull()?.let { staff -> staffRepository.findByUserId(user.id).getOrNull()?.let { staff ->
staffAllocationRepository.findOnGoingAssignedProjectsByStaff(staff).map { project -> staffAllocationRepository.findOnGoingAssignedProjectsByStaff(staff).map { project ->
val timesheetHours = timesheetRepository.totalHoursConsumedByProject(project) val timesheetHours = timesheetRepository.totalHoursConsumedByProject(project)
val staffTimesheetHours = timesheetRepository.totalHoursConsumedByProjectAndStaff(project, currentStaff!!)


AssignedProject(id = project.id!!,
AssignedProject(
id = project.id!!,
code = project.code!!, code = project.code!!,
name = project.name!!, name = project.name!!,
tasks = projectTaskRepository.findAllByProject(project).mapNotNull { pt -> pt.task }, tasks = projectTaskRepository.findAllByProject(project).mapNotNull { pt -> pt.task },
@@ -104,7 +108,9 @@ open class ProjectsService(
hoursAllocated = project.totalManhour ?: 0.0, hoursAllocated = project.totalManhour ?: 0.0,
hoursAllocatedOther = 0.0, hoursAllocatedOther = 0.0,
hoursSpent = timesheetHours.normalConsumed, hoursSpent = timesheetHours.normalConsumed,
hoursSpentOther = timesheetHours.otConsumed
hoursSpentOther = timesheetHours.otConsumed,
currentStaffHoursSpent = staffTimesheetHours.normalConsumed,
currentStaffHoursSpentOther = staffTimesheetHours.otConsumed
) )
} }
} }
@@ -213,7 +219,7 @@ open class ProjectsService(
project.apply { project.apply {
name = request.projectName name = request.projectName
description = request.projectDescription description = request.projectDescription
code = if (request.mainProjectId != null && mainProject != null) createSubProjectCode(
code = if (request.mainProjectId != null && mainProject != null && request.projectCode == null) createSubProjectCode(
mainProject, mainProject,
project project
) else request.projectCode ) else request.projectCode


+ 2
- 0
src/main/java/com/ffii/tsms/modules/project/web/models/AssignedProject.kt Näytä tiedosto

@@ -14,6 +14,8 @@ data class AssignedProject(
val hoursAllocatedOther: Double, val hoursAllocatedOther: Double,
val hoursSpent: Double, val hoursSpent: Double,
val hoursSpentOther: Double, val hoursSpentOther: Double,
val currentStaffHoursSpent: Double,
val currentStaffHoursSpentOther: Double,
) )


data class MilestoneInfo( data class MilestoneInfo(


+ 3
- 0
src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt Näytä tiedosto

@@ -21,6 +21,9 @@ interface TimesheetRepository : AbstractRepository<Timesheet, Long> {
@Query("SELECT new com.ffii.tsms.modules.timesheet.entity.projections.TimesheetHours(IFNULL(SUM(normalConsumed), 0), IFNULL(SUM(otConsumed), 0)) FROM Timesheet t JOIN ProjectTask pt on t.projectTask = pt WHERE pt.project = ?1") @Query("SELECT new com.ffii.tsms.modules.timesheet.entity.projections.TimesheetHours(IFNULL(SUM(normalConsumed), 0), IFNULL(SUM(otConsumed), 0)) FROM Timesheet t JOIN ProjectTask pt on t.projectTask = pt WHERE pt.project = ?1")
fun totalHoursConsumedByProject(project: Project): TimesheetHours fun totalHoursConsumedByProject(project: Project): TimesheetHours


@Query("SELECT new com.ffii.tsms.modules.timesheet.entity.projections.TimesheetHours(IFNULL(SUM(normalConsumed), 0), IFNULL(SUM(otConsumed), 0)) FROM Timesheet t JOIN ProjectTask pt on t.projectTask = pt WHERE pt.project = ?1 and t.staff = ?2")
fun totalHoursConsumedByProjectAndStaff(project: Project, staff: Staff): TimesheetHours

fun findByStaffAndRecordDateBetweenOrderByRecordDate(staff: Staff, start: LocalDate, end: LocalDate): List<Timesheet> fun findByStaffAndRecordDateBetweenOrderByRecordDate(staff: Staff, start: LocalDate, end: LocalDate): List<Timesheet>


fun findDistinctProjectTaskByStaffAndRecordDateBetweenOrderByRecordDate(staff: Staff, start: LocalDate, end: LocalDate): List<Timesheet> fun findDistinctProjectTaskByStaffAndRecordDateBetweenOrderByRecordDate(staff: Staff, start: LocalDate, end: LocalDate): List<Timesheet>

+ 9
- 0
src/main/resources/db/changelog/changes/20240710_01_cyril/01_update_authority.sql Näytä tiedosto

@@ -0,0 +1,9 @@
-- liquibase formatted sql
-- changeset cyril:authority, user_authority

INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_NORMAL_STAFF_WORKSPACE','Maintain Normal Staff Workspace');
INSERT INTO authority (authority,name)
VALUES ('MAINTAIN_MANAGEMENT_STAFF_WORKSPACE','Maintain Management Staff Workspace');

INSERT INTO `user_authority` VALUES (1,43);

BIN
src/main/resources/templates/report/[Dashboard] Financial Summary for client.xlsx Näytä tiedosto


BIN
src/main/resources/templates/report/[Dashboard] Financial Summary for project.xlsx Näytä tiedosto


Ladataan…
Peruuta
Tallenna