Procházet zdrojové kódy

Update PnL Report to show Salary Effective info

tags/Baseline_30082024_BACKEND_UAT
MSI\2Fi před 1 rokem
rodič
revize
29ebadc835
4 změnil soubory, kde provedl 187 přidání a 34 odebrání
  1. +3
    -0
      src/main/java/com/ffii/tsms/modules/data/entity/projections/SalaryEffectiveInfo.kt
  2. +26
    -1
      src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt
  3. +158
    -33
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  4. binární
      src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx

+ 3
- 0
src/main/java/com/ffii/tsms/modules/data/entity/projections/SalaryEffectiveInfo.kt Zobrazit soubor

@@ -5,11 +5,14 @@ import org.springframework.beans.factory.annotation.Value
import java.time.LocalDate

interface SalaryEffectiveInfo {

val id: Long?
val date: LocalDate
val salary: Salary
@get:Value("#{target.staff?.name}")
val name: String?
@get:Value("#{target.staff?.id}")
val idInStaff: Long
@get:Value("#{target.staff?.staffId}")
val staffId: String?
@get:Value("#{target.staff?.grade?.name}")


+ 26
- 1
src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt Zobrazit soubor

@@ -2,11 +2,13 @@ package com.ffii.tsms.modules.data.service

import com.ffii.core.support.AbstractIdEntityService
import com.ffii.core.support.JdbcDao
import com.ffii.tsms.modules.data.entity.Salary
import com.ffii.tsms.modules.data.entity.SalaryEffective
import com.ffii.tsms.modules.data.entity.SalaryEffectiveRepository
import com.ffii.tsms.modules.data.entity.SalaryRepository
import com.ffii.tsms.modules.data.entity.StaffRepository
import org.springframework.stereotype.Service
import java.math.BigDecimal
import java.time.LocalDate

@Service
@@ -14,7 +16,7 @@ open class SalaryEffectiveService(
private val salaryEffectiveRepository: SalaryEffectiveRepository,
private val staffRepository: StaffRepository,
private val salaryRepository: SalaryRepository,
private val jdbcDao: JdbcDao,
jdbcDao: JdbcDao,
) : AbstractIdEntityService<SalaryEffective, Long, SalaryEffectiveRepository>(jdbcDao, salaryEffectiveRepository) {

// open fun combo(args: Map<String, Any>): List<Map<String, Any>> {
@@ -63,4 +65,27 @@ open class SalaryEffectiveService(

return salaryEffective
}

data class SalaryData(val idInStaff: Long, val staffId: String, val financialYear: LocalDate, val hourlyRate: BigDecimal, val salaryPoint: Int)
data class StaffSalaryData(val staffId: String, val salaryData: List<SalaryData>)

open fun getStaffSalaryDataByProjectId(projectId: Long): List<StaffSalaryData> {

val sql = StringBuilder(
" select distinct t.staffId from timesheet t where t.projectId = :projectId "
)
val staffIdList = jdbcDao.queryForList(sql.toString(), mapOf("projectId" to projectId))

val temp = staffIdList.mapNotNull{
it["staffId"].toString().toLong()
}

val salaryEffectiveLists = salaryEffectiveRepository.findSalaryEffectiveInfoByStaffIdInOrderByStaffId(temp)
.map { SalaryData(it.idInStaff, it.staffId!!, it.date, it.salary.hourlyRate, it.salary.salaryPoint) }
.groupBy { it.staffId }
.map { (staffId, salaryData) -> StaffSalaryData(staffId, salaryData.sortedBy { it.financialYear }) }

// println(salaryEffectiveLists)
return salaryEffectiveLists
}
}

+ 158
- 33
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt Zobrazit soubor

@@ -5,8 +5,11 @@ import com.ffii.core.utils.ExcelUtils
import com.ffii.tsms.modules.data.entity.Customer
import com.ffii.tsms.modules.data.entity.Grade
import com.ffii.tsms.modules.data.entity.Salary
import com.ffii.tsms.modules.data.entity.SalaryEffective
import com.ffii.tsms.modules.data.entity.SalaryEffectiveRepository
import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.data.entity.Team
import com.ffii.tsms.modules.data.service.SalaryEffectiveService
import com.ffii.tsms.modules.project.entity.Invoice
import com.ffii.tsms.modules.project.entity.Milestone
import com.ffii.tsms.modules.project.entity.Project
@@ -37,6 +40,7 @@ import java.time.ZoneId
import java.time.format.DateTimeParseException
import java.time.temporal.ChronoUnit
import java.util.*
import kotlin.collections.ArrayList


data class DayInfo(val date: String?, val weekday: String?)
@@ -45,7 +49,8 @@ data class DayInfo(val date: String?, val weekday: String?)
@Service
open class ReportService(
private val jdbcDao: JdbcDao,
private val projectRepository: ProjectRepository
private val projectRepository: ProjectRepository,
private val salaryEffectiveService: SalaryEffectiveService
) {
private val logger: Log = LogFactory.getLog(javaClass)
private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd")
@@ -2121,6 +2126,37 @@ open class ReportService(
return jdbcDao.queryForList(sql.toString(), args)
}

fun getSalaryForMonth(recordDate: String, staffId: String, staffSalaryLists: List<SalaryEffectiveService.StaffSalaryData>): Double? {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM")
val monthDate = YearMonth.parse(recordDate, formatter)

var hourlyRate: Double? = null
// var financialYear = monthDate

val staffSalaryData = staffSalaryLists.find { it.staffId == staffId }
staffSalaryData?.salaryData?.forEach { salaryData ->
val financialYearStart = YearMonth.of(salaryData.financialYear.year, 10)
val financialYearEnd = YearMonth.of(salaryData.financialYear.year + 1, 9)

if ((monthDate.isAfter(financialYearStart) || monthDate.equals(financialYearStart)) && (monthDate.isBefore(financialYearEnd) || monthDate.equals(financialYearEnd))) {
// if(staffId == "B627"){
// println("----------------- Start -------------------------")
// println("financialYearStart: ${financialYearStart}")
// println("financialYearEnd: ${financialYearEnd}")
// println("monthDate: ${monthDate}")
// println("Result: ${(monthDate.isAfter(financialYearStart) || monthDate.equals(financialYearStart)) && monthDate.isBefore(financialYearEnd)}")
// println("hourlyRate: ${salaryData.hourlyRate.toDouble()}")
// println("---------------------- End -----------------------------")
// }
hourlyRate = salaryData.hourlyRate.toDouble()
// financialYear = YearMonth.parse(salaryData.financialYear.toString())
return@forEach
}
}

return hourlyRate
}

open fun getManhoursSpent(projectId: Long, startMonth: String, endMonth: String): List<Map<String, Any>> {
val sql = StringBuilder(
" with cte_timesheet as ("
@@ -2189,16 +2225,14 @@ open class ReportService(
"endMonth" to queryEndMonth.toString(),
)

// All recorded man hours of the project
val manHoursSpent = jdbcDao.queryForList(sql.toString(), args)


val projectCodeSql = StringBuilder(
"select p.code, p.description from project p where p.deleted = false and p.id = :projectId"
)

val args2 = mapOf(
"projectId" to projectId,
)

val projectsCode = jdbcDao.queryForMap(projectCodeSql.toString(), args).get()

val otFactor = BigDecimal(1).toDouble()
@@ -2212,16 +2246,11 @@ open class ReportService(
"code" to projectsCode["code"],
"description" to projectsCode["description"]
)

val financialYears = getFinancialYearDates(queryStartMonth, queryEndMonth, 10)

val staffInfoList = mutableListOf<Map<String, Any>>()
// println("manHoursSpent------- ${manHoursSpent}")

for (financialYear in financialYears) {
// println("${financialYear.start.year}-${financialYear.start.monthValue} - ${financialYear.end.year}-${financialYear.end.monthValue}")
println("financialYear--------- ${financialYear.start} - ${financialYear.end}")
}
val staffSalaryLists = salaryEffectiveService.getStaffSalaryDataByProjectId(projectId)

for (item in manHoursSpent) {
if (info["teamLead"] != item.getValue("teamLead")) {
@@ -2248,8 +2277,18 @@ open class ReportService(
if (info["description"] != item.getValue("description")) {
info["description"] = item.getValue("description")
}
val hourlyRate = (item.getValue("hourlyRate") as BigDecimal).toDouble()
val recordDate = item.getValue("recordDate") as String

// val stadd = item.getValue("staffId") as String
// println("StaffId: ${stadd}")
// println(
// "HourlyRate: " +
// "${
// getSalaryForMonth(
// item.getValue("recordDate") as String,
// item.getValue("staffId") as String,
// staffSalaryLists
// )}")
val hourlyRate = getSalaryForMonth(item.getValue("recordDate") as String, item.getValue("staffId") as String, staffSalaryLists) ?: (item.getValue("hourlyRate") as BigDecimal).toDouble()

if (!staffInfoList.any { it["staffId"] == item["staffId"] && it["name"] == item["name"] }) {

@@ -2312,14 +2351,47 @@ open class ReportService(
staffInfoList[updatedIndex] = updatedMap
}
}
// println("staffInfoList----------------- $staffInfoList")
// println("staffInfoList----------------- ${staffInfoList.filter { it["staffId"] == "B627" }}")
// println("info----------------- $info")

staffInfoList.forEachIndexed { index, staff ->
val staffId = staff["staffId"]
val staffSalaryData = staffSalaryLists.find { it.staffId == staffId }
// println("----------------------- $staffId ---------------------------")
// println(staff)

val financialYears = (staff["financialYears"] as List<FinancialYear>).map { year ->
val relevantSalaryData = staffSalaryData?.salaryData?.lastOrNull {
year.isYearMonthInFinancialYear(YearMonth.from(it.financialYear))
}
// println("it.financialYear: ${relevantSalaryData?.financialYear}")
// println("year: ${year.start}- ${year.end}")
// println("it.salaryPoint: ${relevantSalaryData?.salaryPoint}")
// println("it.staffId: ${relevantSalaryData?.staffId}")

// Create a new FinancialYear object with the updated salaryPoint
year.copy(
start = year.start,
end = year.end,
salaryPoint = relevantSalaryData?.salaryPoint ?: staff["salaryPoint"] as Int,
hourlyRate = relevantSalaryData?.hourlyRate?.toDouble() ?: staff["hourlyRate"] as Double
)
}

// Create a new map with the updated financialYears
val updatedStaff = staff.toMutableMap().apply {
this["financialYears"] = financialYears
}

// Update the staffInfoList with the new staff map
staffInfoList[index] = updatedStaff
}

tempList.add(mapOf("info" to info))
tempList.add(mapOf("staffInfoList" to staffInfoList))

// println("Only Staff Info List --------------------- ${tempList.first() { it.containsKey("staffInfoList") }["staffInfoList"]}")
println("tempList----------------- $tempList")
// println("tempList----------------- ${tempList.filter { it["staffId"] == "B627" }}")

return tempList
}
@@ -2340,13 +2412,21 @@ open class ReportService(
}

// For Calculating the Financial Year
data class FinancialYear(val start: YearMonth, val end: YearMonth, val hourlyRate: Double)
data class FinancialYear(val start: YearMonth, val end: YearMonth, var hourlyRate: Double, var salaryPoint: Int){
fun isYearMonthInFinancialYear(salaryEffectiveMonth: YearMonth) :Boolean{
if ((salaryEffectiveMonth.isAfter(start) || salaryEffectiveMonth.equals(start)) &&
(salaryEffectiveMonth.isBefore(end) || salaryEffectiveMonth.equals(end))){
return true
}
return false
}
}

fun getFinancialYearDates(
startDate: YearMonth,
endDate: YearMonth,
financialYearStartMonth: Int
): Array<FinancialYear> {
): List<FinancialYear> {
val financialYearDates = mutableListOf<FinancialYear>()

var currentYear = startDate.year
@@ -2375,7 +2455,8 @@ open class ReportService(
val financialYear = FinancialYear(
start = YearMonth.of(startYear, startMonth),
end = YearMonth.of(endYear, endMonth),
hourlyRate = 0.0
hourlyRate = 0.0,
salaryPoint = 0
)
financialYearDates.add(financialYear)

@@ -2386,9 +2467,10 @@ open class ReportService(
}
}

return financialYearDates.toTypedArray()
return financialYearDates
}


fun createPandLReportWorkbook(
templatePath: String,
manhoursSpent: List<Map<String, Any>>,
@@ -2422,6 +2504,7 @@ open class ReportService(
val convertEndMonth = YearMonth.of(endDate.year, endDate.month)

val monthRange: MutableList<Map<String, Any>> = ArrayList()
val financialYears = getFinancialYearDates(queryStartMonth, queryEndMonth, 10)

var currentDate = startDate
if (currentDate == endDate) {
@@ -2520,12 +2603,35 @@ open class ReportService(
}
row6Cell.setCellValue(clientSubsidiary)

// Average Hourly Rate by Pay Scale Point
rowNum = 8
val row8: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 2, (financialYears.size+1)*2-1))
val row8Cell = row8.getCell(2) ?: row8.createCell(2)
row8Cell.apply {
setCellValue("Average Hourly Rate by Pay Scale Point")
}
CellUtil.setAlignment(row8Cell, HorizontalAlignment.CENTER);
CellUtil.setVerticalAlignment(row8Cell, VerticalAlignment.CENTER);
CellUtil.setCellStyleProperty(row8Cell, CellUtil.WRAP_TEXT, true)


// Need to be updated to financial year
// Base on the searching criteria
rowNum = 9
val row9: Row = sheet.getRow(rowNum)
val row9Cell = row9.getCell(3)
row9Cell.setCellValue("${convertStartMonth.format(monthFormat)} - ${convertEndMonth.format(monthFormat)}")
val row9: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
var column = 2
financialYears.indices.forEach { i ->
val row9Cell = row9.getCell(column) ?: row9.createCell(column)
val row9Cell2 = row9.getCell(column+1) ?: row9.createCell(column+1)
sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, column, column + 1))
row9Cell.setCellValue("${financialYears[i].start.format(monthFormat)} - ${financialYears[i].end.format(monthFormat)}")
CellUtil.setCellStyleProperty(row9Cell, "borderBottom", BorderStyle.THIN)
CellUtil.setCellStyleProperty(row9Cell2, "borderBottom", BorderStyle.THIN)
column = column.plus(2)
}



rowNum = 10
for (staff in staffInfoList) {
@@ -2542,19 +2648,37 @@ open class ReportService(
}
CellUtil.setAlignment(gradeCell, HorizontalAlignment.CENTER);

val salaryPointCell = row.getCell(2) ?: row.createCell(2)
salaryPointCell.apply {
setCellValue((staff.getValue("salaryPoint") as Long).toDouble())
}
CellUtil.setAlignment(salaryPointCell, HorizontalAlignment.CENTER);
// Get the array of FinancialYear instances
val financialYears = staff["financialYears"] as List<FinancialYear>


sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 3, 4))
val hourlyRateCell = row.getCell(3) ?: row.createCell(3)
hourlyRateCell.apply {
setCellValue((staff.getValue("hourlyRate") as Double))
// Access individual FinancialYear instances
financialYears.let { years ->
var salaryColumn = 2
for (year in years) {
val salaryPointCell = row.getCell(salaryColumn) ?: row.createCell(salaryColumn)
sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, salaryColumn, salaryColumn+1))
salaryPointCell.apply {
setCellValue("${year.salaryPoint} (${year.hourlyRate})")
}
CellUtil.setAlignment(salaryPointCell, HorizontalAlignment.CENTER);
salaryColumn = salaryColumn.plus(2)
}
}
CellUtil.setAlignment(hourlyRateCell, HorizontalAlignment.CENTER);
CellUtil.setVerticalAlignment(hourlyRateCell, VerticalAlignment.CENTER);

// val salaryPointCell = row.getCell(2) ?: row.createCell(2)
// salaryPointCell.apply {
// setCellValue((staff.getValue("salaryPoint") as Long).toDouble())
// }
// CellUtil.setAlignment(salaryPointCell, HorizontalAlignment.CENTER);
//
// sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 3, 4))
// val hourlyRateCell = row.getCell(3) ?: row.createCell(3)
// hourlyRateCell.apply {
// setCellValue((staff.getValue("hourlyRate") as Double))
// }
// CellUtil.setAlignment(hourlyRateCell, HorizontalAlignment.CENTER);
// CellUtil.setVerticalAlignment(hourlyRateCell, VerticalAlignment.CENTER);

sheet.setRowBreak(rowNum++);
}
@@ -2608,6 +2732,7 @@ open class ReportService(
CellUtil.setVerticalAlignment(manhourCell, VerticalAlignment.CENTER);
CellUtil.setCellStyleProperty(manhourCell, CellUtil.WRAP_TEXT, true)
CellUtil.setCellStyleProperty(manhourCell, "borderBottom", BorderStyle.THIN)
sheet.setColumnWidth(2 + monthColumnEnd, 15 * 256)

val manhourECell = row.getCell(2 + monthColumnEnd + 1) ?: row.createCell(2 + monthColumnEnd + 1)
manhourECell.apply {


binární
src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx Zobrazit soubor


Načítá se…
Zrušit
Uložit