Browse Source

Merge branch 'master' of https://git.2fi-solutions.com/davidhui/TSMS-backend

# Conflicts:
#	src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
tags/Baseline_30082024_BACKEND_UAT
MSI\2Fi 1 year ago
parent
commit
a3dee0a3d2
9 changed files with 232 additions and 42 deletions
  1. +10
    -8
      src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt
  2. +2
    -2
      src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt
  3. +32
    -22
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  4. +5
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt
  5. +83
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt
  6. +71
    -10
      src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt
  7. +23
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt
  8. +6
    -0
      src/main/resources/db/changelog/changes/20240725_01_wayne/01_update_timesheet_non_billable_task.sql
  9. BIN
      src/main/resources/templates/report/Cross Team Charge Report.xlsx

+ 10
- 8
src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt View File

@@ -41,20 +41,22 @@ open class SalaryEffectiveService(
return if(result > 0) result.toLong() else -1 return if(result > 0) result.toLong() else -1
} }


open fun saveSalaryEffective (staffId: Long, salaryId: Long): SalaryEffective {
val existSalaryEffective = findByStaffIdAndSalaryId(staffId, salaryId)

if (existSalaryEffective != null) {
open fun saveSalaryEffective (staffId: Long, salaryId: Long): SalaryEffective? {
// val existSalaryEffective = findByStaffIdAndSalaryId(staffId, salaryId)
//
// logger.info(existSalaryEffective)
// if (existSalaryEffective != null) {
val latestSalaryId = findLatestSalaryIdByStaffId(staffId) val latestSalaryId = findLatestSalaryIdByStaffId(staffId)


// If latest salary id is same as current salary id, then skip // If latest salary id is same as current salary id, then skip
if (latestSalaryId == existSalaryEffective.salary.id) {
return existSalaryEffective
// if (latestSalaryId == existSalaryEffective.salary.salaryPoint.toLong()) {
if (latestSalaryId == salaryId) {
return null
} }
}
// }


val staff = staffRepository.findById(staffId).orElseThrow() val staff = staffRepository.findById(staffId).orElseThrow()
val salary = salaryRepository.findById(salaryId).orElseThrow()
val salary = salaryRepository.findBySalaryPoint(salaryId).orElseThrow()
val salaryEffective = SalaryEffective().apply { val salaryEffective = SalaryEffective().apply {
date = LocalDate.now() date = LocalDate.now()
this.staff = staff this.staff = staff


+ 2
- 2
src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt View File

@@ -175,7 +175,7 @@ open class StaffsService(
staffSkillsetRepository.save(ss) staffSkillsetRepository.save(ss)
} }
} }
salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.id!!)
salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.salaryPoint.toLong())
return staff return staff
} }
@Transactional(rollbackFor = [Exception::class]) @Transactional(rollbackFor = [Exception::class])
@@ -224,7 +224,7 @@ open class StaffsService(
this.department = department this.department = department
} }


salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.id!!)
salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.salaryPoint.toLong())


return staffRepository.save(staff) return staffRepository.save(staff)
} }


+ 32
- 22
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt View File

@@ -40,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 java.awt.Color
import kotlin.collections.ArrayList import kotlin.collections.ArrayList




@@ -84,6 +85,23 @@ open class ReportService(
sheetCF.addConditionalFormatting(regions, ruleNegative) sheetCF.addConditionalFormatting(regions, ruleNegative)
} }


private fun conditionalFormattingPositive(sheet: Sheet) {
// Create a conditional formatting rule
val sheetCF = sheet.sheetConditionalFormatting

val color = XSSFColor(Color(255, 242, 204), null)
val formula = "AND(ISNUMBER(A1), A1>0)"
val rulePositive = sheetCF.createConditionalFormattingRule(formula).apply {
createPatternFormatting().apply {
setFillBackgroundColor(color)
}
}

val lastCell = sheet.maxOf { it.lastCellNum - 1 }
val regions = arrayOf(CellRangeAddress(0, sheet.lastRowNum, 0, lastCell))
sheetCF.addConditionalFormatting(regions, rulePositive)
}

// ==============================|| GENERATE REPORT ||============================== // // ==============================|| GENERATE REPORT ||============================== //
fun generalCreateReportIndexed( // just loop through query records one by one, return rowIndex fun generalCreateReportIndexed( // just loop through query records one by one, return rowIndex
sheet: Sheet, sheet: Sheet,
@@ -2121,7 +2139,7 @@ open class ReportService(
else -> "" else -> ""
} }
} }
sql.append(" group by p.code, p.name, tm.code, c.code, c.name, ss.code, ss.name, p.expectedTotalFee, p.subContractFee, p.totalManhour, p.expectedTotalFee ")
sql.append(" group by p.code, p.name, tm.code, c.code, c.name, ss.code, ss.name, p.expectedTotalFee, p.subContractFee, p.totalManhour ")
sql.append(statusFilter) sql.append(statusFilter)
return jdbcDao.queryForList(sql.toString(), args) return jdbcDao.queryForList(sql.toString(), args)
} }
@@ -2228,7 +2246,6 @@ open class ReportService(
// All recorded man hours of the project // 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"
) )
@@ -2278,16 +2295,6 @@ open class ReportService(
info["description"] = item.getValue("description") info["description"] = item.getValue("description")
} }


// 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() 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"] }) {
@@ -2470,7 +2477,6 @@ open class ReportService(
return financialYearDates return financialYearDates
} }



fun createPandLReportWorkbook( fun createPandLReportWorkbook(
templatePath: String, templatePath: String,
manhoursSpent: List<Map<String, Any>>, manhoursSpent: List<Map<String, Any>>,
@@ -2880,7 +2886,7 @@ open class ReportService(
+ " left join salary s2 on s.salaryId = s2.salaryPoint" + " left join salary s2 on s.salaryId = s2.salaryPoint"
+ " left join team t2 on t2.id = s.teamId" + " left join team t2 on t2.id = s.teamId"
+ " )" + " )"
+ " select p.code, p.description, c.name as client, IFNULL(s2.name, \'NA\') as subsidiary, concat(t.code, \' - \', t.name) as teamLead, p.expectedTotalFee, ifnull(p.subContractFee, 0) as subContractFee"
+ " select p.code, p.description, c.name as client, IFNULL(s2.name, \'NA\') as subsidiary, concat(t.code, \' - \', t.name) as teamLead, p.expectedTotalFee, ifnull(p.subContractFee, 0) as subContractFee,"
+ " SUM(IFNULL(cte_ts.normalConsumed, 0)) as normalConsumed," + " SUM(IFNULL(cte_ts.normalConsumed, 0)) as normalConsumed,"
+ " SUM(IFNULL(cte_ts.otConsumed, 0)) as otConsumed," + " SUM(IFNULL(cte_ts.otConsumed, 0)) as otConsumed,"
+ " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate" + " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate"
@@ -3318,17 +3324,18 @@ open class ReportService(


sortedGrades.forEach { grade: Grade -> sortedGrades.forEach { grade: Grade ->
createCell(columnIndex++).apply { createCell(columnIndex++).apply {
setCellValue(grade.name)
setCellValue("${grade.name} - Hours")
val cloneStyle = workbook.createCellStyle() val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) cloneStyle.cloneStyleFrom(boldFontWithBorderStyle)
cellStyle = cloneStyle.apply { cellStyle = cloneStyle.apply {
alignment = HorizontalAlignment.CENTER alignment = HorizontalAlignment.CENTER
wrapText = true
} }
} }


createCell(columnIndex++).apply { createCell(columnIndex++).apply {
val cellValue = grade.name.trim().substring(0, 1) + grade.name.trim() val cellValue = grade.name.trim().substring(0, 1) + grade.name.trim()
.substring(grade.name.length - 1) + " - Cost Adjusted by Salary Point"
.substring(grade.name.length - 1) + " - Cost (HKD)"
setCellValue(cellValue) setCellValue(cellValue)
val cloneStyle = workbook.createCellStyle() val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) cloneStyle.cloneStyleFrom(boldFontWithBorderStyle)
@@ -3348,7 +3355,7 @@ open class ReportService(
} }


createCell(columnIndex).apply { createCell(columnIndex).apply {
setCellValue("Total Cost Adjusted by Salary Point by Team")
setCellValue("Total Cost (HKD) by Team")
val cloneStyle = workbook.createCellStyle() val cloneStyle = workbook.createCellStyle()
cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) cloneStyle.cloneStyleFrom(boldFontWithBorderStyle)
cellStyle = cloneStyle.apply { cellStyle = cloneStyle.apply {
@@ -3485,6 +3492,8 @@ open class ReportService(
// } // }


conditionalFormattingNegative(sheet) conditionalFormattingNegative(sheet)
conditionalFormattingPositive(sheet)

// -------------------------- sheet 1 (Individual) -------------------------- // // -------------------------- sheet 1 (Individual) -------------------------- //
sheet = workbook.getSheetAt(1) sheet = workbook.getSheetAt(1)


@@ -3530,20 +3539,20 @@ open class ReportService(
sortedTeams.forEach { team: Team -> sortedTeams.forEach { team: Team ->
// not his/her team staffs // not his/her team staffs
val staffs = timesheets val staffs = timesheets
.filter { it.project?.teamLead?.team?.id != it.staff?.team?.id && (it.project?.teamLead?.id != team.staff.id || it.staff?.team?.id != team.id) }
.filter { it.project?.teamLead?.team?.id == team.id && it.staff?.team?.id != team.id }
.mapNotNull { it.staff } .mapNotNull { it.staff }
.sortedBy { it.staffId } .sortedBy { it.staffId }
.distinct() .distinct()


// his/her team projects // his/her team projects
val projects = timesheets val projects = timesheets
.filter { it.project?.teamLead?.team?.id != it.staff?.team?.id && it.project?.teamLead?.id == team.staff.id }
.filter { it.project?.teamLead?.team?.id == team.id && it.project?.teamLead?.team?.id != it.staff?.team?.id }
.mapNotNull { it.project } .mapNotNull { it.project }
.sortedByDescending { it.code } .sortedByDescending { it.code }
.distinct() .distinct()


// Team // Team
if (!projects.isNullOrEmpty()) {
if (projects.isNotEmpty()) {
sheet.createRow(rowIndex++).apply { sheet.createRow(rowIndex++).apply {
createCell(0).apply { createCell(0).apply {
setCellValue("Team to be charged:") setCellValue("Team to be charged:")
@@ -3618,7 +3627,7 @@ open class ReportService(
var endRow = rowIndex var endRow = rowIndex
projects.forEach { project: Project -> projects.forEach { project: Project ->
if (teamId.lowercase() == "all" || teamId.toLong() == project.teamLead?.team?.id || teamId.toLong() == team.id) { if (teamId.lowercase() == "all" || teamId.toLong() == project.teamLead?.team?.id || teamId.toLong() == team.id) {
if (team.id == project.teamLead?.team?.id) {
// if (team.id == project.teamLead?.team?.id) {
endRow++ endRow++
sheet.createRow(rowIndex++).apply { sheet.createRow(rowIndex++).apply {
columnIndex = 0 columnIndex = 0
@@ -3674,7 +3683,7 @@ open class ReportService(
} }
} }
} }
}
// }
} }
} }


@@ -3720,6 +3729,7 @@ open class ReportService(
} }


conditionalFormattingNegative(sheet) conditionalFormattingNegative(sheet)
conditionalFormattingPositive(sheet)


return workbook return workbook
} }

+ 5
- 0
src/main/java/com/ffii/tsms/modules/timesheet/entity/Timesheet.kt View File

@@ -4,6 +4,7 @@ import com.ffii.core.entity.BaseEntity
import com.ffii.tsms.modules.data.entity.Staff import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.project.entity.Project
import com.ffii.tsms.modules.project.entity.ProjectTask import com.ffii.tsms.modules.project.entity.ProjectTask
import com.ffii.tsms.modules.project.entity.Task
import jakarta.persistence.* import jakarta.persistence.*
import jakarta.validation.constraints.NotNull import jakarta.validation.constraints.NotNull
import java.time.LocalDate import java.time.LocalDate
@@ -30,6 +31,10 @@ open class Timesheet : BaseEntity<Long>() {
@JoinColumn(name = "projectTaskId") @JoinColumn(name = "projectTaskId")
open var projectTask: ProjectTask? = null open var projectTask: ProjectTask? = null


@ManyToOne
@JoinColumn(name = "nonBillableTaskId")
open var nonBillableTask: Task? = null

@ManyToOne @ManyToOne
@JoinColumn(name = "projectId") @JoinColumn(name = "projectId")
open var project: Project? = null open var project: Project? = null


+ 83
- 0
src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt View File

@@ -1,22 +1,29 @@
package com.ffii.tsms.modules.timesheet.service package com.ffii.tsms.modules.timesheet.service


import com.ffii.core.exception.BadRequestException import com.ffii.core.exception.BadRequestException
import com.ffii.tsms.modules.data.entity.StaffRepository
import com.ffii.tsms.modules.data.service.StaffsService import com.ffii.tsms.modules.data.service.StaffsService
import com.ffii.tsms.modules.data.service.TeamService import com.ffii.tsms.modules.data.service.TeamService
import com.ffii.tsms.modules.timesheet.entity.* import com.ffii.tsms.modules.timesheet.entity.*
import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry
import com.ffii.tsms.modules.timesheet.web.models.TeamMemberLeaveEntries import com.ffii.tsms.modules.timesheet.web.models.TeamMemberLeaveEntries
import org.apache.commons.logging.LogFactory
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.Workbook
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate import java.time.LocalDate
import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import kotlin.jvm.optionals.getOrDefault import kotlin.jvm.optionals.getOrDefault
import kotlin.jvm.optionals.getOrNull


@Service @Service
open class LeaveService( open class LeaveService(
private val leaveRepository: LeaveRepository, private val leaveRepository: LeaveRepository,
private val leaveTypeRepository: LeaveTypeRepository, private val leaveTypeRepository: LeaveTypeRepository,
private val staffsService: StaffsService, private val staffsService: StaffsService,
private val staffRepository: StaffRepository,
private val teamService: TeamService private val teamService: TeamService
) { ) {
open fun getLeaveTypes(): List<LeaveType> { open fun getLeaveTypes(): List<LeaveType> {
@@ -130,4 +137,80 @@ open class LeaveService(
) )
} } } }
} }

@Transactional(rollbackFor = [Exception::class])
open fun importFile(workbook: Workbook?): String {
val logger = LogFactory.getLog(javaClass)

if (workbook == null) {
return "No Excel import" // if workbook is null
}

val notExistStaffList = mutableListOf<String>()
val sheet: Sheet = workbook.getSheetAt(0)

logger.info("---------Start Import Leaves-------")
val leaveList = mutableListOf<Leave>().toMutableList();
for (i in 1..<sheet.lastRowNum) {
val row = sheet.getRow(i)

if (row.getCell(1) != null && !row.getCell(1).stringCellValue.isNullOrBlank()) {
logger.info("row :$i | lastCellNum" + row.lastCellNum)

// process staff
logger.info("---------staff-------")
val staff = staffRepository.findByStaffId(row.getCell(1).stringCellValue).getOrNull()
if (staff == null) {
notExistStaffList += row.getCell(1).stringCellValue
// staff = staffRepository.findByStaffId("B000").orElseThrow()
}

if (staff != null) {
// process leave type
logger.info("---------leave type-------")
val leaveTypeCellValue = row.getCell(4).stringCellValue
val leaveType = when (leaveTypeCellValue) {
"LA" -> {
leaveTypeRepository.findById(1).getOrNull()
}
"LS" -> {
leaveTypeRepository.findById(2).getOrNull()
}
else -> {
leaveTypeRepository.findById(3).getOrNull()
}
}
val remark = if (leaveTypeCellValue == "LA" || leaveTypeCellValue == "LS") {
null
} else {
leaveTypeCellValue
}

// process record date
logger.info("---------record date-------")
val formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
logger.info("Date: ${row.getCell(7).dateCellValue.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()}")
val recordDate = row.getCell(7).dateCellValue.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

// hour
logger.info("---------hour-------")
val leaveHours: Double = row.getCell(8).numericCellValue

leaveList += Leave().apply {
this.staff = staff
this.recordDate = recordDate
this.leaveHours = leaveHours
this.leaveType = leaveType
this.remark = remark
}
}
}
}

leaveRepository.saveAll(leaveList)
logger.info("---------end-------")
logger.info("Not Exist Staff List: "+ notExistStaffList.distinct().joinToString(", "))

return if (sheet.lastRowNum > 0) "Import Excel success btw not Exist: " + notExistStaffList.distinct().joinToString(", ") else "Import Excel failure"
}
} }

+ 71
- 10
src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt View File

@@ -1,15 +1,12 @@
package com.ffii.tsms.modules.timesheet.service package com.ffii.tsms.modules.timesheet.service


import com.ffii.core.exception.BadRequestException import com.ffii.core.exception.BadRequestException
import com.ffii.core.utils.ExcelUtils
import com.ffii.tsms.modules.data.entity.BuildingType
import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.data.entity.StaffRepository import com.ffii.tsms.modules.data.entity.StaffRepository
import com.ffii.tsms.modules.data.entity.WorkNature
import com.ffii.tsms.modules.data.service.StaffsService import com.ffii.tsms.modules.data.service.StaffsService
import com.ffii.tsms.modules.data.service.TeamService import com.ffii.tsms.modules.data.service.TeamService
import com.ffii.tsms.modules.project.entity.* import com.ffii.tsms.modules.project.entity.*
import com.ffii.tsms.modules.project.web.models.*
import com.ffii.tsms.modules.timesheet.entity.Leave
import com.ffii.tsms.modules.timesheet.entity.LeaveRepository
import com.ffii.tsms.modules.timesheet.entity.Timesheet import com.ffii.tsms.modules.timesheet.entity.Timesheet
import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository
import com.ffii.tsms.modules.timesheet.web.models.TeamMemberTimeEntries import com.ffii.tsms.modules.timesheet.web.models.TeamMemberTimeEntries
@@ -31,7 +28,7 @@ open class TimesheetsService(
private val projectRepository: ProjectRepository, private val projectRepository: ProjectRepository,
private val taskRepository: TaskRepository, private val taskRepository: TaskRepository,
private val staffsService: StaffsService, private val staffsService: StaffsService,
private val teamService: TeamService, private val staffRepository: StaffRepository
private val teamService: TeamService, private val staffRepository: StaffRepository, private val leaveRepository: LeaveRepository
) { ) {
@Transactional @Transactional
open fun saveTimesheet(recordTimeEntry: Map<LocalDate, List<TimeEntry>>): Map<String, List<TimeEntry>> { open fun saveTimesheet(recordTimeEntry: Map<LocalDate, List<TimeEntry>>): Map<String, List<TimeEntry>> {
@@ -56,6 +53,7 @@ open class TimesheetsService(
this.projectTask = projectTask this.projectTask = projectTask
this.project = project this.project = project
this.remark = timeEntry.remark this.remark = timeEntry.remark
this.nonBillableTask = if (project == null) task else null
} }
} }
} }
@@ -94,6 +92,7 @@ open class TimesheetsService(
this.remark = entry.remark this.remark = entry.remark
this.recordDate = this.recordDate ?: recordDate this.recordDate = this.recordDate ?: recordDate
this.staff = this.staff ?: memberStaff this.staff = this.staff ?: memberStaff
this.nonBillableTask = if (project == null) task else null
} }


timesheetRepository.save(timesheet) timesheetRepository.save(timesheet)
@@ -149,11 +148,12 @@ open class TimesheetsService(
.groupBy { timesheet -> timesheet.recordDate!!.format(DateTimeFormatter.ISO_LOCAL_DATE) } .groupBy { timesheet -> timesheet.recordDate!!.format(DateTimeFormatter.ISO_LOCAL_DATE) }
.mapValues { (_, timesheets) -> .mapValues { (_, timesheets) ->
timesheets.map { timesheet -> timesheets.map { timesheet ->
val projectTask = timesheet.projectTask
TimeEntry( TimeEntry(
id = timesheet.id!!, id = timesheet.id!!,
projectId = timesheet.projectTask?.project?.id ?: timesheet.project?.id,
taskId = timesheet.projectTask?.task?.id,
taskGroupId = timesheet.projectTask?.task?.taskGroup?.id,
projectId = projectTask?.project?.id ?: timesheet.project?.id,
taskId = (projectTask?.task ?: timesheet.nonBillableTask)?.id,
taskGroupId = (projectTask?.task ?: timesheet.nonBillableTask)?.taskGroup?.id,
inputHours = timesheet.normalConsumed ?: 0.0, inputHours = timesheet.normalConsumed ?: 0.0,
otHours = timesheet.otConsumed ?: 0.0, otHours = timesheet.otConsumed ?: 0.0,
remark = timesheet.remark remark = timesheet.remark
@@ -246,6 +246,67 @@ open class TimesheetsService(
logger.info("---------end-------") logger.info("---------end-------")
logger.info("Not Exist Project List: "+ notExistProjectList.distinct().joinToString(", ")) logger.info("Not Exist Project List: "+ notExistProjectList.distinct().joinToString(", "))


return if (sheet.lastRowNum > 0) "Import Excel success btw " + notExistProjectList.joinToString(", ") else "Import Excel failure"
return if (sheet.lastRowNum > 0) "Import Excel success btw " + notExistProjectList.distinct().joinToString(", ") else "Import Excel failure"
}

@Transactional(rollbackFor = [Exception::class])
open fun rearrangeTimesheets(): String {
val logger = LogFactory.getLog(javaClass)

val timesheets = timesheetRepository.findAll()
.filter { it.deleted == false }
.sortedBy { it.id }

var newTimesheetsList = mutableListOf<Timesheet>()

val leaves = leaveRepository.findAll()
.filter { it.deleted == false }
.groupBy { leave: Leave -> Pair(leave.staff?.id, leave.recordDate) }
.mapValues { (_, leave) ->
leave.sumOf { it.leaveHours ?: 0.0 }
}

timesheets.forEach { timesheet: Timesheet ->
if (timesheet.staff?.staffId != "B000") {
val leaveHours = leaves[Pair(timesheet.staff?.id, timesheet.recordDate)] ?: 0.0

val timesheetHours = newTimesheetsList
.filter { it.recordDate?.equals(timesheet.recordDate) == true && it.staff?.id == timesheet.staff?.id}
.sumOf { (it.normalConsumed ?: 0.0) + (it.otConsumed ?: 0.0) }

val previousHours = leaveHours + timesheetHours
val currentHours = (timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed ?: 0.0)
val totalHours = previousHours + currentHours

val newNormalConsumed = when {
totalHours <= 8.0 -> currentHours
else -> maxOf(8.0 - previousHours, 0.0)
}

val newOtConsumed = when {
totalHours <= 8.0 -> 0.0
else -> maxOf(currentHours - newNormalConsumed, 0.0)
}

logger.info("-----------------------------------------")
logger.info("ID: ${timesheet.id}")
logger.info("Staff: ${timesheet.staff?.staffId} | Record Date: ${timesheet.recordDate}")
logger.info("totalHours: ${totalHours} | " +
"Current Normal Consumed: ${(timesheet.normalConsumed ?: 0.0)} | " +
"Current OT Consumed: ${(timesheet.otConsumed ?: 0.0)} | " +
"Leave Hours: ${leaveHours} | " +
"New Normal Consumed: ${newNormalConsumed} | " +
"New OT Consumed: ${newOtConsumed}")

newTimesheetsList += timesheet.apply {
normalConsumed = newNormalConsumed
otConsumed = newOtConsumed
}
}
}
logger.info("END")
timesheetRepository.saveAll(newTimesheetsList)

return "Rearrange success"
} }
} }

+ 23
- 0
src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt View File

@@ -136,4 +136,27 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri


return ResponseEntity.ok(timesheetsService.importFile(workbook)) return ResponseEntity.ok(timesheetsService.importFile(workbook))
} }

@PostMapping("/import-leave")
@Throws(ServletRequestBindingException::class)
fun importLeaveFile(request: HttpServletRequest): ResponseEntity<*> {
var workbook: Workbook? = null

try {
val multipartFile = (request as MultipartHttpServletRequest).getFile("multipartFileList")
workbook = XSSFWorkbook(multipartFile?.inputStream)
} catch (e: Exception) {
println("Excel Wrong")
println(e)
}

return ResponseEntity.ok(leaveService.importFile(workbook))
}

@PostMapping("/rearrange")
@Throws(ServletRequestBindingException::class)
fun rearrangeTimesheets(request: HttpServletRequest): ResponseEntity<*> {

return ResponseEntity.ok(timesheetsService.rearrangeTimesheets())
}
} }

+ 6
- 0
src/main/resources/db/changelog/changes/20240725_01_wayne/01_update_timesheet_non_billable_task.sql View File

@@ -0,0 +1,6 @@
-- liquibase formatted sql
-- changeset wayne:update_timesheet_non_billable_task

ALTER TABLE timesheet ADD nonBillableTaskId INT NULL;

ALTER TABLE timesheet ADD CONSTRAINT FK_TIMESHEET_ON_NONBILLABLETASKID FOREIGN KEY (nonBillableTaskId) REFERENCES task (id);

BIN
src/main/resources/templates/report/Cross Team Charge Report.xlsx View File


Loading…
Cancel
Save