浏览代码

update salary effective, staff, report, leave, timesheet

tags/Baseline_30082024_BACKEND_UAT
cyril.tsui 1年前
父节点
当前提交
af3b965005
共有 7 个文件被更改,包括 215 次插入23 次删除
  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
    -11
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  4. +83
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt
  5. +65
    -2
      src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt
  6. +23
    -0
      src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt
  7. 二进制
      src/main/resources/templates/report/Cross Team Charge Report.xlsx

+ 10
- 8
src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt 查看文件

@@ -39,20 +39,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 查看文件

@@ -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
- 11
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt 查看文件

@@ -37,7 +37,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


data class DayInfo(val date: String?, val weekday: String?) data class DayInfo(val date: String?, val weekday: String?)


@@ -79,6 +79,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,
@@ -2116,7 +2133,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)
} }
@@ -2755,7 +2772,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"
@@ -3193,17 +3210,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)
@@ -3223,7 +3241,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 {
@@ -3360,6 +3378,8 @@ open class ReportService(
// } // }


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

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


@@ -3405,20 +3425,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:")
@@ -3493,7 +3513,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
@@ -3549,7 +3569,7 @@ open class ReportService(
} }
} }
} }
}
// }
} }
} }


@@ -3595,6 +3615,7 @@ open class ReportService(
} }


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


return workbook return workbook
} }

+ 83
- 0
src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt 查看文件

@@ -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"
}
} }

+ 65
- 2
src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt 查看文件

@@ -10,6 +10,8 @@ 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.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 +33,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>> {
@@ -246,6 +248,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 查看文件

@@ -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())
}
} }

二进制
src/main/resources/templates/report/Cross Team Charge Report.xlsx 查看文件


正在加载...
取消
保存