@@ -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.Customer
import com.ffii.tsms.modules.data.entity.Grade
import com.ffii.tsms.modules.data.entity.Grade
import com.ffii.tsms.modules.data.entity.Salary
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.Staff
import com.ffii.tsms.modules.data.entity.Team
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.Invoice
import com.ffii.tsms.modules.project.entity.Milestone
import com.ffii.tsms.modules.project.entity.Milestone
import com.ffii.tsms.modules.project.entity.Project
import com.ffii.tsms.modules.project.entity.Project
@@ -37,6 +40,7 @@ import java.time.ZoneId
import java.time.format.DateTimeParseException
import java.time.format.DateTimeParseException
import java.time.temporal.ChronoUnit
import java.time.temporal.ChronoUnit
import java.util.*
import java.util.*
import kotlin.collections.ArrayList
data class DayInfo(val date: String?, val weekday: String?)
data class DayInfo(val date: String?, val weekday: String?)
@@ -45,7 +49,8 @@ data class DayInfo(val date: String?, val weekday: String?)
@Service
@Service
open class ReportService(
open class ReportService(
private val jdbcDao: JdbcDao,
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 logger: Log = LogFactory.getLog(javaClass)
private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd")
private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd")
@@ -2121,6 +2126,37 @@ open class ReportService(
return jdbcDao.queryForList(sql.toString(), args)
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>> {
open fun getManhoursSpent(projectId: Long, startMonth: String, endMonth: String): List<Map<String, Any>> {
val sql = StringBuilder(
val sql = StringBuilder(
" with cte_timesheet as ("
" with cte_timesheet as ("
@@ -2189,16 +2225,14 @@ open class ReportService(
"endMonth" to queryEndMonth.toString(),
"endMonth" to queryEndMonth.toString(),
)
)
// All recorded man hours of the project
val manHoursSpent = jdbcDao.queryForList(sql.toString(), args)
val manHoursSpent = jdbcDao.queryForList(sql.toString(), args)
val projectCodeSql = StringBuilder(
val projectCodeSql = StringBuilder(
"select p.code, p.description from project p where p.deleted = false and p.id = :projectId"
"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 projectsCode = jdbcDao.queryForMap(projectCodeSql.toString(), args).get()
val otFactor = BigDecimal(1).toDouble()
val otFactor = BigDecimal(1).toDouble()
@@ -2212,16 +2246,11 @@ open class ReportService(
"code" to projectsCode["code"],
"code" to projectsCode["code"],
"description" to projectsCode["description"]
"description" to projectsCode["description"]
)
)
val financialYears = getFinancialYearDates(queryStartMonth, queryEndMonth, 10)
val financialYears = getFinancialYearDates(queryStartMonth, queryEndMonth, 10)
val staffInfoList = mutableListOf<Map<String, Any>>()
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) {
for (item in manHoursSpent) {
if (info["teamLead"] != item.getValue("teamLead")) {
if (info["teamLead"] != item.getValue("teamLead")) {
@@ -2248,8 +2277,18 @@ open class ReportService(
if (info["description"] != item.getValue("description")) {
if (info["description"] != item.getValue("description")) {
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"] }) {
if (!staffInfoList.any { it["staffId"] == item["staffId"] && it["name"] == item["name"] }) {
@@ -2312,14 +2351,47 @@ open class ReportService(
staffInfoList[updatedIndex] = updatedMap
staffInfoList[updatedIndex] = updatedMap
}
}
}
}
// println("staffInfoList----------------- $staffInfoList")
// println("staffInfoList----------------- ${ staffInfoList.filter { it["staffId"] == "B627" }} ")
// println("info----------------- $info")
// 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("info" to info))
tempList.add(mapOf("staffInfoList" to staffInfoList))
tempList.add(mapOf("staffInfoList" to staffInfoList))
// println("Only Staff Info List --------------------- ${tempList.first() { it.containsKey("staffInfoList") }["staffInfoList"]}")
// println("Only Staff Info List --------------------- ${tempList.first() { it.containsKey("staffInfoList") }["staffInfoList"]}")
println("tempList----------------- $tempList")
// println("tempList----------------- ${ tempList.filter { it["staffId"] == "B627" }} ")
return tempList
return tempList
}
}
@@ -2340,13 +2412,21 @@ open class ReportService(
}
}
// For Calculating the Financial Year
// 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(
fun getFinancialYearDates(
startDate: YearMonth,
startDate: YearMonth,
endDate: YearMonth,
endDate: YearMonth,
financialYearStartMonth: Int
financialYearStartMonth: Int
): Array<FinancialYear> {
): List <FinancialYear> {
val financialYearDates = mutableListOf<FinancialYear>()
val financialYearDates = mutableListOf<FinancialYear>()
var currentYear = startDate.year
var currentYear = startDate.year
@@ -2375,7 +2455,8 @@ open class ReportService(
val financialYear = FinancialYear(
val financialYear = FinancialYear(
start = YearMonth.of(startYear, startMonth),
start = YearMonth.of(startYear, startMonth),
end = YearMonth.of(endYear, endMonth),
end = YearMonth.of(endYear, endMonth),
hourlyRate = 0.0
hourlyRate = 0.0,
salaryPoint = 0
)
)
financialYearDates.add(financialYear)
financialYearDates.add(financialYear)
@@ -2386,9 +2467,10 @@ open class ReportService(
}
}
}
}
return financialYearDates.toTypedArray()
return financialYearDates
}
}
fun createPandLReportWorkbook(
fun createPandLReportWorkbook(
templatePath: String,
templatePath: String,
manhoursSpent: List<Map<String, Any>>,
manhoursSpent: List<Map<String, Any>>,
@@ -2422,6 +2504,7 @@ open class ReportService(
val convertEndMonth = YearMonth.of(endDate.year, endDate.month)
val convertEndMonth = YearMonth.of(endDate.year, endDate.month)
val monthRange: MutableList<Map<String, Any>> = ArrayList()
val monthRange: MutableList<Map<String, Any>> = ArrayList()
val financialYears = getFinancialYearDates(queryStartMonth, queryEndMonth, 10)
var currentDate = startDate
var currentDate = startDate
if (currentDate == endDate) {
if (currentDate == endDate) {
@@ -2520,12 +2603,35 @@ open class ReportService(
}
}
row6Cell.setCellValue(clientSubsidiary)
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
// Need to be updated to financial year
// Base on the searching criteria
// Base on the searching criteria
rowNum = 9
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
rowNum = 10
for (staff in staffInfoList) {
for (staff in staffInfoList) {
@@ -2542,19 +2648,37 @@ open class ReportService(
}
}
CellUtil.setAlignment(gradeCell, HorizontalAlignment.CENTER);
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++);
sheet.setRowBreak(rowNum++);
}
}
@@ -2608,6 +2732,7 @@ open class ReportService(
CellUtil.setVerticalAlignment(manhourCell, VerticalAlignment.CENTER);
CellUtil.setVerticalAlignment(manhourCell, VerticalAlignment.CENTER);
CellUtil.setCellStyleProperty(manhourCell, CellUtil.WRAP_TEXT, true)
CellUtil.setCellStyleProperty(manhourCell, CellUtil.WRAP_TEXT, true)
CellUtil.setCellStyleProperty(manhourCell, "borderBottom", BorderStyle.THIN)
CellUtil.setCellStyleProperty(manhourCell, "borderBottom", BorderStyle.THIN)
sheet.setColumnWidth(2 + monthColumnEnd, 15 * 256)
val manhourECell = row.getCell(2 + monthColumnEnd + 1) ?: row.createCell(2 + monthColumnEnd + 1)
val manhourECell = row.getCell(2 + monthColumnEnd + 1) ?: row.createCell(2 + monthColumnEnd + 1)
manhourECell.apply {
manhourECell.apply {