瀏覽代碼

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

tags/Baseline_30082024_BACKEND_UAT
cyril.tsui 1 年之前
父節點
當前提交
fa4e9d70ba
共有 9 個檔案被更改,包括 222 行新增6 行删除
  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. 二進制
      src/main/resources/templates/report/[Dashboard] Financial Summary for client.xlsx
  9. 二進制
      src/main/resources/templates/report/[Dashboard] Financial Summary for project.xlsx

+ 171
- 0
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<String, Any>): List<Map<String, Any>> {
val sql = StringBuilder("select"
@@ -2105,6 +2126,156 @@ open class DashboardService(
"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 查看文件

@@ -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<String, Any>()
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 查看文件

@@ -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 查看文件

@@ -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<ProjectSearchInfo> {
return projectRepository.findProjectSearchInfoByDeletedIsFalseOrderByCodeDesc()
@@ -83,12 +84,15 @@ open class ProjectsService(
}

open fun allAssignedProjects(): List<AssignedProject> {
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


+ 2
- 0
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(


+ 3
- 0
src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt 查看文件

@@ -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")
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 findDistinctProjectTaskByStaffAndRecordDateBetweenOrderByRecordDate(staff: Staff, start: LocalDate, end: LocalDate): List<Timesheet>

+ 9
- 0
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);

二進制
src/main/resources/templates/report/[Dashboard] Financial Summary for client.xlsx 查看文件


二進制
src/main/resources/templates/report/[Dashboard] Financial Summary for project.xlsx 查看文件


Loading…
取消
儲存