Kaynağa Gözat

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

tags/Baseline_30082024_BACKEND_UAT
cyril.tsui 1 yıl önce
ebeveyn
işleme
fa4e9d70ba
9 değiştirilmiş dosya ile 222 ekleme ve 6 silme
  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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle

@@ -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 Dosyayı Görüntüle


BIN
src/main/resources/templates/report/[Dashboard] Financial Summary for project.xlsx Dosyayı Görüntüle


Yükleniyor…
İptal
Kaydet