diff --git a/src/main/java/com/ffii/tsms/config/security/jwt/web/JwtAuthenticationController.java b/src/main/java/com/ffii/tsms/config/security/jwt/web/JwtAuthenticationController.java index 93dfe66..ef8b2cb 100644 --- a/src/main/java/com/ffii/tsms/config/security/jwt/web/JwtAuthenticationController.java +++ b/src/main/java/com/ffii/tsms/config/security/jwt/web/JwtAuthenticationController.java @@ -2,8 +2,12 @@ package com.ffii.tsms.config.security.jwt.web; import java.time.Instant; import java.util.HashSet; +import java.util.Map; import java.util.Set; +import com.ffii.tsms.modules.data.entity.Staff; +import com.ffii.tsms.modules.data.entity.StaffRepository; +import com.ffii.tsms.modules.user.service.GroupService; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -51,6 +55,12 @@ public class JwtAuthenticationController { @Autowired private JwtUserDetailsService userDetailsService; + @Autowired + private GroupService groupService; + + @Autowired + private StaffRepository staffRepository; + @Autowired private UserRepository userRepository; @@ -90,10 +100,14 @@ public class JwtAuthenticationController { final String accessToken = jwtTokenUtil.generateToken(user); final String refreshToken = jwtTokenUtil.createRefreshToken(user.getUsername()).getToken(); + final Map args = Map.of("userId", user.getId()); + final String role = groupService.getGroupName(args); + final Staff staff = staffRepository.findIdAndNameByUserIdAndDeletedFalse(user.getId()).orElse(null); + Set abilities = new HashSet<>(); userAuthorityService.getUserAuthority(user).forEach(auth -> abilities.add(new AbilityModel(auth.getAuthority()))); - return ResponseEntity.ok(new JwtResponse(accessToken, refreshToken, null, user, abilities)); + return ResponseEntity.ok(new JwtResponse(accessToken, refreshToken, role, user, abilities, staff)); } @PostMapping("/refresh-token") diff --git a/src/main/java/com/ffii/tsms/model/JwtResponse.java b/src/main/java/com/ffii/tsms/model/JwtResponse.java index 9b0d4e7..beaad81 100644 --- a/src/main/java/com/ffii/tsms/model/JwtResponse.java +++ b/src/main/java/com/ffii/tsms/model/JwtResponse.java @@ -3,6 +3,7 @@ package com.ffii.tsms.model; import java.io.Serializable; import java.util.Set; +import com.ffii.tsms.modules.data.entity.Staff; import com.ffii.tsms.modules.user.entity.User; public class JwtResponse implements Serializable { @@ -15,8 +16,11 @@ public class JwtResponse implements Serializable { private final String refreshToken; private final String role; private final Set abilities; + private final Staff staff; - public JwtResponse(String accessToken, String refreshToken, String role, User user, Set abilities) { + + + public JwtResponse(String accessToken, String refreshToken, String role, User user, Set abilities, Staff staff) { this.accessToken = accessToken; this.refreshToken = refreshToken; this.role = role; @@ -24,7 +28,8 @@ public class JwtResponse implements Serializable { this.name = user.getName(); this.email = user.getEmail(); this.abilities = abilities; - } + this.staff = staff; + } public String getAccessToken() { return this.accessToken; @@ -50,6 +55,9 @@ public class JwtResponse implements Serializable { return email; } + public Staff getStaff() { return staff; } + + public Set getAbilities() { return abilities; } diff --git a/src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java b/src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java index 3b0b4d1..d650327 100644 --- a/src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java +++ b/src/main/java/com/ffii/tsms/modules/data/entity/StaffRepository.java @@ -21,4 +21,6 @@ public interface StaffRepository extends AbstractRepository { Optional findByUserId(@Param("userId") Long userId); Optional> findAllByTeamIdAndDeletedFalse(Long id); + + Optional findIdAndNameByUserIdAndDeletedFalse(Long id); } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt index 28cca86..91a963c 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt @@ -110,8 +110,8 @@ open class DashboardService( + " s.name as teamLead," + " tg.name as expectedStage," + " p.totalManhour as budgetedManhour," - + " sum(t.normalConsumed) + sum(t.otConsumed) as spentManhour," - + " p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed) as remainedManhour," + + " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," + + " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour," + " coalesce (round(((sum(t.normalConsumed) - sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage," + " DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d') as comingPaymentMilestone" + " from project p" @@ -254,8 +254,8 @@ open class DashboardService( + " s.name as teamLead," + " tg.name as expectedStage," + " p.totalManhour as budgetedManhour," - + " sum(t.normalConsumed) + sum(t.otConsumed) as spentManhour," - + " p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed) as remainedManhour," + + " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," + + " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour," + " coalesce (round(((sum(t.normalConsumed) - sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage," + " DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d') as comingPaymentMilestone" + " from project p" @@ -301,7 +301,18 @@ open class DashboardService( + " coalesce(round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2),0) as cpi" + " from team t" + " left join project p on t.teamLead = p.teamLead" - + " left join invoice i on p.code = i.projectCode" + + " left join (" + + " select" + + " t3.id as tid," + + " sum(i3.issueAmount) as issueAmount," + + " sum(i3.paidAmount) as paidAmount" + + " from team t3" + + " left join project p3 on t3.teamLead = p3.teamLead" + + " left join invoice i3 on p3.code = i3.projectCode" + + " where t3.deleted = 0" + + " and p3.status = 'On-going'" + + " group by t3.id" + + " ) as i on i.tid = t.id" + " left join (" + " select" + " r.teamId as teamId," @@ -347,7 +358,17 @@ open class DashboardService( + " end as cashFlowStatus," + " round(sum(i.issueAmount) / (expenditure.cumulativeExpenditure),2) as cpi" + " from project p" - + " left join invoice i on p.code = i.projectCode" + + " left join (" + + " select" + + " p3.id as pid," + + " sum(i3.issueAmount) as issueAmount," + + " sum(i3.paidAmount) as paidAmount" + + " from project p3" + + " left join invoice i3 on p3.code = i3.projectCode" + + " where p3.deleted = 0" + + " and p3.status = 'On-going'" + + " group by p3.id" + + " ) as i on i.pid = p.id" + " left join (" + " select" + " sum(r.cumulativeExpenditure) as cumulativeExpenditure" @@ -399,6 +420,7 @@ open class DashboardService( + " left join customer c on p.customerId = c.id" + " left join (" + " select" + + " t3.id as tid," + " c3.id as cid," + " sum(i3.issueAmount) as issueAmount," + " sum(i3.paidAmount) as paidAmount" @@ -415,8 +437,8 @@ open class DashboardService( if (args.containsKey("teamId")) sql.append(" AND t3.id = :teamId"); } - sql.append( " group by c3.id" - + " ) as i on i.cid = c.id" + sql.append( " group by c3.id, t3.id" + + " ) as i on i.cid = c.id and i.tid = t.id" + " left join (" + " select" + " r.teamId as teamId," @@ -657,7 +679,17 @@ open class DashboardService( + " coalesce (expenditure.expenditure,0) as totalExpenditure," + " coalesce (sum(p.expectedTotalFee)*0.8 - expenditure.expenditure,0) as expenditureReceivable" + " from project p" - + " left join invoice i on p.code = i.projectCode" + + " left join (" + + " select" + + " p3.id as pid," + + " sum(i3.issueAmount) as issueAmount," + + " sum(i3.paidAmount) as paidAmount" + + " from project p3" + + " left join invoice i3 on p3.code = i3.projectCode" + + " where p3.deleted = 0" + + " and p3.status = 'On-going'" + + " group by p3.id" + + " ) as i on i.pid = p.id" + " left join(" + " select" + " sum(r.expenditure) as expenditure" @@ -933,9 +965,12 @@ open class DashboardService( + " p.id as id," + " p.code as projectCode," + " p.name as projectName," - + " concat(c.code,'-',c.name) as customerCodeAndName" + + " concat(c.code,'-',c.name) as customerCodeAndName," + + " concat(c.code,'-',c.name) as customerCodeAndName," + + " concat(s.code,'-',s.name) as subsidiaryCodeAndName" + " from project p" + " left join customer c on p.customerId = c.id" + + " left join subsidiary s on p.customerSubsidiaryId = s.id" + " where p.status = 'On-going'" + " and p.deleted = 0" ) @@ -1033,7 +1068,31 @@ open class DashboardService( return jdbcDao.queryForList(sql.toString(), args) } + fun monthlyActualTeamTotalManhoursSpent(args: Map): List> { + val sql = StringBuilder( + " WITH RECURSIVE date_series AS (" + + " SELECT DATE_FORMAT(:startdate, '%Y-%m-01') AS month" + + " UNION ALL" + + " SELECT DATE_FORMAT(DATE_ADD(month, INTERVAL 1 MONTH), '%Y-%m-01')" + + " FROM date_series" + + " WHERE month < DATE_FORMAT(:enddate, '%Y-%m-01')" + + " )" + + " SELECT" + + " ds.month AS yearMonth," + + " IFNULL(SUM(IFNULL(ts.normalConsumed, 0) + IFNULL(ts.otConsumed, 0)), 0) AS 'TotalManhourConsumed'," + + " :teamId AS teamId" + + " FROM date_series ds" + + " LEFT JOIN staff st" + + " on st.teamId = :teamId" + + " LEFT JOIN timesheet ts" + + " ON DATE_FORMAT(ts.recordDate, '%Y-%m') = DATE_FORMAT(ds.month, '%Y-%m') and ts.staffID = st.id" + + " WHERE ds.month BETWEEN DATE_FORMAT(:startdate, '%Y-%m-01') AND DATE_FORMAT(:enddate, '%Y-%m-01')" + + " GROUP BY ds.month, st.teamId" + + " ORDER BY ds.month" + ) + return jdbcDao.queryForList(sql.toString(), args) + } } diff --git a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt index a69305d..70c2162 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt @@ -232,4 +232,24 @@ class DashboardController( result["summarySubStage"] = summarySubStage return listOf(result) } + @GetMapping("/searchMonthlyActualTeamTotalManhoursSpent") + fun searchMonthlyActualTeamTotalManhoursSpent(request: HttpServletRequest?): List> { + val args = mutableMapOf() + val teamId = request?.getParameter("teamId") + val startdate = request?.getParameter("startdate") + val enddate = request?.getParameter("enddate") + if (teamId != null) { + args["teamId"] = teamId + } + if (startdate != null) { + args["startdate"] = startdate + } + if (enddate != null) { + args["enddate"] = enddate + } + val result = mutableMapOf() + val monthlyActualTeamTotalManhoursSpent = dashboardService.monthlyActualTeamTotalManhoursSpent(args) + result["monthlyActualTeamTotalManhoursSpent"] = monthlyActualTeamTotalManhoursSpent + return listOf(result) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/data/web/SkillController.kt b/src/main/java/com/ffii/tsms/modules/data/web/SkillController.kt index a6d9bd0..0b70d12 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/SkillController.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/SkillController.kt @@ -2,44 +2,47 @@ package com.ffii.tsms.modules.data.web import com.ffii.core.response.RecordsRes import com.ffii.core.utils.CriteriaArgsBuilder +import com.ffii.tsms.modules.common.SecurityUtils import com.ffii.tsms.modules.data.entity.Skill import com.ffii.tsms.modules.data.service.SkillService import com.ffii.tsms.modules.data.web.models.NewSkillRequest import jakarta.servlet.http.HttpServletRequest import jakarta.validation.Valid import org.springframework.http.HttpStatus +import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.ServletRequestBindingException import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/skill") -class SkillController(private val skillService: SkillService) { +open class SkillController(private val skillService: SkillService) { @PostMapping("/save") - fun saveSkill(@Valid @RequestBody newSkill: NewSkillRequest): Skill { + @PreAuthorize("hasAuthority('MAINTAIN_MASTERDATA')") + open fun saveSkill(@Valid @RequestBody newSkill: NewSkillRequest): Skill { return skillService.saveOrUpdate(newSkill) } @GetMapping("/{id}") - fun list(@Valid @PathVariable id: Long): List> { + open fun list(@Valid @PathVariable id: Long): List> { val args: MutableMap = HashMap() args["id"] = id return skillService.list(args); } @DeleteMapping("/delete/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) - fun delete(@PathVariable id: Long?) { + open fun delete(@PathVariable id: Long?) { skillService.markDelete(id) } @GetMapping - fun list(): List> { + open fun list(): List> { val args: MutableMap = HashMap() return skillService.list(args); } @GetMapping("/combo") @Throws(ServletRequestBindingException::class) - fun combo(request: HttpServletRequest?): RecordsRes> { + open fun combo(request: HttpServletRequest?): RecordsRes> { println(request) return RecordsRes>( skillService.combo( 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 4389e35..664d91b 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 @@ -178,8 +178,8 @@ open class ReportService( searchedClient: String, projects: List, timesheets: List, - numberOfDays: Int, - projectCompletion: Int, + daysUntilCurrentStageEnd: Int, + resourceUtilizationPercentage: Int, ): ByteArray { // Generate the Excel report with query results val workbook: Workbook = createProjectPotentialDelayReport( @@ -187,8 +187,8 @@ open class ReportService( searchedClient, projects, timesheets, - numberOfDays, - projectCompletion, + daysUntilCurrentStageEnd, + resourceUtilizationPercentage, PROJECT_POTENTIAL_DELAY_REPORT ) @@ -673,28 +673,43 @@ open class ReportService( rowIndex = 4 sheet.getRow(rowIndex).createCell(columnIndex).apply { - setCellValue(if (project.customer?.name == null) "N/A" else project.customer?.name) + setCellValue( + if (project.customer?.code != null && project.customer?.name != null) project.customer!!.code + " - " + project.customer!!.name + else if (project.customer?.code != null) project.customer!!.code + else if (project.customer?.name != null) project.customer!!.name + else "N/A" + ) } rowIndex = 5 + sheet.getRow(rowIndex).createCell(columnIndex).apply { + setCellValue( + if (project.customerSubsidiary?.code != null && project.customerSubsidiary?.name != null) project.customerSubsidiary!!.code + " - " + project.customerSubsidiary!!.name + else if (project.customerSubsidiary?.code != null) project.customerSubsidiary!!.code + else if (project.customerSubsidiary?.name != null) project.customerSubsidiary!!.name + else "N/A" + ) + } + + rowIndex = 6 sheet.getRow(rowIndex).createCell(columnIndex).apply { setCellValue(if (project.teamLead?.team?.name == null) "N/A" else project.teamLead?.team?.name) } - rowIndex = 9 + rowIndex = 10 sheet.getRow(rowIndex).apply { createCell(1).apply { - setCellValue(project.expectedTotalFee!!) + setCellValue(project.expectedTotalFee!! * 0.8) cellStyle.dataFormat = accountingStyle } createCell(2).apply { - setCellValue(project.expectedTotalFee!! / 0.8) + setCellValue(project.expectedTotalFee!!) cellStyle.dataFormat = accountingStyle } } - rowIndex = 10 + rowIndex = 11 val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! } val actualExpenditure = timesheets.sumOf { timesheet -> timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed @@ -712,21 +727,20 @@ open class ReportService( } } - rowIndex = 11 + rowIndex = 12 sheet.getRow(rowIndex).apply { createCell(1).apply { - cellFormula = "B10-B11" + cellFormula = "B11-B12" cellStyle.dataFormat = accountingStyle } createCell(2).apply { - cellFormula = "C10-C11" + cellFormula = "C11-C12" cellStyle.dataFormat = accountingStyle } } - rowIndex = 15 - + rowIndex = 16 val dateFormatter = if (dateType == "Date") DateTimeFormatter.ofPattern("yyyy/MM/dd") else DateTimeFormatter.ofPattern("MMM YYYY") @@ -796,7 +810,7 @@ open class ReportService( createCell(3).apply { val lastRow = rowIndex - 1 - if (lastRow == 15) { + if (lastRow == 16) { cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) } else { @@ -835,7 +849,7 @@ open class ReportService( createCell(3).apply { val lastRow = rowIndex - 1 - if (lastRow == 15) { + if (lastRow == 16) { cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) } else { cellFormula = @@ -863,8 +877,8 @@ open class ReportService( searchedClient: String, projects: List, timesheets: List, - numberOfDays: Int, - projectCompletion: Int, + daysUntilCurrentStageEnd: Int, + resourceUtilizationPercentage: Int, templatePath: String, ): Workbook { // please create a new function for each report template @@ -939,15 +953,29 @@ open class ReportService( createCell(4).apply { val currentClient = project.customer - val currentSubsidiary = project.customerSubsidiary - setCellValue(if (currentSubsidiary != null) currentSubsidiary.code + " - " + currentSubsidiary.name else currentClient?.code + " - " + currentClient?.name) + setCellValue( + if (currentClient?.code != null && currentClient.name != null) currentClient.code + " - " + currentClient.name + else if (currentClient?.code != null) currentClient.code + else if (currentClient?.name != null) currentClient.name + else "N/A" + ) } createCell(5).apply { - setCellValue(project.actualStart?.format(DATE_FORMATTER)) + val currentSubsidiary = project.customerSubsidiary + setCellValue( + if (currentSubsidiary?.code != null && currentSubsidiary.name != null) currentSubsidiary.code + " - " + currentSubsidiary.name + else if (currentSubsidiary?.code != null) currentSubsidiary.code + else if (currentSubsidiary?.name != null) currentSubsidiary.name + else "N/A" + ) } createCell(6).apply { + setCellValue(project.actualStart?.format(DATE_FORMATTER)) + } + + createCell(7).apply { setCellValue(project.planEnd?.format(DATE_FORMATTER)) } } @@ -958,22 +986,22 @@ open class ReportService( val manHoursSpent = groupedTimesheets[Pair(project.id, milestone.id)]?.sum() ?: 0.0 val resourceUtilization = manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!) // logger.info(project.name + " : " + milestone.taskGroup?.name + " : " + ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate)) -// logger.info(numberOfDays) - if (ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate) <= numberOfDays.toLong() && resourceUtilization <= projectCompletion.toDouble() / 100.0) { +// logger.info(daysUntilCurrentStageEnd) + if (ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate) <= daysUntilCurrentStageEnd.toLong() && resourceUtilization <= resourceUtilizationPercentage.toDouble() / 100.0) { milestoneCount++ val tempRow = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) rowIndex++ tempRow.apply { - createCell(7).apply { + createCell(8).apply { setCellValue(milestone.taskGroup?.name ?: "N/A") } - createCell(8).apply { + createCell(9).apply { setCellValue(milestone.endDate?.format(DATE_FORMATTER) ?: "N/A") } - createCell(9).apply { + createCell(10).apply { cellStyle.dataFormat = workbook.createDataFormat().getFormat("0.00%") // if (groupedTimesheets.containsKey(Pair(project.id, milestone.id))) { @@ -1396,18 +1424,18 @@ open class ReportService( generalCreateReportIndexed(sheet, result, rowIndex, columnIndex) val sheetCF = sheet.sheetConditionalFormatting - val rule1 = sheetCF.createConditionalFormattingRule("AND(J7 >= $lowerLimit, J7 <= 1)") - val rule2 = sheetCF.createConditionalFormattingRule("J7 > 1") + val rule1 = sheetCF.createConditionalFormattingRule("AND(K7 >= $lowerLimit, K7 <= 1)") + val rule2 = sheetCF.createConditionalFormattingRule("K7 > 1") var fillOrange = rule1.createPatternFormatting() fillOrange.setFillBackgroundColor(IndexedColors.LIGHT_ORANGE.index); fillOrange.setFillPattern(PatternFormatting.SOLID_FOREGROUND) var fillRed = rule2.createPatternFormatting() - fillRed.setFillBackgroundColor(IndexedColors.PINK.index); + fillRed.setFillBackgroundColor(IndexedColors.RED1.index); fillRed.setFillPattern(PatternFormatting.SOLID_FOREGROUND) val cfRules = arrayOf(rule1, rule2) - val regions = arrayOf(CellRangeAddress.valueOf("\$J7:\$K${rowIndex + 1}")) + val regions = arrayOf(CellRangeAddress.valueOf("\$K7:\$L${rowIndex + 1}")) sheetCF.addConditionalFormatting(regions, cfRules); return workbook @@ -1778,6 +1806,7 @@ open class ReportService( + " p.name, " + " t.code as team, " + " c.code as client, " + + " COALESCE(ss.name, 'N/A') as subsidiary, " + " p.expectedTotalFee * 0.8 as plannedBudget, " + " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, " + " COALESCE(p.totalManhour, 0) as plannedManhour, " @@ -1798,6 +1827,7 @@ open class ReportService( + " LEFT JOIN staff s ON p.teamLead = s.id " + " LEFT JOIN salary sa ON s.salaryId = sa.salaryPoint " + " LEFT JOIN customer c ON p.customerId = c.id " + + " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " + " left join teamNormalConsumed tns on tns.project_id = p.id " + " WHERE p.deleted = false " + " and p.status = 'On-going' " @@ -1808,6 +1838,8 @@ open class ReportService( sql.append("and t.id = :teamId") if (args.containsKey("custId")) sql.append("and c.id = :custId") + if (args.containsKey("subsidiaryId")) + sql.append("and ss.id = :subsidiaryId") if (args.containsKey("status")) statusFilter = when (args.get("status")) { "Potential Overconsumption" -> "and " + diff --git a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt index 44dc0d4..02ea8fa 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt @@ -35,6 +35,7 @@ import java.time.format.DateTimeFormatter import org.springframework.stereotype.Controller import org.springframework.data.jpa.repository.JpaRepository import com.ffii.tsms.modules.project.entity.Project +import com.ffii.tsms.modules.project.service.SubsidiaryService import com.ffii.tsms.modules.report.web.model.* import com.ffii.tsms.modules.timesheet.entity.Timesheet import org.springframework.data.domain.Example @@ -56,6 +57,7 @@ class ReportController( private val leaveRepository: LeaveRepository, private val teamService: TeamService, private val customerService: CustomerService, + private val subsidiaryService: SubsidiaryService, private val invoiceService: InvoiceService, private val gradeAllocationRepository: GradeAllocationRepository, private val subsidiaryRepository: SubsidiaryRepository ) { @@ -119,7 +121,7 @@ class ReportController( val projectTasks = projectTaskRepository.findAllByProjectIn(projects) val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) - val reportResult: ByteArray = excelReportService.generateProjectPotentialDelayReport(searchedTeam, searchedClient, projects, timesheets, request.numberOfDays, request.projectCompletion) + val reportResult: ByteArray = excelReportService.generateProjectPotentialDelayReport(searchedTeam, searchedClient, projects, timesheets, request.daysUntilCurrentStageEnd, request.resourceUtilizationPercentage) return ResponseEntity.ok() .header("filename", "Project Potential Delay Report - " + LocalDate.now() + ".xlsx") .body(ByteArrayResource(reportResult)) @@ -154,7 +156,7 @@ class ReportController( fun ProjectResourceOverconsumptionReport(@RequestBody @Valid request: ProjectResourceOverconsumptionReport): ResponseEntity { val lowerLimit = request.lowerLimit var team: String = "All" - var customer: String = "All" + var customerOrSubsidiary: String = "All" val args: MutableMap = mutableMapOf( "status" to request.status, "lowerLimit" to lowerLimit @@ -165,10 +167,14 @@ class ReportController( } if (request.custId != null) { args["custId"] = request.custId - customer = customerService.find(request.custId).orElseThrow().name + customerOrSubsidiary = customerService.find(request.custId).orElseThrow().name + } + if (request.subsidiaryId != null) { + args["subsidiaryId"] = request.subsidiaryId + customerOrSubsidiary = subsidiaryService.find(request.subsidiaryId).orElseThrow().name } val result: List> = excelReportService.getProjectResourceOverconsumptionReport(args); - val reportResult: ByteArray = excelReportService.generateProjectResourceOverconsumptionReport(team, customer, result, lowerLimit) + val reportResult: ByteArray = excelReportService.generateProjectResourceOverconsumptionReport(team, customerOrSubsidiary, result, lowerLimit) // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") return ResponseEntity.ok() // .contentType(mediaType) diff --git a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt index 8d3abb4..f302693 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt @@ -27,8 +27,8 @@ data class ProjectCashFlowReportRequest ( data class ProjectPotentialDelayReportRequest ( val teamId: String, val clientId: String, - val numberOfDays: Int, - val projectCompletion: Int, + val daysUntilCurrentStageEnd: Int, + val resourceUtilizationPercentage: Int, val type: String, ) @@ -46,6 +46,7 @@ data class LateStartReportRequest ( data class ProjectResourceOverconsumptionReport ( val teamId: Long?, val custId: Long?, + val subsidiaryId: Long?, val status: String, val lowerLimit: Double ) 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 97658ac..73a42ba 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 @@ -75,6 +75,17 @@ open class LeaveService( return transformToLeaveEntryMap(leaveRepository.findAllByStaff(memberStaff)) } + open fun deleteMemberLeaveEntry(staffId: Long, entryId: Long): Map> { + val currentStaff = staffsService.currentStaff() ?: throw BadRequestException() + // Make sure current staff is a team lead + teamService.getMyTeamForStaff(currentStaff) ?: throw BadRequestException() + + val memberStaff = staffsService.getStaff(staffId) + + leaveRepository.deleteById(entryId) + return transformToLeaveEntryMap(leaveRepository.findAllByStaff(memberStaff)) + } + open fun getTeamMemberLeave(): Map { val currentStaff = staffsService.currentStaff() ?: return emptyMap() // Get team where current staff is team lead 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 206daf0..96f9c23 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 @@ -83,6 +83,17 @@ open class TimesheetsService( return transformToTimeEntryMap(timesheetRepository.findAllByStaff(memberStaff)) } + open fun deleteMemberTimeEntry(staffId: Long, entryId: Long): Map> { + val currentStaff = staffsService.currentStaff() ?: throw BadRequestException() + // Make sure current staff is a team lead + teamService.getMyTeamForStaff(currentStaff) ?: throw BadRequestException() + + val memberStaff = staffsService.getStaff(staffId) + + timesheetRepository.deleteById(entryId) + return transformToTimeEntryMap(timesheetRepository.findAllByStaff(memberStaff)) + } + open fun getTimesheet(): Map> { // Need to be associated with a staff val currentStaff = staffsService.currentStaff() ?: return emptyMap() 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 df9bbe3..6f2d4ce 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 @@ -66,6 +66,16 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri }.getOrNull()) } + @PostMapping("/deleteMemberEntry") + fun deleteMemberEntry(@Valid @RequestBody request: TeamEntryDelete): Map> { + return timesheetsService.deleteMemberTimeEntry(request.staffId, request.entryId) + } + + @PostMapping("/deleteMemberLeave") + fun deleteMemberLeave(@Valid @RequestBody request: TeamEntryDelete): Map> { + return leaveService.deleteMemberLeaveEntry(request.staffId, request.entryId) + } + @GetMapping("/leaves") fun getLeaveEntry(): Map> { return leaveService.getLeaves() diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamEntryDelete.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamEntryDelete.kt new file mode 100644 index 0000000..a79a278 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamEntryDelete.kt @@ -0,0 +1,6 @@ +package com.ffii.tsms.modules.timesheet.web.models + +data class TeamEntryDelete( + val staffId: Long, + val entryId: Long, +) diff --git a/src/main/java/com/ffii/tsms/modules/user/entity/UserRepository.java b/src/main/java/com/ffii/tsms/modules/user/entity/UserRepository.java index 40214d3..7107cb2 100644 --- a/src/main/java/com/ffii/tsms/modules/user/entity/UserRepository.java +++ b/src/main/java/com/ffii/tsms/modules/user/entity/UserRepository.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.Optional; import org.springframework.data.repository.query.Param; - +import org.springframework.data.jpa.repository.Query; import com.ffii.core.support.AbstractRepository; public interface UserRepository extends AbstractRepository { @@ -12,5 +12,6 @@ public interface UserRepository extends AbstractRepository { List findByName(@Param("name") String name); List findAllByAndDeletedFalse(); - Optional findByUsernameAndDeletedFalse(String username); + Optional findByUsernameAndDeletedFalse(String username); + } diff --git a/src/main/java/com/ffii/tsms/modules/user/service/GroupService.java b/src/main/java/com/ffii/tsms/modules/user/service/GroupService.java index 7b128bd..d0fb138 100644 --- a/src/main/java/com/ffii/tsms/modules/user/service/GroupService.java +++ b/src/main/java/com/ffii/tsms/modules/user/service/GroupService.java @@ -172,6 +172,18 @@ public class GroupService extends AbstractBaseEntityService args) { + StringBuilder sql = new StringBuilder("select" + + " g.name " + + " from user u " + + " left join user_group ug on u.id = ug.userId " + + " left join `group`g on ug.groupId = g.id " + + " where g.deleted = false " + + " and u.id = :userId" + ); + return jdbcDao.queryForString(sql.toString(), args); + } @Transactional(rollbackFor = Exception.class) diff --git a/src/main/resources/db/changelog/changes/20240529_01_cyril/01_insert_authority.sql b/src/main/resources/db/changelog/changes/20240529_01_cyril/01_insert_authority.sql new file mode 100644 index 0000000..471ea88 --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240529_01_cyril/01_insert_authority.sql @@ -0,0 +1,8 @@ +-- liquibase formatted sql +-- changeset cyril:authority, user_authority + +INSERT INTO authority (authority,name) +VALUES ('DELETE_PROJECT','delete project'); + +INSERT INTO user_authority (userId,authId) +VALUES (1,19); diff --git a/src/main/resources/templates/report/AR02_Delay Report v02.xlsx b/src/main/resources/templates/report/AR02_Delay Report v02.xlsx index d07a414..1a4348a 100644 Binary files a/src/main/resources/templates/report/AR02_Delay Report v02.xlsx and b/src/main/resources/templates/report/AR02_Delay Report v02.xlsx differ diff --git a/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx b/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx index 88dd3c8..fb94181 100644 Binary files a/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx and b/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx differ diff --git a/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx b/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx index 108ca57..9213dcd 100644 Binary files a/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx and b/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx differ