diff --git a/src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt b/src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt index f78fd42..52aaf45 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/SalaryEffectiveService.kt +++ b/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 } - 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) // 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 salary = salaryRepository.findById(salaryId).orElseThrow() + val salary = salaryRepository.findBySalaryPoint(salaryId).orElseThrow() val salaryEffective = SalaryEffective().apply { date = LocalDate.now() this.staff = staff diff --git a/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt b/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt index d575011..22cae68 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt @@ -175,7 +175,7 @@ open class StaffsService( staffSkillsetRepository.save(ss) } } - salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.id!!) + salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.salaryPoint.toLong()) return staff } @Transactional(rollbackFor = [Exception::class]) @@ -224,7 +224,7 @@ open class StaffsService( this.department = department } - salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.id!!) + salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.salaryPoint.toLong()) return staffRepository.save(staff) } diff --git a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt index 356575c..f9a8b54 100644 --- a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt +++ b/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.temporal.ChronoUnit import java.util.* - +import java.awt.Color data class DayInfo(val date: String?, val weekday: String?) @@ -79,6 +79,23 @@ open class ReportService( 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 ||============================== // fun generalCreateReportIndexed( // just loop through query records one by one, return rowIndex sheet: Sheet, @@ -2116,7 +2133,7 @@ open class ReportService( 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) return jdbcDao.queryForList(sql.toString(), args) } @@ -2755,7 +2772,7 @@ open class ReportService( + " left join salary s2 on s.salaryId = s2.salaryPoint" + " 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.otConsumed, 0)) as otConsumed," + " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate" @@ -3193,17 +3210,18 @@ open class ReportService( sortedGrades.forEach { grade: Grade -> createCell(columnIndex++).apply { - setCellValue(grade.name) + setCellValue("${grade.name} - Hours") val cloneStyle = workbook.createCellStyle() cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) cellStyle = cloneStyle.apply { alignment = HorizontalAlignment.CENTER + wrapText = true } } createCell(columnIndex++).apply { 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) val cloneStyle = workbook.createCellStyle() cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) @@ -3223,7 +3241,7 @@ open class ReportService( } createCell(columnIndex).apply { - setCellValue("Total Cost Adjusted by Salary Point by Team") + setCellValue("Total Cost (HKD) by Team") val cloneStyle = workbook.createCellStyle() cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) cellStyle = cloneStyle.apply { @@ -3360,6 +3378,8 @@ open class ReportService( // } conditionalFormattingNegative(sheet) + conditionalFormattingPositive(sheet) + // -------------------------- sheet 1 (Individual) -------------------------- // sheet = workbook.getSheetAt(1) @@ -3405,20 +3425,20 @@ open class ReportService( sortedTeams.forEach { team: Team -> // not his/her team staffs 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 } .sortedBy { it.staffId } .distinct() // his/her team projects 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 } .sortedByDescending { it.code } .distinct() // Team - if (!projects.isNullOrEmpty()) { + if (projects.isNotEmpty()) { sheet.createRow(rowIndex++).apply { createCell(0).apply { setCellValue("Team to be charged:") @@ -3493,7 +3513,7 @@ open class ReportService( var endRow = rowIndex projects.forEach { project: Project -> 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++ sheet.createRow(rowIndex++).apply { columnIndex = 0 @@ -3549,7 +3569,7 @@ open class ReportService( } } } - } +// } } } @@ -3595,6 +3615,7 @@ open class ReportService( } conditionalFormattingNegative(sheet) + conditionalFormattingPositive(sheet) return workbook } diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt b/src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt index bdcb0ed..7b01cf7 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/service/LeaveService.kt @@ -1,22 +1,29 @@ package com.ffii.tsms.modules.timesheet.service 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.TeamService import com.ffii.tsms.modules.timesheet.entity.* import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry 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.transaction.annotation.Transactional import java.time.LocalDate +import java.time.ZoneId import java.time.format.DateTimeFormatter import kotlin.jvm.optionals.getOrDefault +import kotlin.jvm.optionals.getOrNull @Service open class LeaveService( private val leaveRepository: LeaveRepository, private val leaveTypeRepository: LeaveTypeRepository, private val staffsService: StaffsService, + private val staffRepository: StaffRepository, private val teamService: TeamService ) { open fun getLeaveTypes(): List { @@ -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() + val sheet: Sheet = workbook.getSheetAt(0) + + logger.info("---------Start Import Leaves-------") + val leaveList = mutableListOf().toMutableList(); + for (i in 1.. { + 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" + } } diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt b/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt index 32bdf90..0445689 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt +++ b/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.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.TimesheetRepository import com.ffii.tsms.modules.timesheet.web.models.TeamMemberTimeEntries @@ -31,7 +33,7 @@ open class TimesheetsService( private val projectRepository: ProjectRepository, private val taskRepository: TaskRepository, 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 open fun saveTimesheet(recordTimeEntry: Map>): Map> { @@ -246,6 +248,67 @@ open class TimesheetsService( logger.info("---------end-------") 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() + + 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" } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt index 68ccecf..4aa049e 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt +++ b/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)) } + + @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()) + } } \ No newline at end of file diff --git a/src/main/resources/templates/report/Cross Team Charge Report.xlsx b/src/main/resources/templates/report/Cross Team Charge Report.xlsx index 54ec3cf..3beb992 100644 Binary files a/src/main/resources/templates/report/Cross Team Charge Report.xlsx and b/src/main/resources/templates/report/Cross Team Charge Report.xlsx differ