Просмотр исходного кода

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

tags/Baseline_30082024_BACKEND_UAT
cyril.tsui 1 год назад
Родитель
Сommit
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 Просмотреть файл


Загрузка…
Отмена
Сохранить