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 ef8b2cb..04e052a 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 @@ -3,10 +3,12 @@ package com.ffii.tsms.config.security.jwt.web; import java.time.Instant; import java.util.HashSet; import java.util.Map; +import java.util.Objects; 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.data.service.StaffsService; import com.ffii.tsms.modules.user.service.GroupService; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -58,6 +60,9 @@ public class JwtAuthenticationController { @Autowired private GroupService groupService; + @Autowired + private StaffsService staffsService; + @Autowired private StaffRepository staffRepository; @@ -102,7 +107,7 @@ public class JwtAuthenticationController { final Map args = Map.of("userId", user.getId()); final String role = groupService.getGroupName(args); - final Staff staff = staffRepository.findIdAndNameByUserIdAndDeletedFalse(user.getId()).orElse(null); + final Map staff = Objects.requireNonNull(staffsService.getCurrentStaff(user.getId())).orElse(null); Set abilities = new HashSet<>(); userAuthorityService.getUserAuthority(user).forEach(auth -> abilities.add(new AbilityModel(auth.getAuthority()))); diff --git a/src/main/java/com/ffii/tsms/model/JwtResponse.java b/src/main/java/com/ffii/tsms/model/JwtResponse.java index beaad81..f752fd8 100644 --- a/src/main/java/com/ffii/tsms/model/JwtResponse.java +++ b/src/main/java/com/ffii/tsms/model/JwtResponse.java @@ -1,6 +1,7 @@ package com.ffii.tsms.model; import java.io.Serializable; +import java.util.Map; import java.util.Set; import com.ffii.tsms.modules.data.entity.Staff; @@ -16,11 +17,9 @@ public class JwtResponse implements Serializable { private final String refreshToken; private final String role; private final Set abilities; - private final Staff staff; + private final Map staff; - - - public JwtResponse(String accessToken, String refreshToken, String role, User user, Set abilities, Staff staff) { + public JwtResponse(String accessToken, String refreshToken, String role, User user, Set abilities, Map staff) { this.accessToken = accessToken; this.refreshToken = refreshToken; this.role = role; @@ -55,7 +54,7 @@ public class JwtResponse implements Serializable { return email; } - public Staff getStaff() { return staff; } + public Map getStaff() { return staff; } public Set getAbilities() { 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 26d2695..add47f1 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 @@ -93,6 +93,27 @@ open class StaffsService( return jdbcDao.queryForMap(sql.toString(), args) } + open fun getCurrentStaff(userId: Long): Optional>? { + val staff = staffRepository.findByUserId(userId).orElse(null) + logger.info(staff) + + if (staff == null) { + return Optional.ofNullable(null) + } + + val sql = StringBuilder("select " + + " s.id as id, " + + " t.id as teamId, " + + " case when t.teamLead = s.id then true else false end as isTeamLead " + + " from staff s " + + " left join team t on t.id = s.teamId " + + " where s.deleted = false " + + " and s.id = " + staff.id + ) + + return jdbcDao.queryForMap(sql.toString()) + } + @Transactional(rollbackFor = [Exception::class]) open fun saveStaff(req: NewStaffRequest): Staff { val checkStaffIdList: List = staffRepository.findStaffSearchInfoByAndDeletedFalse() diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/StaffAllocationRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/StaffAllocationRepository.kt index 31ca9b8..7f3927a 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/StaffAllocationRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/StaffAllocationRepository.kt @@ -3,10 +3,12 @@ package com.ffii.tsms.modules.project.entity; import com.ffii.core.support.AbstractRepository import com.ffii.tsms.modules.data.entity.Staff import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo +import org.springframework.data.jpa.repository.Query import java.time.LocalDate interface StaffAllocationRepository : AbstractRepository { - fun findAssignedProjectsByStaff(staff: Staff): List + @Query("SELECT sa.project FROM StaffAllocation sa WHERE sa.staff = ?1 AND sa.project.status = 'On-going'") + fun findOnGoingAssignedProjectsByStaff(staff: Staff): List fun findByProject(project: Project): List } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/entity/TaskTemplateRepository.kt b/src/main/java/com/ffii/tsms/modules/project/entity/TaskTemplateRepository.kt index 90efb7c..8b0c701 100644 --- a/src/main/java/com/ffii/tsms/modules/project/entity/TaskTemplateRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/project/entity/TaskTemplateRepository.kt @@ -3,4 +3,5 @@ package com.ffii.tsms.modules.project.entity; import com.ffii.core.support.AbstractRepository interface TaskTemplateRepository : AbstractRepository { + fun findByCode(code: String): TaskTemplate? } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt b/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt index 16012b6..dfb9bbe 100644 --- a/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt +++ b/src/main/java/com/ffii/tsms/modules/project/service/ProjectsService.kt @@ -75,29 +75,27 @@ open class ProjectsService( open fun allAssignedProjects(): List { return SecurityUtils.getUser().getOrNull()?.let { user -> staffRepository.findByUserId(user.id).getOrNull()?.let { staff -> - staffAllocationRepository.findAssignedProjectsByStaff(staff).mapNotNull { - it.project?.let { project -> - val timesheetHours = timesheetRepository.totalHoursConsumedByProject(project) - - AssignedProject(id = project.id!!, - code = project.code!!, - name = project.name!!, - tasks = projectTaskRepository.findAllByProject(project).mapNotNull { pt -> pt.task }, - milestones = milestoneRepository.findAllByProject(project) - .filter { milestone -> milestone.taskGroup?.id != null } - .associateBy { milestone -> milestone.taskGroup!!.id!! } - .mapValues { (_, milestone) -> - MilestoneInfo( - startDate = milestone.startDate?.format(DateTimeFormatter.ISO_LOCAL_DATE), - endDate = milestone.endDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) - ) - }, - hoursAllocated = project.totalManhour ?: 0.0, - hoursAllocatedOther = 0.0, - hoursSpent = timesheetHours.normalConsumed, - hoursSpentOther = timesheetHours.otConsumed - ) - } + staffAllocationRepository.findOnGoingAssignedProjectsByStaff(staff).map { project -> + val timesheetHours = timesheetRepository.totalHoursConsumedByProject(project) + + AssignedProject(id = project.id!!, + code = project.code!!, + name = project.name!!, + tasks = projectTaskRepository.findAllByProject(project).mapNotNull { pt -> pt.task }, + milestones = milestoneRepository.findAllByProject(project) + .filter { milestone -> milestone.taskGroup?.id != null } + .associateBy { milestone -> milestone.taskGroup!!.id!! } + .mapValues { (_, milestone) -> + MilestoneInfo( + startDate = milestone.startDate?.format(DateTimeFormatter.ISO_LOCAL_DATE), + endDate = milestone.endDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) + ) + }, + hoursAllocated = project.totalManhour ?: 0.0, + hoursAllocatedOther = 0.0, + hoursSpent = timesheetHours.normalConsumed, + hoursSpentOther = timesheetHours.otConsumed + ) } } } ?: emptyList() @@ -108,6 +106,7 @@ open class ProjectsService( ProjectWithTasks(id = project.id!!, code = project.code!!, name = project.name!!, + status = project.status, tasks = projectTaskRepository.findAllByProject(project).mapNotNull { pt -> pt.task }, milestones = milestoneRepository.findAllByProject(project) .filter { milestone -> milestone.taskGroup?.id != null } diff --git a/src/main/java/com/ffii/tsms/modules/project/service/TasksService.kt b/src/main/java/com/ffii/tsms/modules/project/service/TasksService.kt index 98d6247..acebfab 100644 --- a/src/main/java/com/ffii/tsms/modules/project/service/TasksService.kt +++ b/src/main/java/com/ffii/tsms/modules/project/service/TasksService.kt @@ -3,10 +3,7 @@ package com.ffii.tsms.modules.project.service import com.ffii.core.support.JdbcDao import com.ffii.tsms.modules.data.entity.GradeRepository import com.ffii.tsms.modules.project.entity.* -import com.ffii.tsms.modules.project.web.models.EditTaskTemplateDetails -import com.ffii.tsms.modules.project.web.models.NewProjectRequest -import com.ffii.tsms.modules.project.web.models.NewTaskTemplateRequest -import com.ffii.tsms.modules.project.web.models.TaskGroupAllocation +import com.ffii.tsms.modules.project.web.models.* import org.springframework.stereotype.Service import kotlin.jvm.optionals.getOrNull @@ -36,6 +33,10 @@ class TasksService( taskTemplateRepository.deleteById(id) } + fun findTaskTemplateByCode(code: String): TaskTemplate? { + return taskTemplateRepository.findByCode(code) + } + fun getTaskTemplateDetails(id: Long): EditTaskTemplateDetails? { val taskTemplate = taskTemplateRepository.findById(id) @@ -63,8 +64,19 @@ class TasksService( } } - fun saveTaskTemplate(request: NewTaskTemplateRequest): TaskTemplate { + fun saveTaskTemplate(request: NewTaskTemplateRequest): NewTaskTemplateResponse { val id = request.id + + val duplicateTaskTemplate = findTaskTemplateByCode(request.code) + + //check duplicate customer + if (duplicateTaskTemplate != null && duplicateTaskTemplate.id?.equals(id) == false) { + return NewTaskTemplateResponse( + taskTemplate = duplicateTaskTemplate, + message = "The task template code has already existed" + ); + } + val taskTemplate = if (id != null && id > 0) findTaskTemplate(id) else TaskTemplate() taskTemplate.apply { this.name = request.name @@ -96,17 +108,13 @@ class TasksService( val savedTaskTemplate = taskTemplateRepository.save(taskTemplate) - - println(taskTemplateGroupAllocationRepository.findAllByTaskTemplate(taskTemplate).size) - println(groupAllocations.size) - println(taskTemplateGroupAllocationRepository.findAllByTaskTemplate(taskTemplate).subtract(gradeAllocations.toSet()).size) taskTemplateGradeAllocationRepository.deleteAll(taskTemplateGradeAllocationRepository.findAllByTaskTemplate(taskTemplate).subtract(gradeAllocations.toSet())) taskTemplateGroupAllocationRepository.deleteAll(taskTemplateGroupAllocationRepository.findAllByTaskTemplate(taskTemplate).subtract(groupAllocations.toSet())) taskTemplateGradeAllocationRepository.saveAll(gradeAllocations) taskTemplateGroupAllocationRepository.saveAll(groupAllocations) - return savedTaskTemplate + return NewTaskTemplateResponse(taskTemplate = savedTaskTemplate, message = "Success"); } fun allTaskGroups(): List { diff --git a/src/main/java/com/ffii/tsms/modules/project/web/TasksController.kt b/src/main/java/com/ffii/tsms/modules/project/web/TasksController.kt index a454bbc..51c68b1 100644 --- a/src/main/java/com/ffii/tsms/modules/project/web/TasksController.kt +++ b/src/main/java/com/ffii/tsms/modules/project/web/TasksController.kt @@ -6,6 +6,7 @@ import com.ffii.tsms.modules.project.entity.TaskTemplate import com.ffii.tsms.modules.project.service.TasksService import com.ffii.tsms.modules.project.web.models.EditTaskTemplateDetails import com.ffii.tsms.modules.project.web.models.NewTaskTemplateRequest +import com.ffii.tsms.modules.project.web.models.NewTaskTemplateResponse import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.* @@ -40,7 +41,7 @@ class TasksController(private val tasksService: TasksService) { } @PostMapping("/templates/save") - fun saveTaskTemplate(@Valid @RequestBody newTaskTemplate: NewTaskTemplateRequest): TaskTemplate { + fun saveTaskTemplate(@Valid @RequestBody newTaskTemplate: NewTaskTemplateRequest): NewTaskTemplateResponse { return tasksService.saveTaskTemplate(newTaskTemplate) } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/web/models/NewTaskTemplateResponse.kt b/src/main/java/com/ffii/tsms/modules/project/web/models/NewTaskTemplateResponse.kt new file mode 100644 index 0000000..8fdb123 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/project/web/models/NewTaskTemplateResponse.kt @@ -0,0 +1,8 @@ +package com.ffii.tsms.modules.project.web.models + +import com.ffii.tsms.modules.project.entity.TaskTemplate + +data class NewTaskTemplateResponse ( + val taskTemplate: TaskTemplate, + val message: String, +) \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/project/web/models/ProjectWithTasks.kt b/src/main/java/com/ffii/tsms/modules/project/web/models/ProjectWithTasks.kt index 83b9922..8d0bce7 100644 --- a/src/main/java/com/ffii/tsms/modules/project/web/models/ProjectWithTasks.kt +++ b/src/main/java/com/ffii/tsms/modules/project/web/models/ProjectWithTasks.kt @@ -5,6 +5,7 @@ import com.ffii.tsms.modules.project.entity.Task data class ProjectWithTasks( val id: Long, val code: String, + val status: String?, val name: String, val tasks: List, val milestones: Map 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 67328c6..5b27922 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 @@ -1721,9 +1721,11 @@ open class ReportService( + " result.code, " + " result.name, " + " result.teamCode, " - + " result.custCode, " ) + + " result.custCode, " + + " result.subCode, " ) if (args.get("outstanding") as Boolean) { - sql.append(" result.projectFee - (result.totalBudget - COALESCE(i.issueAmount , 0) + COALESCE(i.issueAmount, 0) - COALESCE(i.paidAmount, 0)) as `Receivable Remained`, ") + sql.append(" result.projectFee - COALESCE(i.paidAmount, 0) as `Receivable Remained`, ") +// sql.append(" result.projectFee - (result.totalBudget - COALESCE(i.issueAmount , 0) + COALESCE(i.issueAmount, 0) - COALESCE(i.paidAmount, 0)) as `Receivable Remained`, ") } sql.append( " DATE_FORMAT(result.actualEnd, '%d/%m/%Y') as actualEnd " @@ -1734,6 +1736,7 @@ open class ReportService( + " min(p.name) as name, " + " min(t.code) as teamCode, " + " min(c.code) as custCode, " + + " COALESCE(concat(min(ss.code),' - ',min(ss.name)), 'N/A') as subCode, " + " min(p.actualEnd) as actualEnd, " + " min(p.expectedTotalFee) as projectFee, " + " sum(COALESCE(tns.totalConsumed*sal.hourlyRate, 0)) as totalBudget " @@ -1754,14 +1757,18 @@ open class ReportService( + " left join salary sal on s.salaryId = sal.salaryPoint " + " left JOIN team t ON s.teamId = t.id " + " left join customer c on c.id = p.customerId " - + " where p.deleted = false " - + " and p.status = 'Completed' " + + " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " + + " where p.deleted = false ") + if (args.containsKey("teamId")) { + sql.append("t.id = :teamId") + } + sql.append( + " and p.status = 'Completed' " + " and p.actualEnd BETWEEN :startDate and :endDate " + " group by pt.project_id " + " ) as result " + " left join invoice i on result.code = i.projectCode " - + " order by result.actualEnd " - ) + + " order by result.actualEnd ") return jdbcDao.queryForList(sql.toString(), args) } @@ -1794,7 +1801,7 @@ open class ReportService( + " p.name, " + " t.code as team, " + " c.code as client, " - + " COALESCE(ss.name, 'N/A') as subsidiary, " + + " COALESCE(ss.code, 'N/A') as subsidiary, " + " p.expectedTotalFee * 0.8 as plannedBudget, " + " COALESCE(tns.totalBudget, 0) as actualConsumedBudget, " + " COALESCE(p.totalManhour, 0) as plannedManhour, " 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 4d2464e..563801f 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 @@ -2,9 +2,11 @@ package com.ffii.tsms.modules.report.web import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.ffii.tsms.modules.common.SecurityUtils import com.ffii.tsms.modules.data.entity.* //import com.ffii.tsms.modules.data.entity.projections.FinancialStatusReportInfo import com.ffii.tsms.modules.data.service.CustomerService +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.service.InvoiceService @@ -48,6 +50,7 @@ class ReportController( private val customerRepository: CustomerRepository, private val staffRepository: StaffRepository, private val leaveRepository: LeaveRepository, + private val staffsService: StaffsService, private val teamService: TeamService, private val customerService: CustomerService, private val subsidiaryService: SubsidiaryService, @@ -184,6 +187,9 @@ class ReportController( "endDate" to request.endDate, "outstanding" to request.outstanding ) + if (request.teamId != null) { + args["teamId"] = request.teamId + } val result = excelReportService.getProjectCompletionReport(args); val reportResult: ByteArray = excelReportService.generateProjectCompletionReport(args, result) // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") 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 a99d6e7..c56925d 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 @@ -52,6 +52,7 @@ data class ProjectResourceOverconsumptionReport ( val lowerLimit: Double ) data class ProjectCompletionReport ( + val teamId: Long?, val startDate: LocalDate, val endDate: LocalDate, val outstanding: Boolean diff --git a/src/main/resources/templates/report/AR05_Project Completion Report.xlsx b/src/main/resources/templates/report/AR05_Project Completion Report.xlsx index d9187b6..2c07680 100644 Binary files a/src/main/resources/templates/report/AR05_Project Completion Report.xlsx and b/src/main/resources/templates/report/AR05_Project Completion Report.xlsx differ diff --git a/src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx b/src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx index 700dc59..ee9bcda 100644 Binary files a/src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx and b/src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx differ