# Conflicts: # src/main/java/com/ffii/tsms/modules/report/service/ReportService.kttags/Baseline_30082024_BACKEND_UAT
| @@ -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 | ||||
| @@ -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) | ||||
| } | } | ||||
| @@ -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 | ||||
| } | } | ||||
| @@ -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 | ||||
| @@ -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" | |||||
| } | |||||
| } | } | ||||
| @@ -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" | |||||
| } | } | ||||
| } | } | ||||
| @@ -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()) | |||||
| } | |||||
| } | } | ||||
| @@ -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); | |||||