From fa4e9d70bac6f4c0e7558f638a7f6ae0b4eb6285 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Wed, 10 Jul 2024 17:23:12 +0800 Subject: [PATCH] add export dashboard (finanical status by client) excel , update project & timesheet & authority --- .../modules/data/service/DashboardService.kt | 171 ++++++++++++++++++ .../modules/data/web/DashboardController.kt | 16 +- .../web/models/ExportDashboardExcelRequest.kt | 15 ++ .../project/service/ProjectsService.kt | 12 +- .../project/web/models/AssignedProject.kt | 2 + .../timesheet/entity/TimesheetRepository.kt | 3 + .../20240710_01_cyril/01_update_authority.sql | 9 + ...shboard] Financial Summary for client.xlsx | Bin 0 -> 13316 bytes ...hboard] Financial Summary for project.xlsx | Bin 0 -> 13436 bytes 9 files changed, 222 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/ffii/tsms/modules/data/web/models/ExportDashboardExcelRequest.kt create mode 100644 src/main/resources/db/changelog/changes/20240710_01_cyril/01_update_authority.sql create mode 100644 src/main/resources/templates/report/[Dashboard] Financial Summary for client.xlsx create mode 100644 src/main/resources/templates/report/[Dashboard] Financial Summary for project.xlsx diff --git a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt index e9c8b73..388d397 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt @@ -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.CustomerRepository 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.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.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.core.io.ClassPathResource import org.springframework.stereotype.Service +import java.io.ByteArrayOutputStream +import java.io.IOException import java.math.BigDecimal +import java.time.LocalDate +import java.time.format.DateTimeFormatter import java.util.Optional @Service @@ -22,6 +38,11 @@ open class DashboardService( private val staffsService: StaffsService, 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): List> { val sql = StringBuilder("select" @@ -2105,6 +2126,156 @@ open class DashboardService( "no_authority" } } + + @Throws(IOException::class) + fun exportFinancialSummaryByClientExcel( + financialSummaryByClients: List, + ): 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, + 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 + } } diff --git a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt index 679a734..615af9f 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt @@ -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.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.RequestMapping 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.utils.CriteriaArgsBuilder 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 @RequestMapping("/dashboard") @@ -440,4 +442,12 @@ class DashboardController( val args = mutableMapOf() return dashboardService.staffCombo(args) } + + @PostMapping("/exportFinancialSummaryByClientExcel") + fun exportFinancialSummaryByClientExcel(@Valid @RequestBody request: ExportFinancialSummaryByClientExcelRequest): ResponseEntity { + val reportResult: ByteArray = dashboardService.exportFinancialSummaryByClientExcel(request.financialSummaryByClients) + return ResponseEntity.ok() + .header("filename", "Financial Summary for Client - " + LocalDate.now() + ".xlsx") + .body(ByteArrayResource(reportResult)) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/data/web/models/ExportDashboardExcelRequest.kt b/src/main/java/com/ffii/tsms/modules/data/web/models/ExportDashboardExcelRequest.kt new file mode 100644 index 0000000..54b71b6 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/data/web/models/ExportDashboardExcelRequest.kt @@ -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 +) \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt b/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt index d043718..36ee1e3 100644 --- a/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt +++ b/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt @@ -58,6 +58,7 @@ open class ProjectsService( private val customerRepository: CustomerRepository, private val subsidiaryRepository: SubsidiaryRepository, private val customerSubsidiaryService: CustomerSubsidiaryService, + private val staffsService: StaffsService, ) { open fun allProjects(): List { return projectRepository.findProjectSearchInfoByDeletedIsFalseOrderByCodeDesc() @@ -83,12 +84,15 @@ open class ProjectsService( } open fun allAssignedProjects(): List { + val currentStaff = staffsService.currentStaff() return SecurityUtils.getUser().getOrNull()?.let { user -> staffRepository.findByUserId(user.id).getOrNull()?.let { staff -> staffAllocationRepository.findOnGoingAssignedProjectsByStaff(staff).map { project -> val timesheetHours = timesheetRepository.totalHoursConsumedByProject(project) + val staffTimesheetHours = timesheetRepository.totalHoursConsumedByProjectAndStaff(project, currentStaff!!) - AssignedProject(id = project.id!!, + AssignedProject( + id = project.id!!, code = project.code!!, name = project.name!!, tasks = projectTaskRepository.findAllByProject(project).mapNotNull { pt -> pt.task }, @@ -104,7 +108,9 @@ open class ProjectsService( hoursAllocated = project.totalManhour ?: 0.0, hoursAllocatedOther = 0.0, hoursSpent = timesheetHours.normalConsumed, - hoursSpentOther = timesheetHours.otConsumed + hoursSpentOther = timesheetHours.otConsumed, + currentStaffHoursSpent = staffTimesheetHours.normalConsumed, + currentStaffHoursSpentOther = staffTimesheetHours.otConsumed ) } } @@ -213,7 +219,7 @@ open class ProjectsService( project.apply { name = request.projectName description = request.projectDescription - code = if (request.mainProjectId != null && mainProject != null) createSubProjectCode( + code = if (request.mainProjectId != null && mainProject != null && request.projectCode == null) createSubProjectCode( mainProject, project ) else request.projectCode diff --git a/src/main/java/com/ffii/tsms/modules/project/web/models/AssignedProject.kt b/src/main/java/com/ffii/tsms/modules/project/web/models/AssignedProject.kt index 52e33a9..d7e9f7f 100644 --- a/src/main/java/com/ffii/tsms/modules/project/web/models/AssignedProject.kt +++ b/src/main/java/com/ffii/tsms/modules/project/web/models/AssignedProject.kt @@ -14,6 +14,8 @@ data class AssignedProject( val hoursAllocatedOther: Double, val hoursSpent: Double, val hoursSpentOther: Double, + val currentStaffHoursSpent: Double, + val currentStaffHoursSpentOther: Double, ) data class MilestoneInfo( diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt b/src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt index 2826b9f..d1ef25e 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt @@ -21,6 +21,9 @@ interface TimesheetRepository : AbstractRepository { @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 + @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 fun findDistinctProjectTaskByStaffAndRecordDateBetweenOrderByRecordDate(staff: Staff, start: LocalDate, end: LocalDate): List diff --git a/src/main/resources/db/changelog/changes/20240710_01_cyril/01_update_authority.sql b/src/main/resources/db/changelog/changes/20240710_01_cyril/01_update_authority.sql new file mode 100644 index 0000000..1abb68a --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240710_01_cyril/01_update_authority.sql @@ -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); \ No newline at end of file diff --git a/src/main/resources/templates/report/[Dashboard] Financial Summary for client.xlsx b/src/main/resources/templates/report/[Dashboard] Financial Summary for client.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d2674ba87a9b2d7b9defc7472b31d80e9f0c9ce7 GIT binary patch literal 13316 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/templates/report/[Dashboard] Financial Summary for project.xlsx b/src/main/resources/templates/report/[Dashboard] Financial Summary for project.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..bb7e7f97629c7dfa66db29d9b23d459f2a5a3c52 GIT binary patch literal 13436 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 A>;M1& literal 0 HcmV?d00001